遊戲 Lag 白皮書 › L9 伺服器遊戲程式
執行緒池耗盡 Thread pool starvation
原因 ID sp-threadpool · 主要負責 遊戲開發團隊(伺服器開發)
在含圖解與實驗的完整版中開啟卡片 →
負責處理工作的工作執行緒全被慢的工作綁住時,新請求只能無止盡地等待。
為什麼 工作執行緒都被綁在等待外部 API 或 DB 回應上 → 於是 沒有執行緒可以接新請求 → 畫面上 登入、商城等特定功能出現無限讀取
- 症狀
- 連不上/無限讀取, 輸入延遲, 定格
- 因素
- 停滯
- 誰會遇到
- 只有特定功能, 整個伺服器
- 何時
- 人潮湧入時, 剛登入/維護剛結束
- 負責單位
- 主要負責 遊戲開發團隊(伺服器開發)
- 遊戲開發團隊要做的事
- 為慢的呼叫設逾時、依功能分開執行緒池、改為非同步。
- 圖表上
- 碰到上限後持平 · 執行緒池的執行緒數與佇列長度、請求處理時間
- 查看位置
- .NET 看 dotnet-counters monitor 的執行緒池執行緒數與佇列長度(.NET 9 以後為 dotnet.thread_pool.thread.count、dotnet.thread_pool.queue.length,8 以前為 ThreadPool Thread Count、ThreadPool Queue Length),並用 dotnet-stack 確認工作執行緒在哪裡等待。JVM 與原生伺服器則用 thread dump 確認同樣的內容
- 符合的跡象
- CPU 使用率遠低於 100%,執行緒數卻緩慢持續增加或卡在上限,佇列不斷累積,大部分 worker 都在等同一個外部呼叫(DB、HTTP)的回應
- 不符合的跡象
- 佇列是空的卻仍然很慢時,是被呼叫的對象本身慢,要查連鎖故障或依賴外部服務
- 確認方式
- 用基礎設施工具確認(不需要遊戲程式碼)
- 深入了解
- 若封包接收與遊戲邏輯共用同一個工作執行緒池,只要幾個慢的工作占滿所有 worker,整台伺服器的封包處理就會停住。
- 實際案例
- Riot Games 2021: League of Legends EUW 5 小時事故:一個附屬 DB 讓整個伺服器停擺
出處
- Debug ThreadPool Starvation Microsoft
池中沒有剩餘執行緒、新工作只能等待時,回應就會變慢,原因是占住執行緒的阻塞程式碼。在 dotnet-counters 中,CPU 遠低於 100%,dotnet.thread_pool.thread.count 卻緩慢持續增加,就是耗盡的訊號(dotnet.thread_pool.queue.length 通常也很大);用 dotnet-stack 確認執行緒等待的位置 - Avoiding insurmountable queue backlogs AWS
同時處理數 = 抵達率 × 延遲(利特爾法則,Little's law)。每秒 100 筆時,延遲從 100ms 增加到 10 秒,執行緒就從 10 個增加到 1,000 個,池因此耗盡 - Bulkhead Pattern Microsoft Azure
為每個被呼叫的對象分別建立連線池與執行緒池,一個對象故障時只會卡住它自己的池 - .NET runtime metrics .NET
dotnet.thread_pool.thread.count(執行緒池執行緒數)、dotnet.thread_pool.queue.length(等待中的工作數)自 .NET 9 起提供 - Well-known EventCounters in .NET Microsoft
.NET 8 以前的 ThreadPool Thread Count(threadpool-thread-count)、ThreadPool Queue Length(threadpool-queue-length)
相關原因
同一層:L9 伺服器遊戲程式
同一症狀(連不上/無限讀取)在其他層的原因
查看含圖解與實驗的完整版卡片