2026 年 10 月 19 日,掘金出現一篇技術文章,作者 ErpanOmer 回顧自家 AI 客服助手全量上線三天後的一場事故:問答介面的 QPS 莫名翻倍,客訴湧入,使用者抱怨回答「半天不吐字」,以為卡死就不斷重試,重試流量又反過來壓垮閘道。查了一圈,問題不在大模型,而在傳輸層。

這是一篇給工程師看的後端文章,但如果你最近也在趕 AI 專案、盯過那種「畫面轉圈、使用者暴動」的夜晚,這則故事裡有技術,也有你自己的身體。

先把這場事故講清楚

依照作者描述,根因有兩層。第一層是 Nginx 的預設行為:代理緩衝(proxy_buffering)預設開啟,會把流式回應先攢一批再送出去,第一個字被硬生生拖到八秒才出現。使用者等不到字,就以為當機。

第二層在前端:團隊用的是最省事的裸 EventSource(瀏覽器內建的 SSE 連線物件),斷線不報錯、變慢不提示,使用者只能狂點重試。這些重試流量回頭又加重後端負擔,形成惡性循環。

作者下的結論很務實:下行主導、低頻、延遲要求不極致的場景,用 SSE 就夠;雙向即時、高頻上行、需要服端主動推、客戶端也要頻繁說話的場景,才值得上 WebSocket。多輪問答和摘要生成是 SSE 的主場,協作編輯、語音對講、即時遊戲狀態同步才是 WebSocket 的主場。

幾個工程判斷,白話版

EventSource 這條路的坑,作者列了三個。它只支援 GET、不能自訂請求頭,權杖只能拼在網址上,會留在各種日誌裡,安全審查過不了。斷線重連看起來自動,但若服務端沒配合回放 Last-Event-ID,重連後要嘛丟訊息,要嘛從頭重打一遍,使用者會看到回答閃一下重來。再加上 Nginx 緩衝那層,首字時間完全不可控。

WebSocket 那條路也不是免費的。長連線讓閘道要為每個連線維持狀態,連線數直接變成成本;對「問完就關」的客服頁來說,等於為低頻流量維持全雙工管線,實例數要翻好幾倍。行動端更麻煩,切背景、鎖屏、進電梯都會斷線,重連、重新鑑權、心跳與退避策略全要自己寫。

作者的解法是留在一端補強:改用 fetch 加 ReadableStream 實作生產級的 SSE 客戶端,同時解決 POST 與請求頭鑑權、可取消、按 Last-Event-ID 續接、退避重試四件事。這部分屬於單一團隊的實戰經驗,不是大型研究或業界共識,參考時記得自己驗證。

讀技術文章的你,也順便照顧一下

如果你正是那種「出事第三天被運維找上門」的人,有幾件事比選 SSE 還是 WebSocket 更急。

線上事故的壓力會讓人想一口氣修完。這篇文章裡那種「使用者重試、閘道被壓垮」的循環,也很常發生在救火的人身上:盯著儀表板、反覆重載頁面、熬夜到天亮。如果可以,把驗證工作切成可停下的段落,每次改動之間離開座位十分鐘,比連續盯八小時更容易看出問題。

眼睛的部分,排查這種「到底卡在哪一層」的問題,代表你要長時間來回切換終端機、日誌和瀏覽器。定時把視線移到遠處,讓負責近距離對焦的眼肌肉輪流休息。這個原則在我們先前聊測新模型要不要熬夜跑一輪時也提過,適用於所有半夜盯著串流輸出等第一個字的人。

還有一個資訊識讀的小提醒。這類「我踩過的坑」文章價值很高,但它反映的是特定技術棧、特定流量情境下的經驗。你的 Nginx 設定、使用者行為、行動端佔比都可能不同。把它當檢查清單用,先確認自己的緩衝設定與重試行為,而不是直接照搬結論。這一點,和德國法院限制 Google AI 摘要錯誤陳述背後的道理相通:任何來源的摘要式結論,都值得回頭對一下原始脈絡。

給非工程師的你

就算你不寫程式,這則故事也有一個用得到的版本:當你用的 AI 工具回答遲遲不出字,它未必是模型變笨了,可能是中間任何一段管線在卡。與其狂點重試加重整條鏈路的負擔,重新整理一次、等個幾秒,往往更有效。

而如果你身邊有最近正在救這種火的人,幫他帶杯水、提醒他喫飯,比叫他「快一點」有用得多。管子會修好的,人要先顧好。