協(xié)作崩潰:從局部正確到整體穩(wěn)定的排查與測(cè)試方法)
不管是自己做過(guò)的多 Agent 系統(tǒng)還是幫別人排查過(guò)的我聽(tīng)到最多的一句話就是“每個(gè) Agent 我都單獨(dú)測(cè)過(guò)了都能跑為什么一合起來(lái)就崩”這句話幾乎成了多 Agent 協(xié)作領(lǐng)域的“經(jīng)典開(kāi)場(chǎng)白”。而且說(shuō)這話的人往往不是在推卸責(zé)任他是真冤——每個(gè) Agent 的單元測(cè)試確實(shí)過(guò)了接口調(diào)通了甚至單獨(dú)壓測(cè)都沒(méi)問(wèn)題。但系統(tǒng)一旦跑起來(lái)要么任務(wù)卡死要么狀態(tài)錯(cuò)亂要么消息瘋狂重試最后整個(gè)流程徹底癱掉。這篇我就把這類(lèi)問(wèn)題掰開(kāi)揉碎講清楚為什么“每個(gè) Agent 都測(cè)過(guò)”在協(xié)作系統(tǒng)里根本不成立真正的坑藏在哪些位置以及我實(shí)際用來(lái)排查和預(yù)防這些崩潰的一套方法。如果你正在搭多 Agent 系統(tǒng)或者已經(jīng)被線上協(xié)作問(wèn)題折磨過(guò)這篇文章應(yīng)該能幫你省下不少排查時(shí)間。1. “每個(gè) Agent 都測(cè)過(guò)”是個(gè)典型的局部正確陷阱先別急著往代碼層面查。這個(gè)問(wèn)題的根子在于我們對(duì)“測(cè)過(guò)”這件事的理解出了偏差。1.1 單點(diǎn)正常組合未必正常一個(gè)多 Agent 系統(tǒng)本質(zhì)上是一堆各自有輸入輸出的獨(dú)立單元通過(guò)消息互相配合最終完成一件單個(gè) Agent 干不了的事。這個(gè)過(guò)程很像一條手工流水線每個(gè)工位的人都覺(jué)得自己手上的活兒干得沒(méi)問(wèn)題但最終裝配出來(lái)的東西可能就是缺個(gè)零件、擰反了螺絲、或者用了不匹配的型號(hào)。Agent 的情況比流水線更復(fù)雜。流水線至少傳送帶順序固定、節(jié)拍固定而 Agent 之間的交互往往是異步的、并發(fā)的、有條件的。A 和 B 單獨(dú)跑都沒(méi)事但 A 的某個(gè)輸入恰好是 B 的某種輸出格式A 處理超時(shí)的時(shí)候 B 正好在等它A 重試了三次B 那邊已經(jīng)生成了重復(fù)數(shù)據(jù)。這些交互層面的問(wèn)題你在單獨(dú)測(cè)任何一個(gè) Agent 的時(shí)候根本觸發(fā)不了。所以“每個(gè) Agent 都測(cè)過(guò)了”這句話翻譯過(guò)來(lái)就是我驗(yàn)證了每個(gè)零件的靜態(tài)屬性但我沒(méi)有驗(yàn)證零件之間的咬合關(guān)系。而在協(xié)作系統(tǒng)里恰恰是咬合關(guān)系決定了一切。1.2 “測(cè)過(guò)了”到底是測(cè)到了哪一層為了把問(wèn)題看得更清楚我一般都建議團(tuán)隊(duì)把“測(cè)過(guò)”分三層測(cè)試層級(jí)驗(yàn)證對(duì)象典型問(wèn)題覆蓋情況單元測(cè)試單個(gè) Agent 內(nèi)部邏輯、狀態(tài)轉(zhuǎn)換覆蓋不了協(xié)作問(wèn)題接口測(cè)試兩個(gè) Agent 之間的單次請(qǐng)求響應(yīng)能發(fā)現(xiàn)報(bào)文格式問(wèn)題但覆蓋不了時(shí)序、重復(fù)、并發(fā)協(xié)作流測(cè)試多個(gè) Agent 按真實(shí)流程共同執(zhí)行能覆蓋大部分協(xié)作問(wèn)題但最容易被忽視絕大多數(shù)人說(shuō)的“測(cè)過(guò)了”指的是前兩層。你測(cè)了 A 能處理輸入 X測(cè)了 B 能響應(yīng) A 的請(qǐng)求但你沒(méi)測(cè) A 處理到一半時(shí) B 發(fā)來(lái)了新消息、沒(méi)測(cè) A 的響應(yīng)和數(shù)據(jù)落庫(kù)之間的故障窗口、沒(méi)測(cè) B 在 A 故障時(shí)會(huì)不會(huì)自己把自己繞進(jìn)死循環(huán)。這些恰恰是“系統(tǒng)崩了”的高發(fā)原因。我第一次強(qiáng)烈意識(shí)到這個(gè)問(wèn)題是做一個(gè)模擬多 Agent 協(xié)作的 Demo。任務(wù)分配 Agent、執(zhí)行 Agent、匯總 Agent 三個(gè)角色分別測(cè)了個(gè)遍全綠。結(jié)果聯(lián)調(diào)一跑整個(gè)流程十分鐘就跑完的事跑了四十分鐘還沒(méi)結(jié)束。查了半天發(fā)現(xiàn)是匯總 Agent 把上游消息當(dāng)成了必達(dá)消息用了同步阻塞的等待方式而執(zhí)行 Agent 因?yàn)橘Y源競(jìng)爭(zhēng)頻繁超時(shí)超時(shí)之后又觸發(fā)上游重試重試的消息又把匯總 Agent 的隊(duì)列塞滿了。每個(gè) Agent 單獨(dú)拉出來(lái)都是正常邏輯但放一起就是互相拖垮。2. 多 Agent 系統(tǒng)真正會(huì)崩的位置基本就這幾個(gè)排查多了就會(huì)發(fā)現(xiàn)多 Agent 系統(tǒng)的崩潰位置是有規(guī)律可循的。別到處瞎查優(yōu)先看這四個(gè)地方。2.1 共享狀態(tài)和資源的所有權(quán)說(shuō)不清這是最常見(jiàn)、也最隱蔽的坑。多個(gè) Agent 如果共享同一份狀態(tài)數(shù)據(jù)比如同一個(gè)緩存、同一個(gè)數(shù)據(jù)庫(kù)記錄、同一個(gè)內(nèi)存對(duì)象就必須明確一個(gè)問(wèn)題這份數(shù)據(jù)的“寫(xiě)權(quán)限”到底歸誰(shuí)。我見(jiàn)過(guò)一個(gè)真實(shí)場(chǎng)景兩個(gè) Agent 都會(huì)去更新某個(gè)任務(wù)的進(jìn)度字段。Agent A 負(fù)責(zé)更新“執(zhí)行狀態(tài)”Agent B 負(fù)責(zé)更新“質(zhì)量檢查狀態(tài)”。單獨(dú)看都沒(méi)問(wèn)題但 A 更新時(shí)是按“當(dāng)前記錄”整行讀出來(lái)再改的B 也是這么干的。結(jié)果就是 A 寫(xiě)完了B 基于舊的快照覆蓋寫(xiě)回去A 的更新直接丟了。這不是并發(fā)課里那種教科書(shū)級(jí)死鎖問(wèn)題就是最簡(jiǎn)單的“丟失更新”但因?yàn)樗l(fā)生在兩個(gè)服務(wù)之間排查起來(lái)特別費(fèi)勁。更麻煩的是隱式共享。比如兩個(gè) Agent 都調(diào)用了同一個(gè)第三方接口接口另一端的狀態(tài)是共享的。Agent A 重置了某個(gè)配置Agent B 還在按舊配置干活兩邊都覺(jué)得自己是在正常執(zhí)行。這類(lèi)問(wèn)題有個(gè)常見(jiàn)的早期信號(hào)系統(tǒng)偶爾不對(duì)但單獨(dú)查任何一個(gè) Agent 的數(shù)據(jù)都找不出毛病。如果你發(fā)現(xiàn)“數(shù)據(jù)看起來(lái)都對(duì)但整體結(jié)果就是怪”優(yōu)先懷疑共享狀態(tài)的所有權(quán)是不是沒(méi)有收緊。2.2 消息契約只對(duì)了一半多 Agent 協(xié)作是靠消息串聯(lián)的消息契約的問(wèn)題是崩的第二大來(lái)源。這里的“契約”不只是字段格式對(duì)不對(duì)還包括三個(gè)容易忽略的維度。第一個(gè)是語(yǔ)義契約。兩個(gè) Agent 對(duì)于同一個(gè)字段的理解可能不一樣。比如一個(gè)字段叫“status”A 的想法是“業(yè)務(wù)生命周期狀態(tài)”B 的理解是“處理進(jìn)度標(biāo)識(shí)”。A 傳了一個(gè)“completed”B 按“已經(jīng)結(jié)束不再處理”來(lái)響應(yīng)但實(shí)際上 A 的意思只是“當(dāng)前這一步完成了后面還有步驟”。這種歧義在各自開(kāi)發(fā)時(shí)很難暴露聯(lián)調(diào)時(shí)一旦出現(xiàn)表現(xiàn)出的問(wèn)題五花八門(mén)。第二個(gè)是時(shí)序契約。A 發(fā)消息給 B是不是一定要等 B 有響應(yīng)才繼續(xù)如果 B 暫時(shí)不可用消息是排隊(duì)、丟棄、還是重試A 發(fā)出去的消息有沒(méi)有順序要求這些時(shí)序規(guī)則如果只寫(xiě)在文檔里代碼里任何一方?jīng)]遵守協(xié)作就直接亂掉。第三個(gè)是冪等契約。分布式環(huán)境里消息重復(fù)是常態(tài)不是異常。B 收到重復(fù)消息時(shí)能不能正確去重A 重試的時(shí)候會(huì)不會(huì)產(chǎn)生重復(fù)副作用我見(jiàn)過(guò)最典型的崩潰現(xiàn)場(chǎng)一個(gè)通知 Agent 收到重試消息后把同一條通知發(fā)了五遍用戶端直接炸了。判斷消息契約是不是有問(wèn)題的辦法很簡(jiǎn)單把所有 Agent 之間傳遞的真實(shí)消息樣本收集起來(lái)看看字段覆蓋、取值空間、時(shí)序關(guān)系再對(duì)照每個(gè) Agent 的處理邏輯是否覆蓋了這些情況。不需要多復(fù)雜大部分契約問(wèn)題在這些樣本面前都會(huì)現(xiàn)形。2.3 編排層是最大的單點(diǎn)偽命題很多人把多 Agent 系統(tǒng)的希望寄托在編排層覺(jué)得只要有一個(gè)靠譜的調(diào)度者在中間控制就不會(huì)亂。但編排層往往自己就是最脆弱的環(huán)節(jié)。編排層的工作本質(zhì)上是做決策下一步讓誰(shuí)干活、等待多久、失敗了怎么辦。這三個(gè)問(wèn)題每一個(gè)都容易出嚴(yán)重事故。等待多久這個(gè)事最考驗(yàn)設(shè)計(jì)。超時(shí)設(shè)置得太短Agent 明明在正常處理只是稍微慢了點(diǎn)編排層就判定失敗并觸發(fā)重試然后重復(fù)任務(wù)把系統(tǒng)壓垮超時(shí)設(shè)置得太長(zhǎng)故障 Agent 會(huì)讓整個(gè)流程長(zhǎng)時(shí)間掛起用戶看到的就是“卡死了”。編排層的另一個(gè)隱蔽問(wèn)題是它本身持有的全局狀態(tài)。如果編排層靠?jī)?nèi)存保存“當(dāng)前流程執(zhí)行到哪一步”一旦編排服務(wù)重啟整個(gè)流程就斷掉。想讓系統(tǒng)穩(wěn)定編排層的狀態(tài)必須落到可靠存儲(chǔ)里但這又引入了一致性和性能問(wèn)題。很多系統(tǒng)的崩潰表面上是某個(gè) Agent 出了故障實(shí)際是編排層在故障恢復(fù)過(guò)程中的狀態(tài)錯(cuò)亂把局部問(wèn)題放大成了整體不可用。2.4 級(jí)聯(lián)故障比故障本身更可怕多 Agent 系統(tǒng)里最經(jīng)典的崩潰模式不是什么高深的理論問(wèn)題就是級(jí)聯(lián)故障。A 依賴 BB 處理慢了A 等得不耐煩開(kāi)始重試重試讓 B 的負(fù)載更高B 更慢A 更頻繁地重試最后 B 被壓垮A 也超時(shí)崩掉C 繼續(xù)等 A把整個(gè)鏈路都拖死。這個(gè)模式我見(jiàn)過(guò)太多次了。它最難受的地方在于一開(kāi)始只是一個(gè) Agent 的輕微性能抖動(dòng)結(jié)果卻演化成整個(gè)系統(tǒng)的癱瘓。單獨(dú)測(cè)的時(shí)候你根本不可能觸發(fā)這種場(chǎng)景因?yàn)樗?Agent 都處在資源充足、響應(yīng)快速的情況下。應(yīng)對(duì)級(jí)聯(lián)故障核心不是讓每個(gè) Agent 都變得更健壯而是要在協(xié)作層面加入“熔斷”和“降級(jí)”的機(jī)制。B 已經(jīng)不行了A 能不能不重試直接走降級(jí)邏輯C 能不能感知到 B 的故障不要傻等這些機(jī)制需要主動(dòng)設(shè)計(jì)不可能等系統(tǒng)崩了以后再指望 Agent 自己“商量”出對(duì)策。3. 怎么把多 Agent 系統(tǒng)真正測(cè)到“敢上線”既然“每個(gè)測(cè)過(guò)”不夠那要測(cè)到什么程度才算夠我的答案是不僅要測(cè) Agent更要測(cè) Agent 之間的“協(xié)作文檔”。下面這套思路我實(shí)踐過(guò)多次雖然不能保證零故障但基本能避免“上線就崩”的尷尬。3.1 先把每個(gè) Agent 的行為邊界固定下來(lái)Agent 之間出現(xiàn)問(wèn)題很多時(shí)候不是因?yàn)槟骋粋€(gè) Agent 寫(xiě)錯(cuò)了而是它的行為邊界沒(méi)定義清楚。我建議在寫(xiě)代碼之前先給每個(gè) Agent 補(bǔ)上三個(gè)標(biāo)準(zhǔn)文檔接口說(shuō)明輸入是什么、輸出是什么、會(huì)不會(huì)有副作用什么情況下返回成功、什么情況下返回失敗、失敗之后怎么重試。狀態(tài)機(jī)這個(gè) Agent 有哪些狀態(tài)事件是什么狀態(tài)之間怎么遷移尤其是“異常狀態(tài)”和“恢復(fù)路徑”必須寫(xiě)清楚。資源假設(shè)這個(gè) Agent 默認(rèn)哪些資源是可用的哪些是不可用的如果假設(shè)被打破了它應(yīng)該怎么表現(xiàn)。邊界文檔的作用是讓每個(gè) Agent 的開(kāi)發(fā)者對(duì)“這個(gè) Agent 的承諾”達(dá)成一致。我之前參與的一個(gè)模擬系統(tǒng)里任務(wù)分配 Agent 和任務(wù)執(zhí)行 Agent 各有一個(gè)“狀態(tài)機(jī)”文檔兩邊對(duì)“failed”狀態(tài)的理解一直不同聯(lián)調(diào)的時(shí)候來(lái)回改了好幾輪。后來(lái)把狀態(tài)機(jī)畫(huà)出來(lái)貼在文檔里一對(duì)比就發(fā)現(xiàn)了問(wèn)題。不需要什么高端工具一張表、幾行字就能避免大量的互相指責(zé)和返工。3.2 用“場(chǎng)景編排測(cè)試”代替單測(cè)堆疊單 Agent 的測(cè)試照做但真正的協(xié)作驗(yàn)證必須放在完整場(chǎng)景里做。你可以把核心業(yè)務(wù)場(chǎng)景列出來(lái)比如“用戶發(fā)起任務(wù)、多 Agent 協(xié)作完成、返回結(jié)果”為每個(gè)場(chǎng)景構(gòu)造輸入讓整個(gè) Agent 群真實(shí)跑一遍。這里有個(gè)關(guān)鍵點(diǎn)不要只測(cè)“成功路徑”。至少要覆蓋下面這幾種場(chǎng)景某個(gè) Agent 不可用直接停掉它。某個(gè) Agent 響應(yīng)很慢人為加延遲。消息重復(fù)送達(dá)手動(dòng)重發(fā)消息。編排層重啟過(guò)程中任務(wù)正在執(zhí)行。多個(gè) Agent 同時(shí)修改同一份數(shù)據(jù)。如果你把每個(gè)核心場(chǎng)景都跑完這五種故障注入大概率能發(fā)現(xiàn)真正會(huì)讓系統(tǒng)崩掉的問(wèn)題。而且這個(gè)過(guò)程不要手工點(diǎn)點(diǎn)點(diǎn)最好把它腳本化沉淀成自動(dòng)化測(cè)試。每次改動(dòng)任何 Agent 的邏輯都要跑一遍整個(gè)協(xié)作場(chǎng)景不然你就等于又回到了“每個(gè) Agent 都測(cè)過(guò)”的狀態(tài)。3.3 給 Agent 加上“故障演練日”比自動(dòng)化測(cè)試更進(jìn)一步的做法是定期做故障演練。不是只在測(cè)試環(huán)境練而是挑一個(gè)低峰期在預(yù)發(fā)布甚至生產(chǎn)環(huán)境主動(dòng)搞事。當(dāng)然這要先在團(tuán)隊(duì)內(nèi)部達(dá)成共識(shí)演練期間不追求核心指標(biāo)專心觀察系統(tǒng)表現(xiàn)。我個(gè)人的經(jīng)驗(yàn)是一次好的故障演練能讓你發(fā)現(xiàn)至少三個(gè)單測(cè)完全覆蓋不到的協(xié)作問(wèn)題。印象很深的一次我們?cè)谘菥氈腥藶樽屢粋€(gè) Agent 響應(yīng)變慢本來(lái)預(yù)期只是增加一點(diǎn)處理延遲結(jié)果一個(gè)小時(shí)后發(fā)現(xiàn)所有 Agent 的線程池都快滿了數(shù)據(jù)庫(kù)連接數(shù)被打滿整個(gè)系統(tǒng)接近不可用。回頭一查是另一個(gè) Agent 在等待過(guò)程中做了同步阻塞調(diào)用而且沒(méi)有設(shè)置超時(shí)上限導(dǎo)致上游一慢、下游跟著全部掛起。這種問(wèn)題不通過(guò)真實(shí)場(chǎng)景的故障演練你根本意識(shí)不到它的存在。等線上真的發(fā)生了你一邊應(yīng)付業(yè)務(wù)方的投訴一邊手忙腳亂定位那種壓力完全是另一個(gè)數(shù)量級(jí)。4. 排查實(shí)錄一起典型的多 Agent 協(xié)作崩潰理論講多了容易飄我給你看一個(gè)實(shí)際排查案例。這是我?guī)鸵粋€(gè)模擬團(tuán)隊(duì)處理過(guò)的真實(shí)問(wèn)題細(xì)節(jié)我已經(jīng)處理掉不影響理解。4.1 崩潰現(xiàn)象這套系統(tǒng)跑著三個(gè) Agent調(diào)度 Agent、處理 Agent、匯總 Agent。調(diào)度 Agent 把一批數(shù)據(jù)分發(fā)下去處理 Agent 并行處理匯總 Agent 收集結(jié)果生成報(bào)表。某個(gè)下午系統(tǒng)開(kāi)始出現(xiàn)大面積卡頓任務(wù)隊(duì)列越積越長(zhǎng)沒(méi)過(guò)多久整個(gè)流程不再推進(jìn)。4.2 排查過(guò)程第一步看日志。最直觀的發(fā)現(xiàn)是處理 Agent 的日志里有大量超時(shí)和重試記錄重試次數(shù)在持續(xù)攀升。第二步看資源。處理 Agent 的線程池居高不下數(shù)據(jù)庫(kù)連接池被打滿。第三步回看時(shí)間線發(fā)現(xiàn)真正的引爆點(diǎn)是一個(gè)慢查詢某一條數(shù)據(jù)的處理觸發(fā)了數(shù)據(jù)庫(kù)層面的鎖等待導(dǎo)致處理 Agent 的響應(yīng)時(shí)間從平均 200ms 漲到了 5 秒。如果事情到此為止那只是個(gè)普通的數(shù)據(jù)庫(kù)問(wèn)題。真正致命的是后續(xù)反應(yīng)調(diào)度 Agent 看到處理 Agent 沒(méi)有及時(shí)確認(rèn)就開(kāi)始重試把同一條數(shù)據(jù)又發(fā)了過(guò)來(lái)處理 Agent 的鎖沒(méi)釋放新請(qǐng)求繼續(xù)排隊(duì)匯總 Agent 因?yàn)槭詹坏酵暾Y(jié)果也在等但它用的是阻塞調(diào)用占著線程不釋放。三個(gè) Agent 的“正常應(yīng)對(duì)”疊在一起形成了完美的雪崩。4.3 根本原因事后復(fù)盤(pán)有三個(gè)設(shè)計(jì)層面的問(wèn)題疊在一起。第一處理 Agent 的重試沒(méi)有退避策略而且重試上限設(shè)得過(guò)高相當(dāng)于故障期間把流量放大了好幾倍。第二匯總 Agent 對(duì)上游的等待沒(méi)有超時(shí)機(jī)制等于默認(rèn)上游無(wú)限可靠。第三調(diào)度 Agent 的重試和數(shù)據(jù)冪等沒(méi)有做好重復(fù)消息直接進(jìn)隊(duì)列處理而不是被去重丟棄。這三個(gè)問(wèn)題任何一個(gè)單獨(dú)存在都不至于讓整個(gè)系統(tǒng)崩掉但合在一起就呈現(xiàn)出了“全線癱瘓”的狀態(tài)。4.4 修復(fù)方案修的時(shí)候不是只改一個(gè)點(diǎn)而是三層一起調(diào)整。調(diào)度 Agent 增加指數(shù)退避加抖動(dòng)每次重試等待時(shí)間翻倍限制總重試次數(shù)同時(shí)做好消息去重。處理 Agent 為所有外部依賴設(shè)置超時(shí)上限超過(guò)時(shí)限直接返回失敗而不是無(wú)限等待同時(shí)對(duì)重復(fù)任務(wù)做冪等處理。匯總 Agent 改成異步響應(yīng)的交互方式不再占用線程干等而且增加了守護(hù)機(jī)制——超過(guò)一定時(shí)間沒(méi)有結(jié)果主動(dòng)向上游查詢狀態(tài)而不是一味等待。改完之后再模擬同樣的數(shù)據(jù)庫(kù)慢查詢場(chǎng)景系統(tǒng)仍然會(huì)出現(xiàn)部分處理失敗但不會(huì)再出現(xiàn)級(jí)聯(lián)癱瘓。重試的次數(shù)被控制住了其他 Agent 的正常運(yùn)轉(zhuǎn)沒(méi)被打斷。5. 多 Agent 協(xié)作常見(jiàn)問(wèn)題速查表把日常排查的經(jīng)驗(yàn)整理成一個(gè)速查表遇到問(wèn)題可以先對(duì)照看看。都是我實(shí)際碰到過(guò)的情況不是從教科書(shū)上搬來(lái)的。現(xiàn)象可能原因優(yōu)先排查方向系統(tǒng)偶爾結(jié)果不對(duì)但單項(xiàng)數(shù)據(jù)都對(duì)共享狀態(tài)丟失更新或覆蓋寫(xiě)檢查多個(gè) Agent 對(duì)同一數(shù)據(jù)的寫(xiě)路徑任務(wù)卡住不推進(jìn)沒(méi)有報(bào)錯(cuò)某個(gè) Agent 在同步等待沒(méi)設(shè)超時(shí)找到對(duì)應(yīng)的調(diào)用鏈看是否有無(wú)限等待上游一慢整個(gè)鏈路全掛超時(shí)設(shè)置不當(dāng)或沒(méi)有熔斷機(jī)制檢查重試策略和下游保護(hù)機(jī)制消息反復(fù)處理產(chǎn)生重復(fù)數(shù)據(jù)缺少冪等控制檢查消息去重和各 Agent 的冪等邏輯編排服務(wù)一重啟流程全丟調(diào)度狀態(tài)保存在內(nèi)存中將編排狀態(tài)持久化到可靠存儲(chǔ)多個(gè) Agent 更新同一任務(wù)狀態(tài)相互覆蓋寫(xiě)權(quán)限未收斂或沒(méi)有鎖機(jī)制明確單一寫(xiě)入方或引入版本控制壓測(cè)時(shí)流量一大就崩級(jí)聯(lián)故障和資源耗盡注入延遲觀察各 Agent 的排隊(duì)行為重試風(fēng)暴導(dǎo)致故障放大重試無(wú)退避、上限過(guò)高增加指數(shù)退避、限制總重試次數(shù)這個(gè)表不能解決所有問(wèn)題但每次排查前過(guò)一遍至少能幫你快速排除掉一大半的“經(jīng)典錯(cuò)誤”。6. 一些我踩過(guò)坑之后才想明白的事老實(shí)說(shuō)我早期做多 Agent 系統(tǒng)的時(shí)候也犯過(guò)“每個(gè) Agent 都測(cè)過(guò)就說(shuō)能上”的毛病。當(dāng)時(shí)還不理解為什么集成測(cè)試這么重要總覺(jué)得自己把每個(gè)組件測(cè)穩(wěn)了整體自然就穩(wěn)了。連續(xù)踩了兩次協(xié)作故障的坑之后我才意識(shí)到組件正確和系統(tǒng)正確之間隔著一整條交互邏輯的鴻溝。現(xiàn)在我做這類(lèi)系統(tǒng)有個(gè)習(xí)慣從一開(kāi)始就把“協(xié)作場(chǎng)景測(cè)試”當(dāng)成一級(jí)公民。寫(xiě)任何新 Agent 的代碼第一件事不是寫(xiě)單元測(cè)試而是先把它會(huì)跟哪些 Agent 交互、交互的失敗模式是什么列出來(lái)。所有交互都要配超時(shí)、重試上限、冪等策略所有共享狀態(tài)都要有明確的所有者。這些事情看起來(lái)費(fèi)時(shí)間但比起線上故障后的通宵排查這點(diǎn)時(shí)間簡(jiǎn)直太劃算了。最后再分享一個(gè)小技巧排查協(xié)作問(wèn)題時(shí)不要一上來(lái)就看代碼先把所有 Agent 之間的消息流時(shí)間線拉出來(lái)。哪條消息在什么時(shí)間發(fā)出、多久被消費(fèi)、有沒(méi)有重試、重試間隔是多長(zhǎng)這些信息一旦可視化大部分協(xié)作崩潰的原因都會(huì)一目了然。這比看任何源碼都管用。希望這次的分享能幫到正在跟多 Agent 協(xié)作死磕的朋友。系統(tǒng)崩潰不可怕可怕的是找不到一個(gè)靠譜的排查起點(diǎn)——希望這篇能成為你的那個(gè)起點(diǎn)。