遊戲 Lag 白皮書 › 依症狀查找
定格:67 個原因與負責團隊
其他說法:畫面凍結、完全不動、沒有回應
在含圖解的完整版症狀辭典中開啟 →
畫面裡所有東西停住一下(0.5 秒到數秒),然後又開始動。
所有人都靜止不動。只有自己的角色靠預測稍微動一點,或是原地跑步。恢復後,積壓的動作會以快轉或瞬移的方式一次補上。
可能是整台伺服器停住(GC、死結、同步呼叫)、線路短暫中斷,或是自己的電腦停住。
造成這個症狀的原因
L1 用戶端遊戲程式
- 畫格時間尖峰: 某一個畫格的計算時間比平常多出好幾倍,畫面短暫停住。 (遊戲開發團隊(用戶端開發))
- 用戶端垃圾回收: 回收用過即丟的記憶體(垃圾)期間,整個遊戲會暫停。特徵是以規律的間隔出現卡頓。 (遊戲開發團隊(用戶端開發))
- 主執行緒同步載入、著色器編譯: 要繪製第一次出現的地圖、怪物、特效之前,得先讀取檔案並建立著色器,畫面因此停住。 (遊戲開發團隊(用戶端開發))
- 儲存裝置太慢,資源串流跟不上: 在 HDD 這類較慢的儲存裝置上,讀取開放世界貼圖與模型的速度跟不上移動,物件會晚出現,或遊戲為了等待讀取而卡頓。 (遊戲開發團隊(用戶端開發))
- 遊戲安全模組(反作弊)檢查: 為了防範外掛而與遊戲一起執行的安全模組會定期檢查。檢查太重,或與安全伺服器往來的心跳封包(定期確認連線存活的訊號)延遲時,就會卡頓或斷線。 (遊戲開發團隊(用戶端開發))
L2 用戶端 OS 與裝置
- Wi-Fi ↔ LTE/5G 切換: 走出家門時 Wi-Fi 斷掉、改用 LTE/5G,自己的 IP 位址會改變,原本的連線就此失效。 (遊戲開發團隊(伺服器開發))
- 用戶端記憶體不足、swap: 同時開著數十個瀏覽器分頁和遊戲時,OS 會把遊戲的一部分記憶體移到磁碟上。 (外部(外部))
- 顯示記憶體(VRAM)不足: 畫質選項需要的記憶體比顯示卡記憶體還大時,OS 會把貼圖移到電腦的主記憶體再搬回來,因此出現卡頓。 (遊戲開發團隊(用戶端開發))
- 網路卡省電、驅動程式問題: 有線網路卡或 Wi-Fi 晶片在封包與封包之間進入省電狀態時,要重新喚醒需要時間。 (外部(外部))
- overlay 程式干擾: 通訊軟體、啟動器、錄影、FPS 顯示程式為了在遊戲畫面上疊加自己的 UI,會介入遊戲的渲染流程(hook)。每個畫格的工作量因此增加,偶爾還會與遊戲衝突,造成頓一下或遊戲被強制關閉。 (外部(外部))
L3 家用網路
L4 網際網路線路
- BGP 路由變更與收斂: 網際網路的路由資訊改變後重新收斂的幾秒到幾十秒(偶爾幾分鐘)之間,封包會遺失。 (基礎設施團隊(網路基礎設施))
- 線路品質不良: 接頭接觸不良、老舊線路或數據機異常,會造成持續的封包遺失與週期性的線路中斷。 (外部(外部))
L5 資料中心網路設備
L6 伺服器網路卡
- 雲端主機維護與即時遷移: 雲端供應商維護實體伺服器(主機)時,會把虛擬機器搬到其他主機(即時遷移),或暫時停住。這段期間整台伺服器都會停住,停住的時間一長,連線就會中斷。 (基礎設施團隊(伺服器基礎設施))
- NIC 驅動程式/韌體問題: 驅動程式 bug 或功能異常讓網路卡停住並重新啟動,這段期間所有收發都會中斷。 (基礎設施團隊(伺服器基礎設施))
L7 伺服器 OS(kernel)
L8 Socket 與協定
- TCP HOL 阻塞: TCP 為了維持順序,在重新收到遺失的那一個封包之前,不會把之後到達的封包交給遊戲。 (遊戲開發團隊(伺服器開發))
- TCP RTO 與指數退避: 每次重傳又失敗,等待時間就加倍,於是線路短暫中斷會變成長時間停住。 (遊戲開發團隊(伺服器開發))
- 慢速用戶端造成的阻塞式傳送: 線路很慢的某位玩家傳送緩衝區已滿時,若用阻塞方式(緩衝區有空間之前呼叫不會返回的傳送)送出,伺服器執行緒就會一直等那一個人。 (遊戲開發團隊(伺服器開發))
- SO_REUSEPORT 分配不均: 多個處理程序共用同一個 port 接收時,kernel 會依位址雜湊為每個連線指定負責的處理程序,之後不再更換。其中一個處理程序停住時,只有分配給它的人要等待。 (遊戲開發團隊(伺服器開發))
- Windows UDP socket 的 WSAECONNRESET 錯誤: Windows 伺服器向已經離開的用戶端送出 UDP 時,會收到「port 無法到達」(ICMP)的通知。這個通知會讓下一次接收呼叫以錯誤結束;如果伺服器程式碼把這個錯誤當成 socket 本身故障來處理,所有使用該 socket 的人都會受到影響。 (遊戲開發團隊(伺服器開發))
L9 伺服器遊戲程式
- 鎖競爭: 多個執行緒為了寫入同一份資料而等待同一個鎖時,執行緒再多也只能一次執行一個。 (遊戲開發團隊(伺服器開發))
- 死結: 兩個執行緒各自等待對方持有的鎖時,就會永遠停住。 (遊戲開發團隊(伺服器開發))
- 遊戲執行緒上的同步呼叫: 在 tick 進行中等待 DB 回應或檔案寫入時,伺服器上整個遊戲進行就會停住同樣長的時間。 (遊戲開發團隊(伺服器開發))
- 計時器集中同時觸發: 所有怪物重生、所有 buff 到期、整點獎勵都擠在同一個 tick 時,那一個 tick 的負擔會變成平常的數十倍。 (遊戲開發團隊(伺服器開發))
- 執行緒池耗盡: 負責處理工作的工作執行緒全被慢的工作綁住時,新請求只能無止盡地等待。 (遊戲開發團隊(伺服器開發))
- 無窮迴圈、邏輯失控: 因 bug 導致一個 tick 結束不了時,伺服器就會停住,看門狗會強制重新啟動。 (遊戲開發團隊(伺服器開發))
- 進入密集區域時的生成暴增: 用傳送移動到擠滿人的城鎮時,伺服器必須一次送出數百位新進入視野者的外觀、裝備與狀態。 (遊戲開發團隊(伺服器開發))
L10 記憶體
- 伺服器 GC 全面暫停: Java、C# 伺服器為了回收垃圾而暫停所有執行緒(stop-the-world)的期間,整個伺服器都會停住。 (遊戲開發團隊(伺服器開發))
- 腳本引擎的 GC 暫停: 即使是 C++ 伺服器,只要任務、AI、技能是用 Lua 等腳本執行,腳本引擎進行 GC 的期間,該 zone 就會停住。 (遊戲開發團隊(伺服器開發))
- 記憶體配置暴增: 活動期間大量產生暫時物件時,GC 執行的頻率會比平常高出許多。 (遊戲開發團隊(伺服器開發))
- 記憶體洩漏: 沒有釋放的記憶體一點一點累積,幾天後演變成 GC 暴增、swap 或處理程序被強制終止。 (遊戲開發團隊(伺服器開發))
- GC thrashing(heap 可用空間不足): 存活資料逼近 heap 上限時,每次 GC 幾乎回收不到東西,GC 就會不停反覆執行。 (遊戲開發團隊(伺服器開發))
- Swap: 記憶體不足、OS 把部分內容移到磁碟後,每次用到那塊記憶體,都得等待慢上 1,000 倍以上的磁碟。 (基礎設施團隊(伺服器基礎設施))
L11 磁碟
- 同步寫入 log: 遊戲執行緒每寫一行 log 都要等磁碟寫完的話,磁碟一忙,遊戲進行也會跟著停住。 (遊戲開發團隊(伺服器開發))
- IOPS 上限與佇列飽和: 請求數超過磁碟每秒能處理的數量時,佇列會變長,延遲急遽增加。 (基礎設施團隊(伺服器基礎設施))
- 伺服器端的延遲載入: 伺服器在副本、地圖資料第一次被請求時才從磁碟讀取的話,那個 tick 期間所有人都會停住。 (遊戲開發團隊(伺服器開發))
L12 資料庫
- DB 容錯移轉: 主 DB 故障、切換到備援 DB 的期間無法寫入,最後一批還沒複寫過去的資料可能會遺失。 (基礎設施團隊(DB 基礎設施))
- Cache stampede: 熱門資料的快取同時過期時,數千個請求會一口氣湧向 DB。 (遊戲開發團隊(伺服器開發))
- Redis 慢指令: Redis 一次只處理一個指令,因此一個慢指令就會擋住後面所有的請求。 (遊戲開發團隊(伺服器開發))
L13 伺服器架構與維運
- 切換 zone(跨伺服器轉移): 進入其他地圖或副本時,把角色資料交給另一台伺服器的過程中會產生延遲與失敗。 (遊戲開發團隊(伺服器開發))
- 連鎖故障: 一個服務變慢時,呼叫它的伺服器會卡在等待回應,連不相關的功能也跟著停擺。 (遊戲開發團隊(伺服器開發))
- log 與監控過載: 發生故障時 log 會暴增,以同步方式送出 log 的伺服器會因為 log 變得更慢。 (遊戲開發團隊(伺服器開發))
同步設計
- Lockstep 中等待最慢的玩家: 在所有人一起計算同一回合的架構中,只要一個人的輸入晚到,所有人都要等。 (遊戲開發團隊(伺服器開發))
- 主機(房主)架構: 由某個玩家的電腦擔任伺服器時,那個人的線路與電腦效能決定了所有人的體感。 (遊戲開發團隊(伺服器開發))
只有部分人遇到的問題
- 特定角色的資料過於龐大: 累積了數千個道具或信件,或好友、封鎖名單、buff 特別多的角色,在登入、儲存與通知周圍玩家時要處理的量是別人的好幾倍。與線路無關,只有用那個角色時才慢。 (遊戲開發團隊(伺服器開發))
TCP 重傳的根本原因
- 無線區段的封包遺失: Wi-Fi 與行動網路在無線區段會先重傳幾次,仍然失敗就丟棄封包。被丟棄的封包,TCP 要過好一段時間才會重送。 (外部(外部))
- 瓶頸佇列溢位(壅塞遺失): 分享器、電信業者之間的互連區段、資料中心線路這類最窄的地方,佇列一滿就會丟棄新進來的封包。 (基礎設施團隊(網路基礎設施))
- 突發傳送造成淺緩衝區溢位: 伺服器每個 tick 把數千人份的更新在一瞬間集中送出時,交換器的小緩衝區或雲端的瞬間上限不到 1ms 就會溢位,部分封包因此被丟棄。 (遊戲開發團隊(伺服器開發))
- Policer 丟棄超額流量: 電信業者的資費方案、雲端執行個體的上限、DDoS 防護設備,有時會把超過規定速度的封包直接丟棄,不放進佇列。 (基礎設施團隊(網路基礎設施))
- 實體層錯誤(線材、光模組、接頭不良): 纜線損壞、沾了灰塵的光纖接頭、壽命將盡的光模組會造成位元錯誤,損壞的封包會被設備默默丟棄。 (基礎設施團隊(網路基礎設施))
- 雙工模式不一致: 一端設為自動協商、另一端把速度與雙工固定時,其中一端會以半雙工運作,每當負載升高就因碰撞而遺失封包。 (基礎設施團隊(網路基礎設施))
- 接收端伺服器主機丟棄封包: 封包已經到了伺服器,卻因 NIC 的 ring buffer(暫存剛抵達封包的緩衝區)溢位,或 kernel 負責接收處理的 CPU 核心滿載而被丟棄。 (基礎設施團隊(伺服器基礎設施))
- 防火牆與連線追蹤丟棄封包: 防火牆或 Linux 連線追蹤(conntrack,把經過的連線記錄在表中的功能),在表已滿或判斷連線狀態不符時,會丟棄封包。 (基礎設施團隊(網路基礎設施))
- 中間設備超過處理上限(防火牆、IPS、DDoS 防護): 防火牆、入侵防禦設備(IPS)、DDoS 防護設備會逐一檢查經過的封包。從超過檢查能力的那一刻起,處理不了的封包就會被丟棄。 (基礎設施團隊(網路基礎設施))
- MTU 黑洞(只有大封包反覆遺失): 中途區段能接收的大小變小,而「封包太大」的通知(ICMP)又被擋掉時,大封包不管重送幾次都會一直消失。 (基礎設施團隊(網路基礎設施))
- 連線途中 NAT 或負載平衡器的 mapping 過期: 中途設備刪除閒置連線的 mapping(記錄這條連線要轉送到哪裡的項目)後,接下來送出的封包就無法送達。連線會不斷重傳直到斷線,或是設備回傳拒絕連線(RST)而立刻斷線。 (遊戲開發團隊(用戶端開發))
- 路由變更、ECMP 不良路徑: 網際網路路由變更的那幾秒內,或是多條 ECMP 路徑中被分配到不良路徑的連線,會有封包消失。 (基礎設施團隊(網路基礎設施))
- 延遲飆升造成的不必要重傳: 封包沒有消失,只是短暫地很晚才到;但這段延遲比 RTO 長時,傳送端就會判斷為遺失而重傳。 (外部(外部))
- RTO 設定不符合環境: RTO 最小值調得太低時,只要稍微延遲就會產生不必要的重傳;預設值(200ms)對遊戲來說又太長,每遺失一次就要停住很久。 (基礎設施團隊(伺服器基礎設施))
- thin stream 復原緩慢: 像遊戲這樣零星送出小封包時,在湊齊「後續 3 個封包」之前 RTO 就先到了。同樣的遺失,停住的時間比大量傳輸長得多。 (遊戲開發團隊(伺服器開發))
- 中間設備移除 TCP 選項: 部分防火牆或加速設備刪除或改寫 TCP 選項時,遺失多個封包後一個往返只能復原一個,或是視窗(一次可送出的量)變小,傳輸因此變慢。 (基礎設施團隊(網路基礎設施))
- Zero window(看起來像重傳的停頓): 接收端程式沒有及時讀取 socket、緩衝區塞滿時,傳送端會停止傳送,只送出 zero window probe。線路本身沒有問題。 (遊戲開發團隊(用戶端開發))
查看含圖解的完整版症狀辭典