當 Agent 協定遇上雲端:水平擴展的狀態幻覺
許多看似可水平擴展的 AI Agent 協定,在實際部署時卻因狀態管理問題而失效。本文從一個常見的 MCP 伺服器 404 錯誤出發,探討為何依賴「黏性會話」是治標不治本的基礎設施妥協,並提出真正的解決方案:將架構設計為無狀態請求、共享狀態層與可路由的執行語意,這是打造可信賴、可規模化 Agent 系統的關鍵一步。
面對 AI Agent 協定在雲端部署時的挑戰,尤其是 MCP 伺服器常見的 404 錯誤,我們發現將會話狀態綁定在單一服務實例,是導致水平擴展失敗的根本原因。雖然「黏性會話」能暫時解決問題,卻犧牲了負載平衡與系統韌性。真正的解方在於擁抱無狀態架構:透過無狀態請求、共享狀態層與可路由的執行語意,將 Agent 系統設計為可彈性擴展、高可用的雲端原生應用。這不僅是技術上的最佳實踐,更是確保 AI Agent 系統長期穩定運行的關鍵一步。
為什麼雲端部署會讓 Agent 協定頻頻 404?
在開發環境中,我們通常直接透過標準輸入輸出(stdio)或單一的網路端點與 Agent 伺服器互動。在這種情境下,整個互動生命週期都發生在同一個作業系統進程中,狀態管理非常直觀。一個 MCP 伺服器,從建立會話、列出可用工具(tools/list),到執行特定工具(tools/call),一切順暢。
然而,當我們將完全相同的應用程式部署到雲端,放在一個負載平衡器(如 AWS ELB 或 NGINX)後面並啟動多個實例時,災難就發生了。使用者發出的第一個請求,可能是初始化會話,被負載平衡器導向了實例 A。實例 A 在自己的記憶體中建立了會話狀態,並回傳了會話 ID。
但下一個使用該會話 ID 的 tools/call 請求,很可能被負載平衡器基於輪詢(Round-robin)或其他演算法導向了實例 B。對於實例 B 而言,這個會話 ID 是完全陌生的,因為相關的狀態資訊只存在於實例 A 的記憶體中。結果,實例 B 只能回傳一個誠實卻令人沮喪的 404 Not Found 錯誤。
這不是 MCP(Model-driven Communication Protocol)特有的問題,而是所有將狀態與特定實例綁定的協定在分散式環境中必然會遭遇的困境。
為什麼「黏性會話」不是根本解方?
面對上述問題,許多團隊的第一反應是啟用負載平衡器的「黏性會話(Sticky Sessions)」或稱會話親和性(Session Affinity)功能。這個功能會確保來自同一個使用者的所有請求,都被導向同一個後端實例。表面上,這解決了 404 錯誤,讓系統恢復運作。然而,我認為這是一種技術債,一個用基礎設施的妥協去掩蓋應用程式架構缺陷的短期解法。
依賴黏性會話會帶來幾個嚴重的副作用:
- 破壞負載平衡:它違背了負載平衡的初衷。如果某些使用者產生了大量運算密集的請求,所有負擔都會集中在少數幾個實例上,導致資源分配不均。
- 降低系統韌性:如果持有大量會話的那個實例因故崩潰或需要重啟,所有黏著在上面的使用者會話將全部遺失。這對需要長時間運行的 Agent 任務是致命的打擊。
- 增加擴展複雜性:自動水平擴展(Autoscaling)會變得困難。當系統需要縮減實例時,如何優雅地處理即將被關閉實例上的現存會話,成了一個棘手的問題。
黏性會話本質上是將一個無狀態的 HTTP 網路,強行扭轉成一個狀態化的、類似傳統桌面應用程式的互動模式。這與現代雲端原生應用所追求的彈性、可拋棄性(disposability)與水平擴展能力背道而馳。正如 The Twelve-Factor App 方法論中所強調的,應用程式應該以無狀態進程(stateless processes)的形式執行。
如何設計真正可擴展的 Agent 執行層?
根本的解決方案,是將應用程式本身從狀態化(stateful)改造為無狀態(stateless)。這意味著任何一個服務實例都能處理任何一個使用者的請求,因為執行請求所需的所有「狀態」或「上下文」,都不再儲存於實例自身的記憶體中,而是存放在一個所有實例都能存取的外部共享狀態層。
一個真正可擴展的 Agent 基礎設施應具備以下三個核心要素:
- 無狀態請求(Stateless Requests):每個進入系統的請求都應包含足夠的資訊,讓後端能獨立完成處理。例如,客戶端在初始化會話後,會得到一個全域唯一的
session_id。後續的每個請求,都必須在標頭(header)或內文(body)中攜帶這個session_id。 - 共享狀態層(Shared State Layer):所有與會話相關的資料,例如對話歷史、工具執行的中間結果、使用者的偏好設定等,都必須持久化到一個獨立的儲存系統中。根據需求的複雜度和延遲要求,這個系統可以是像 Redis 這樣的記憶體內資料庫(用於高速快取會話),也可以是 PostgreSQL 或 DynamoDB 這樣的資料庫(用於儲存結構化的長期狀態)。
- 可路由的執行語意(Routable Execution Semantics):當一個請求(例如
tools/call)攜帶著session_id抵達任何一個實例時,該實例的任務就是:(1) 使用session_id從共享狀態層讀取上下文;(2) 執行業務邏輯;(3) 將更新後的狀態寫回共享狀態層。如此一來,每個實例都變成了可互換的、純粹的計算單元,系統的擴展性與韌性才得以真正實現。
這種架構轉變,讓我們從擔心「請求會打到哪個實例」,變成了「任何實例都能處理任何請求」。這不僅解決了 MCP 部署的 404 問題,更為建立能夠處理數千甚至數百萬併發 Agent 會話的複雜系統奠定了堅實的基礎。這不只是技術細節的調整,而是從根本上擁抱分散式系統的設計哲學,是 Agent 基礎設施走向成熟的必經之路。
延伸閱讀
- MCPサーバーを本番スケールさせるにはセッションのステートレス化が必須
- Microservices by Martin Fowler
- Workload isolation using shuffling and sharding
我是江中喬,專注於 AI Agent Architecture、Memory Governance 與 Cognitive Diversity,持續研究如何打造能夠長期協作、可信任且可治理的 AI 系統。