NO IMAGE

GitChat 作者:楊彪
原文:一次線上遊戲卡死的解決歷程
關注微信公眾號:GitChat 技術雜談 ,一本正經的講技術

【不要錯過文末活動】

事故的發生詳細過程

故事是發生在幾個月前的線上真實案例,我將在本文中以故事形式為大家還原這次解決遊戲卡死的經歷過程,其中有很多線上實戰經驗和技巧都值得分享借鑑的,也有作者自創的處理線上問題“四部曲”–望問聞切,還有最經典的“甩鍋”祕訣。不管白貓黑貓,能立馬解決線上問題的就是好貓,線上問題實戰經驗最重要。下來就讓我先來回顧下這次事故發生的背景吧。

公司的遊戲獲得了Google Play的最佳新遊推薦位展示,這代表著公司遊戲可以在Google Play首頁持續一週的全球推薦。如果對Google Play還不瞭解的小夥伴們可以看看下圖,展示了Google Play推薦位的效果:

Google Play 推薦位

就是在這樣一個重大利好訊息推動下,專案研發組緊急加班加點地趕製進度和進行遊戲壓力測試(以後有機會詳細寫篇遊戲壓力測試機器人實現方案),最後內網測試環境(Testing Environment)和預生產環境(Staging Environment)一切都測試正常,隨時等待更新線上正式壞境了。

像以往一樣,遊戲釋出了停服更新公告(由於新增加了聯盟戰和幾個大的活動,擔心不停服更新有問題),執行完停服、備份、更新等一系列自動化流程後,伺服器狀態變為“更新完畢,白名單可進入”狀態,然後通知QA進行上線前的最後一次生產環境(Production Environment)測試。整個專案的同學和QA同學以白名單身份線上上生產環境測試了近半個小時,沒有任何bug和異常,我們就信心滿滿的準備開服了。

遊戲對外開放後,我們像往常一樣邊觀察邊繼續做著新的工作,突然公司運營同學過來說遊戲怎麼感覺好卡,我們的第一反應是你網絡卡了吧(因為遊戲伺服器在國外,中間有一道不可逾越的qiang),我也沒太在意還是繼續做著別的事情,後來QA同學也說遊戲好卡啊,我自己也登陸游戲試了下,確實挺卡,每一次操作都要等待好久,不過到現在我還是沒有意識到伺服器卡了,只是讓運維同學檢視遊戲服的log有沒有報錯,而日誌顯示似乎一切都正常(後面會解釋為什麼日誌還正常地輸出)。

慢慢地遊戲內的聊天中開始有玩家反饋這次更新後遊戲太卡,而且反饋的使用者越來越多,我這才意識到問題的嚴重性了,遊戲服肯定哪裡操作太慢反應遲鈍了(之前可能因為遊戲公測到現在大半年還沒出現過事故,所以有點掉以輕心了)。

公司BOSS也過來問怎麼回事,正值Google Play推薦導量的時期,公司上下非常重視。當然我知道越是面對大問題,有經驗的人就越要冷靜,我直覺得給BOSS說:“伺服器有點卡了,小問題,馬上就能弄好的,彆著急”。其實當時我心裡也沒底,不知道問題在哪,不過根據自己以往經驗和實踐操作,只要按照“四步曲”流程化的執行一遍,肯定能找到點線索和眉目的。

更多的線上應急和技術攻關可以參照我和朋友合著的《分散式服務架構:原理、設計與實戰》一書中的第六章“Java服務的線上應急和技術攻關”,該章中介紹了海恩法則和墨菲定律,以及線上應急目標、原則和方法,同時提供了大量的Linux和JVM命令,也有很多平時工作中常用的自定義腳步檔案和實際案例。

接下來我們一起,一步步地解決遊戲卡死的問題,文章中很多截圖只是本次文章演示說明,不是當時的現場截圖,不過我會說明清楚儘量還原線上真實過程。

事故的處理過程還原

解決線上問題的“四部曲”

  • :就是觀察的意思,出了問題最重要一點就是觀察線上問題發生的規律,切忌有病亂投醫,一上來就先各種償試各種改的。除了觀察現象外,我們還要觀察各種日誌、監控和報警系統,具體如何搭建“大資料日誌監控系統”請參照作者書的第四章。

  • :就是問清楚現在問題發生的情況,這個很重要,後面會重點介紹具體需要問清楚的哪些問題。

  • :就是認真聽取別人的意見,有時線上出了問題,我們大多數心裡還是比較抵觸別人說這說那的,不過在這種情況下,我們更應該多聽,找到可能引起問題的情況或有關的事情,同時也為後面的“甩鍋”技巧開啟思路。

  • :就是動手實踐驗證了,通過前面的觀察、詢問問題,我們心中應該能有些假設和猜測的線索了,這時候就需要動手在測試環境或預生產環境上進一步驗證我們的假設是否成立了。

下面這張圖概括的介紹了“望問聞切”各階段需要關心和注重的事情:

四部曲

前面通過對“四部曲”的介紹,大家可能會覺得很抽象,不過它是我們解決線上問題的指導方針、核心思想,那我們在實際專案中又是如何“望問聞切”的呢?

首先是如何發現問題

發現問題通常通過自動化的監控和報警系統來實現,線上遊戲服搭建了一個完善、有效的日誌中心、監控和報警系統,通常我們會對系統層面、應用層面和資料庫層面進行監控。

對系統層面的監控包括對系統的CPU利用率、系統負載、記憶體使用情況、網路I/O負載、磁碟負載、I/O 等待、交換區的使用、執行緒數及開啟的檔案控制代碼數等進行監控,一旦超出閾值, 就需要報警。對應用層面的監控包括對服務介面的響應時間、吞吐量、呼叫頻次、介面成功率及介面的波動率等進行監控。

對資源層的監控包括對資料庫、快取和訊息佇列的監控。我們通常會對資料庫的負載、慢 SQL、連線數等進行監控;對快取的連線數、佔用記憶體、吞吐量、響應時間等進行監控;以及對訊息佇列的響應時間、吞吐量、負載、積壓情況等進行監控。

其次是如何定位問題

定位問題,首先要根據經驗來分析,如果應急團隊中有人對相應的問題有經驗,並確定能夠通過某種手段進行恢復,則應該第一時間恢復,同時保留現場,然後定位問題。

在應急人員定位過程中需要與業務負責人、技術負責人、核心技術開發人員、技術專家、
架構師、運營和運維人員一起,對產生問題的原因進行快速分析。在分析過程中要先考慮系統最近發生的變化,需要考慮如下問題。

  • 問題系統最近是否進行了上線?

  • 依賴的基礎平臺和資源是否進行了上線或者升級?

  • 依賴的系統最近是否進行了上線?

  • 運營是否在系統裡面做過運營變更?

  • 網路是否有波動?

  • 最近的業務是否上量?

  • 服務的使用方是否有促銷活動?

然後解決問題

解決問題的階段有時在應急處理中,有時在應急處理後。在理想情況下,每個系統會對各種嚴重情況設計止損和降級開關,因此,在發生嚴重問題時先使用止損策略,在恢復問題後再定位和解決問題。解決問題要以定位問題為基礎,必須清晰地定位問題產生的根本原因,再提出解決問題的有效方案,切記在沒有明確原因之前,不要使用各種可能的方法來嘗試修復問題,這樣可能還沒有解決這個問題又引出另一個問題。

最後消除造成的影響

在解決問題時,某個問題可能還沒被解決就已恢復,無論在哪種情況下都需要消除問題產生的影響。

  • 技術人員在應急過程中對系統做的臨時性改變,後證明是無效的,則要嘗試恢復到原來的狀態。

  • 技術人員在應急過程中對系統進行的降級開關的操作,在事後需要恢復。

  • 運營人員在應急過程中對系統做的特殊設定如某些流量路由的開關,需要恢復。

  • 對使用方或者使用者造成的問題,儘量採取補償的策略進行修復,在極端情況下需要一一核實。

  • 對外由專門的客服團隊整理話術統一對外宣佈發生故障的原因並安撫使用者,話術儘量貼近客觀事實,並從使用者的角度出發。

當我們詳細地瞭解瞭如何發現問題、定位問題、解決問題和消除造成的影響後,接下來讓我們看下本次解決線上遊戲卡死過程中是如何具體的應用的。

排查遊戲卡死的過程

第一步,找運維看日誌

如果日誌監控系統中有報錯,謝天謝地,很好定位問題,我們只需要根據日誌報錯的堆疊資訊來解決。如果日誌監控系統中沒有任何異常資訊,那麼接下來就得開始最重要的儲存現場了。

第二步,儲存現場並恢復服務

日誌系統中找不到任何線索的情況下,我們需要趕緊儲存現場快照,並儘快恢復遊戲服務,以達到最大程度止損的目的。

通常JVM中儲存現場快照分為兩種:

  • 儲存當前執行執行緒快照。

  • 儲存JVM記憶體堆疊快照。其方法如下:

  1. 儲存當前執行執行緒快照,可以使用jstack [pid]命令實現,通常情況下需要儲存三份不同時刻的執行緒快照,時間間隔在1-2分鐘。

  2. 儲存JVM記憶體堆疊快照,可以使用jmap –heapjmap –histojmap -dump:format=b,file=xxx.hprof等命令實現。

快速恢復服務的常用方法:

  1. 隔離出現問題的服務,使其退出線上服務,便於後續的分析處理。

  2. 償試快速重啟服務,第一時間恢復系統,而不是徹底解決問題。

  3. 對服務降級處理,只使用少量的請求來重現問題,以便我們可以全程跟蹤觀察,因為之前可能沒太注意這個問題是如何發生的。

通過上面一系列的操作後,儲存好現場環境、快照和日誌後,我們就需要通過接下來的具體分析來定位問題了。

第三步,分析日誌定位問題

這一步是最關鍵的,也是需要有很多實戰經驗的,接下來我將一步步還原當時解決問題的具體操作步聚。

診斷服務問題,就像比醫生給病人看病一樣,需要先檢視一下病人的臉色如何、摸一摸有沒有發燒、或再聽聽心臟的跳動情況等等。同樣的道理,我們需要先檢視伺服器的“當前症狀”,才能進一步對症下藥。

  • 首先使用top命令檢視伺服器負載狀況

    top命令

load average一共有三個平均值:1分鐘系統負荷、5分鐘系統負荷,15分鐘系統負荷。哪我們應該參考哪個值?

如果只有1分鐘的系統負荷大於1.0,其他兩個時間段都小於1.0,這表明只是暫時現象,問題不大。
如果15分鐘內,平均系統負荷大於1.0,表明問題持續存在,不是暫時現象。所以,你應該主要觀察”15分鐘系統負荷”,將它作為伺服器正常執行的指標。

說明:我們當時伺服器負載顯示並不高,所以當時第一反應就排除了承載壓力的問題。

  • 接下來再使用top命令 1檢視CPU的使用情況

    top命令

我們主要關注紅框中指標,它表示當前cpu空閒情況,而其它各指標具體含義如下:

0.7%us:使用者態程序佔用CPU時間百分比,不包含renice值為負的任務佔用的CPU的時間。

0.0%sy:核心佔用CPU時間百分比。

0.0%ni:改變過優先順序的程序佔用CPU的百分比。

99.3%id:空閒CPU時間百分比。

0.0%wa:等待I/O的CPU時間百分比。

0.0%hi:CPU硬中斷時間百分比。

0.0%si:CPU軟中斷時間百分比。

說明:我們線上伺服器為8核16G的配置,當時只有一個cpu顯示繁忙,id(空閒時間百分比)為50%左右,其餘顯示90%多。從這裡看似乎沒有什麼太大的問題。

既然cpu負載和使用都沒太大問題,那是什麼卡住了服務呢?直覺告訴我,可能是執行緒死鎖或等待了什麼耗時的操作,我們接下來就來檢視執行緒的使用情況。不過在檢視執行緒使用情況之前,我們首先看看JVM有沒有出現記憶體洩漏(即OOM問題,我的書中有介紹一個實際OOM的案例),因為如果JVM大量的出現FGC也會造成使用者執行緒卡住服務變慢的情況。

  • 使用jstat –gcutil pid檢視堆中各個記憶體區域的變化以及GC的工作狀態

    jstat命令

S0:倖存1區當前使用比例

S1:倖存2區當前使用比例

E:伊甸園區使用比例

O:老年代使用比例

M:後設資料區使用比例

CCS:壓縮使用比例

YGC:年輕代垃圾回收次數

FGC:老年代垃圾回收次數

FGCT:老年代垃圾回收消耗時間

GCT:垃圾回收消耗總時間

說明:當時服務也沒有出現大量的FGC情況,所以排除了有OOM導致的使用者執行緒卡死。

  • 接下來使用top命令 H檢視執行緒的使用情況

top命令

PID:程序的ID

USER:程序所有者

PR:程序的優先順序別,越小越優先被執行

NInice:值

VIRT:程序佔用的虛擬記憶體

RES:程序佔用的實體記憶體

SHR:程序使用的共享記憶體

S:程序的狀態。S表示休眠,R表示正在執行,Z表示僵死狀態,N表示該程序優先值為負數

%CPU:程序佔用CPU的使用率

%MEM:程序使用的實體記憶體和總記憶體的百分比

TIME :該程序啟動後佔用的總的CPU時間,即佔用CPU使用時間的累加值。

COMMAND:程序啟動命令名稱

說明:通過檢視執行緒%CPU指標,明顯能看到某個java執行緒執行非常佔用CPU,因此斷定該執行緒當時出現了問題。那麼我們接下來如何找到這個執行緒當時在幹嘛呢?請看以下三步聚。(圖片只是示意圖,不是當時線上截圖)

  • 使用jstack pid列印程序中執行緒堆疊資訊,我們可以使用如下三步找出最繁忙的執行緒資訊。

檢視程序中各執行緒佔用cpu狀態, 選出最繁忙的執行緒id,使用命令top -Hp pid

jstack命令

把執行緒id轉成16進位制,使用命令printf “%x\n”{執行緒id}

jstack命令

列印當前執行緒執行的堆疊資訊,查詢執行緒id為0x766B的執行緒堆疊資訊

jstack命令

說明:線上通過列印繁忙執行緒,檢視執行緒的執行堆疊,並沒有找到被卡住的業務程式碼,每次都是執行成功的。當時就非常納悶,為什麼一直只是這一個執行緒在不停地消耗著CPU,突然一個程式設計的小技巧幫我找到了問題的罪魁禍首——執行緒中任務分配不均導致的服務響應變慢。

小技巧:為不同的業務執行緒自定義名稱,比如列印日誌的執行緒為log_xxx,接收訊息請求的執行緒為msg_xxx,遊戲業務執行緒為game_xxx等。java中具體如何為執行緒命令如下圖所示:

threadname

  • 罪魁禍首——分散式唯一ID生成器

wenti

通過上面紅框中的程式碼我們可以看到,執行緒任務的分配規則是通過使用者的uuid模上執行緒池的長度,這樣實現的目的是想讓同一個使用者的所有請求操作都分配到同一個執行緒中去完成(執行緒親和性),這樣的實現是為了從使用者角度保證執行緒的安全性,不會出現多執行緒下資料的不一致性。

而問題就出現在這個uuid取模上了,我們使用的是Twitter的分散式自增ID演算法snowflake,而它生成的所有id剛好與我設定的執行緒池大小64取模後為0(具體原因不明),導致所有使用者的所有請求全部分配到了一個執行緒中排隊執行了。這也是為什麼在檢視執行緒堆疊資訊時感覺都在正常執行,而列印的所有執行緒中只看到編號為0的執行緒在執行,其它都空閒等待。

說明:此功能實現是在上線前兩天,運營同學告訴說,有玩家反饋前一刻領取到的鑽石在下一刻莫名消失了,我的第一反應肯定是多執行緒造成的,所以就臨時採取了這種執行緒親和方式統一解決了執行緒安全的問題。現在找到了問題產生的原因,接下來看是如何解決的。

  • 使用MurmurHash雜湊下解決ID生成不均勻的問題

jiejue

第四步,Hotfix後繼續觀察情況

在測試環境或預生產環境修改測試後,如果問題不能再復現了,可以根據公司的Hotfix流程進行線上bug更新,並繼續觀察。如果一切都正常後,需要消除之前可能造成的影響。

一鍵檢視最繁忙執行緒堆疊指令碼

此命令通過結合Linux作業系統的ps命令和JVM自帶的jstack命令,來查詢Java程序內CPU利用率最高的執行緒,一般適用於伺服器負載較高的場景,並需要快速定位負載高的成因。

此指令碼最初來自於網際網路,後來為了讓其在不同的類UNIX環境下執行,所以我做了一些修改,該命令在我每一次定位負載問題時都起到了重要作用。

命令格式:

./show-busiest-java-threads -p 程序號 -c 顯示條數
./show-busiest-java-threads -h

使用示例:

./show-busiest-java-threads -p 30054 -c 3

示例輸出:

findtop

指令碼原始碼見《分散式服務架構:原理、設計與實戰》書中241頁,更多服務化治理指令碼請參照書中的第六章“Java服務的線上應急和技術攻關”。

對本次線上事故的總結

在技術方面,線上問題大致分為以下三類。

CPU繁忙型

  • 執行緒中出現死迴圈、執行緒阻塞,JVM中頻繁的垃圾回收,或者執行緒上下文切換導致。

  • 常用命令topjstack [pid]Btrace等工具排查解決。

記憶體溢位型

  • 堆外記憶體: JNI的呼叫或NIO中的DirectByteBuffer等使用不當造成的。

  • 堆內記憶體:程式中建立的大物件、全域性集合、快取、 ClassLoader載入的類或大量的執行緒消耗等容易引起。

  • 常用的命令有jmap –heapjmap –histojmap -dump:format=b,file=xxx.hprof等檢視JVM記憶體情況的。

IO讀寫型

  • 檔案IO:可以使用命令vmstatlsof –c -p pid等。

  • 網路IO: 可以使用命令netstat –anptcpdump -i eth0 ‘dst host 239.33.24.212’ -w raw.pcap和wireshark工具等。

以上只是簡單的介紹了下相關問題分類和常用命令工具,由於篇幅有限更多內容請參照《分散式服務架構:原理、設計與實戰》書中“線上應急和技術攻關”一章,詳細介紹了各種情況下技術命令的使用。

在制度方面的應急處理和響應保障

制定事故的種類和級別

  • S級事故,核心業務重要功能不可用且大面積影響使用者,響應時間:立即。

  • A級事故,核心業務重要功能不可用,但影響使用者有限;周邊業務功能不可用且大面積影響使用者體驗,響應時間:小於15分鐘。

  • B級事故,周邊業務功能不可用,輕微影響使用者體驗,響應時間:小於4小時。

說明:每個公司定義的事故種類和級別都不一樣,具體情況具體分析,只要公司有了統一化的標準,當我們遇到線上問題時才不會顯的雜亂無章,知道事情的輕重緩急,以及如何處理和什麼時候處理。

對待事故的態度

  • 儲存現場並減少損失,第一時間恢復服務,減少線上損失,儲存好現在所有資訊用於問題分析定位。

  • 積極主動的解決問題,線上問題第一時間解決,也是展現個人能力的最佳時機。

  • 主動承擔部分責任,承擔自己能承擔的責任,畢竟事故涉及KPI考核等問題,有時也需要混淆問題原因,拒絕老實人背鍋。

  • 不要輕信經驗,線上無小事,而大多引起線上事故的問題一般都是小問題,所以一定不要輕信經驗,每一項改動都必須經過測試。

說明:當線上出現問題後,大多數人第一反應可能是“這不關我事,我寫的東西沒問題”,面對線上問題不要怕承擔責任,反而正是我們表現個人能力的最好時機。平時大家可能做了非常多的工作,勤勤懇懇的努力奉獻著,最後BOSS連你的名字可能都沒記住,尷尬!!但是一旦線上遇到問題,可能直接就造成很大的經濟損失,全專案組甚至全公司都在關注的時候,你勇於站出來完美的解決了該問題,收穫的成就會是相當大的。當然在這個過程中,我們也要學會“合理地甩鍋”,具體的“甩鍋”請聽我直播。


實錄:《楊彪:線上遊戲卡死問題實戰解析》


【GitChat達人課】

  1. 前端惡棍 · 大漠窮秋 :《Angular 初學者快速上手教程
  2. Python 中文社群聯合創始人 · Zoom.Quiet :《GitQ: GitHub 入味兒
  3. 前端顏值擔當 · 餘博倫:《如何從零學習 React 技術棧
  4. GA 最早期使用者 · GordonChoi:《GA 電商資料分析實踐課
  5. 技術總監及合夥人 · 楊彪:《Gradle 從入門到實戰
  6. 混元霹靂手 · 江湖前端:《Vue 元件通訊全揭祕
  7. 知名網際網路公司安卓工程師 · 張拭心:《安卓工程師跳槽面試全指南

這裡寫圖片描述