一個小小字符“0”,竟引得B站全面崩潰。
不知你是否還記得那一夜,B站“大樓停電”“服務(wù)器爆炸”“程序員刪庫跑路”的徹夜狂歡。(手動狗頭)
時隔一年,背后“真兇”現(xiàn)在終于被阿 B 披露出來 ——
沒想到吧,就是這么簡單幾行代碼,直接干趴B站兩三個小時,搞得B站程序員徹夜無眠頭發(fā)狂掉。
你可能會問,這不就是個普普通通用來求最大公約數(shù)的函數(shù)嗎,怎么就有如此大的威力?背后一樁樁一件件,歸根結(jié)底其實就一句話:0,它真的不興除啊。
具體詳情,咱們還是一起來看看“事故報告”。
字符串“0”引發(fā)的“血案”
先來說道說道引發(fā)慘案的根本原因,也就是開頭貼出的這個 gcd 函數(shù)。學(xué)過一點編程知識的小伙伴應(yīng)該都知道,這是一種用輾轉(zhuǎn)相除法來計算最大公約數(shù)的遞歸函數(shù)。
跟我們手算最大公約數(shù)的方法不同,這個算法是這樣的:
舉個簡單的例子,a=24,b=18,求 a 和 b 的最大公約數(shù);
a 除以 b,得到的余數(shù)是 6,那么就讓 a=18,b=6,然后接著往下算;
18 除以 6,這回余數(shù)是 0,那么 6 也就是 24 和 18 的最大公約數(shù)了。
也就是說,a 和 b 反復(fù)相除取余數(shù),直到 b=0,函數(shù)中:
if b==0 then return a end
這個判斷語句生效,結(jié)果就算出來了?;谶@樣的數(shù)學(xué)原理,我們再來看這段代碼,似乎沒什么問題:
但如果輸入的 b 是個字符串“0”呢?
B站的技術(shù)解析文章中提到,這段出事的代碼是用 Lua 寫的。Lua 具有這么幾個特點:
這是一種動態(tài)類型語言,常用習(xí)慣里變量不需要定義類型,直接給變量賦值就行。
Lua 在對一個數(shù)字字符串進行算術(shù)操作時,會嘗試將這個數(shù)字字符串轉(zhuǎn)成一個數(shù)字。
在 Lua 語言中,數(shù)學(xué)運算 n%0 的結(jié)果是 nan (Not A Number)。
我們來模擬一下這個過程:
1、當 b 是一個字符串“0”時,由于這個 gcd 函數(shù)沒有對其進行類型校驗,因此在碰上判定語句時,“0”不等于 0,代碼中“return _gcd (b, a% b)”觸發(fā),返回_gcd (“0”, nan)。
2、_gcd (“0”, nan) 再次被執(zhí)行,于是返回值變成了_gcd (nan, nan)。
這下就完犢子了,判定語句中 b=0 的條件永遠沒法達到,于是,死循環(huán)出現(xiàn)了。也就是說,這個程序開始瘋狂地原地轉(zhuǎn)圈,并且為了一個永遠得不到的結(jié)果,把 CPU 占了個 100%,別的用戶請求自然就處理不了了。
那么問題來了,這個“0”它到底是怎么進去的呢?
官方說法是:
在某種發(fā)布模式中,應(yīng)用的實例權(quán)重會短暫地調(diào)整為 0,此時注冊中心返回給 SLB(負載均衡)的權(quán)重是字符串類型的“0”。此發(fā)布環(huán)境只有生產(chǎn)環(huán)境會用到,同時使用的頻率極低,在 SLB 前期灰度過程中未觸發(fā)此問題。
SLB 在 balance_by_lua 階段,會將共享內(nèi)存中保存的服務(wù) IP、Port、Weight 作為參數(shù)傳給 lua-resty-balancer 模塊用于選擇 upstream server,在節(jié)點 weight=“0”時,balancer 模塊中的_gcd 函數(shù)收到的入?yún)?b 可能為“0”。
bug 是如何定位的
以“事后諸葛亮”的視角來看,這個引發(fā)B站全面崩潰的根本原因多少有點讓人直呼“就這”。但從當事程序員的視角來看,事情確實沒有辣么簡單。
當天晚上 22:52 分 —— 大部分程序員才剛下班或者還沒下班的節(jié)骨眼(doge),B站運維收到服務(wù)不可用的報警,第一時間懷疑機房、網(wǎng)絡(luò)、四層 LB、七層 SLB 等基礎(chǔ)設(shè)施出現(xiàn)問題。
然后立馬和相關(guān)技術(shù)人員拉了個緊急語音會議開始處理。5 分鐘后,運維發(fā)現(xiàn)承載全部在線業(yè)務(wù)的主機房七層 SLB 的 CPU 占用率達到了 100%,無法處理用戶請求,排除其他設(shè)施后,鎖定故障為該層。
(七層 SLB 是指基于 URL 等應(yīng)用層信息的負載均衡。負載均衡通過算法把客戶請求分配到服務(wù)器集群,從而減少服務(wù)器壓力。)
萬般緊急之時,小插曲還現(xiàn)了:遠程在家的程序員沒法進入內(nèi)網(wǎng),只好又去 call 了一遍內(nèi)網(wǎng)負責人,走了個綠色通道才全部上線(因為其中一個域名是由故障的 SLB 代理的)。
此時已經(jīng)過去了 25 分鐘,搶修正式開始。
首先,運維先熱重啟了一遍 SLB,未恢復(fù);然后嘗試拒絕用戶流量冷重啟 SLB,CPU 依然 100%,還是未恢復(fù)。
接著,運維發(fā)現(xiàn)多活機房 SLB 請求大量超時,但 CPU 未過載,正準備重啟多活機房 SLB 時,內(nèi)部群反應(yīng)主站服務(wù)已恢復(fù),視頻播放、推薦、評論、動態(tài)等功能已基本正常。
此時是 23 點 23 分,距離事故發(fā)生 31 分鐘。
值得一提的是,這些功能恢復(fù)其實是事發(fā)之時被網(wǎng)友們吐槽的“高可用容災(zāi)架構(gòu)”發(fā)揮了作用。
至于這道防線為啥一開始沒發(fā)揮作用,里頭可能還有你我一點鍋。
簡單來說,就是大家伙點不開B站就開始瘋狂刷新,CDN 流量回源重試 + 用戶重試,直接讓B站流量突增 4 倍以上,連接數(shù)突增 100 倍到千萬級別,多活 SLB 就給整過載了。
不過,并不是所有服務(wù)都搞了多活架構(gòu),至此事情并沒完全解決。接下來的半個小時里,大家做了很多操作,回滾了最近兩周左右上線的 Lua 代碼,都沒把剩余的服務(wù)恢復(fù)。
時間來到了 12 點,沒有辦法了,“先不管 bug 是怎么出來的,把服務(wù)全恢復(fù)了再說”。簡單 + 粗暴:運維直接耗時一小時重建了一組全新的 SLB 集群。
凌晨 1 點,新集群終于建好:一邊,有人負責陸續(xù)將直播、電商、漫畫、支付等核心業(yè)務(wù)流量切換到新集群,恢復(fù)全部服務(wù)(凌晨 1 點 50 分全部搞定,暫時結(jié)束了崩了逼近 3 個小時的事故);
另一邊,繼續(xù)分析 bug 原因。在他們用分析工具跑出一份詳細的火焰圖數(shù)據(jù)后,那個搞事的“0”才終于露出了一點端倪:CPU 熱點明顯集中在一個對 lua-resty-balancer 模塊的調(diào)用中。而該模塊的_gcd 函數(shù)在某次執(zhí)行后返回了一個預(yù)期外的值:NaN。
同時,他們也發(fā)現(xiàn)了觸發(fā)誘因的條件:某個容器 IP 的 weight=0。他們懷疑是該函數(shù)觸發(fā)了 jit 編譯器的某個 bug,運行出錯陷入死循環(huán)導(dǎo)致 SLB CPU 100%。于是就全局關(guān)閉了 jit 編譯,暫時規(guī)避了風險。一切都解決完后,已經(jīng)快 4 點,大家終于暫時睡了個好覺。
第二天大家也沒閑著,馬不停蹄地在線下環(huán)境復(fù)現(xiàn)了 bug 后,發(fā)現(xiàn)并不是 jit 編譯器的問題,而是服務(wù)的某種特殊發(fā)布模式會出現(xiàn)容器實例權(quán)重為 0 的情況,而這個 0 是個字符串形式。
正如前面所說,這個字符串“0”在動態(tài)語言 Lua 中的算術(shù)操作中,被轉(zhuǎn)成了數(shù)字,走到了不該走的分支,造成了死循環(huán),引發(fā)了 b 站此次前所未見的大崩潰事件。
遞歸的鍋還是弱類型語言的鍋?
不少網(wǎng)友都還對這次事故記憶猶新,有回想起自己就是以為手機不行換電腦也不行的,也有人還記得當時 5 分鐘后此事就上了熱搜。
大家都很詫異,就這么一個簡單的死循環(huán)就能造成如此大的網(wǎng)站崩服。不過,有人指出,死循環(huán)不罕見,罕見的是在 SLB 層、在分發(fā)過程出問題,它還不像在后臺出問題很快能重啟解決。
為了避免這種情況發(fā)生,有人認為要慎用遞歸,硬要用還是設(shè)置一個計數(shù)器,達到一個業(yè)務(wù)不太可能達到的值后直接 return 掉。
還有人認為這不怪遞歸,主要還是弱類型語言的鍋。以此還導(dǎo)致了“詭計多端的‘0’”這一打趣的說法。
另外,由于事故實在是耽誤了太久、太多事兒,當時B站給所有用戶補了一天大會員。
有人就在此算了一筆賬,稱就是這 7 行代碼,讓 b 站老板一下虧了大約 1,5750,0000 元。(手動狗頭)
對于這個 bug,你有什么想吐槽的?
參考鏈接:
[1]《2021.07.13 我們是這樣崩的》by 嗶哩嗶哩技術(shù)
https://mp.weixin.qq.com/s/nGtC5lBX_Iaj57HIdXq3Qg
廣告聲明:文內(nèi)含有的對外跳轉(zhuǎn)鏈接(包括不限于超鏈接、二維碼、口令等形式),用于傳遞更多信息,節(jié)省甄選時間,結(jié)果僅供參考,IT之家所有文章均包含本聲明。