病毒與故障:漫談計算機軟件的故障應對
近期肆虐的新型冠狀病毒,已然成為大眾視野的焦點。筆者,最近趁過年之際也看了一些相關新聞和書籍,其中,有一本名為卡爾·齊默《病毒星球》讓我印象深刻。當然,本文并不是談及新型冠狀病毒和《病毒星球》,而是將故障和病毒進行類比,聊一聊計算機軟件的故障應對機制,而其中關于病毒相關科普性的資料和數據來自于《病毒星球》一書。
一、故障:潛伏于計算機軟件的病毒
人鼻病毒作為普通感冒和哮喘的罪魁禍首,是人類廣泛存在的老朋友。鼻病毒巧妙地利用鼻涕來自我擴散。人擤鼻涕的時候,病毒會借機跑到手上,通過手再蹭到門把手和其他手碰過的地方。下次其他人碰到這些地方,病毒就會借機沾上他們的手,再進入他們的身體——大多數時候也是借道鼻子。鼻病毒能巧妙地讓細胞對它們打開一扇“小門”,繼而入侵位于鼻腔內部、咽喉內部或肺臟內部的細胞。在接下來的幾個小時里,鼻病毒利用宿主細胞,復制自己的遺傳物質和包裹它們的蛋白外殼。隨后這些復制產生的病毒會從宿主細胞內破壁而出。此外,我們每個人的基因組中攜帶了近 10 萬個內源性逆轉錄病毒的 DNA 片段,占到人類 DNA 總量的 8%。雖然這類病毒 DNA 中的大多數都沒用,但我們的祖先也的確“征用”了一些對我們自身有好處的病毒。如果沒有這些病毒,我們甚至沒法出生。在演化史上最近的瞬間,人類脫穎而出,病毒對我們的生存功不可沒。原本就并沒有什么“它們”和“我們”之分——生物在本質上只是一堆不斷混合、不斷閃轉騰挪的 DNA 而已。因此,鼻病毒在幾千年前就開始讓古埃及人患上感冒,內源性逆轉錄病毒早在數千萬年前就入侵了我們靈長類祖先的基因組。(摘自《病毒星球》)
故障也與之類似,它就好似生命體的 DNA 片段纏繞于計算機軟件中,無法割舍。如今軟件開發迭代頻繁,我們很難全部排除故障,只能說盡可能多地發現和解決問題,避免故障發生在生產環境導致線上問題。當我們遭到病毒感染,細胞釋放一種名為“細胞因子”的信號分子,把附近的免疫細胞都召喚過來。它們讓我們的身體產生炎性反應,等免疫系統幫我們把體內的病毒全部干掉。而在計算機軟件,我們也會有類似的場景,我們的開發人員或測試人員一旦確認是程序 BUG,就會立即記錄并周知相關人員進行處理與修復,并持續跟蹤,直至故障解決。
二、聽過很多案例,依然無法解決故障
感冒這么難治,一個原因是它存在形態多種多樣,由于其基于突變及快速復制帶來來遺傳多樣性。而面對故障,雖然它的底層導火索可能就只有哪幾種,但是由于技術的復雜性和業務的復雜性導致了計算機軟件的整體復雜性。
我們知道 NPE (NullPointerExcepion)會給我們帶來巨大災難,但是我們在實際的研發中經常遺忘或忽視。這里,由于沒有對受檢對象進行非空判斷導致 NPE 故障。
- public static void npe03(){
- Person person = null;
- System.out.println(person.blog);
- }
下面的示例將會導致 NPE,你發現了嗎?
- public static void npe01(){
- Integer x = 1;
- Integer y = 2;
- Integer z = null;
- Integer val = false ? x * y : z;
- }
而這個示例,也是非常典型的由 Java 自動裝箱和拆箱導致的 NPE 故障。
- public static void invoke(){
- Long x = null;
- npe02(x);
- }
- public static void npe02(long x){
- System.out.println(x);
- }
再聊一個有意思的故障問題。大家都知道由于死循環會導致 CPU 100%。但是,導致 CPU 100% 導因是多樣性的,筆者團隊曾經遇到一個 JDK 8 的 BUG,它是由于 ConcurrentHashMap 遞歸創建對象擴展導致死循環,文章鏈接:《ConcurrentHashMap.computeIfAbsent 死循環》。
三、故障應急機制:監控、告警、預案
通常情況下,線上故障一旦發生,其后果一般都比較嚴重。所以,我們需要盡快解決,降低其帶來的影響和資損。例如,我們團隊之前口號是:“1-5-10”,即一分鐘發現,五分鐘處理,十分鐘解決。那么,如何做到快速的發現線上故障呢?搭建成熟的監控系統就非常重要啦,例如通過 Zabbix 或 Prometheus 監控各種基礎設施(MySQL、Redis、MongoDB、ElasticSearch 等)運行情況,以及業務系統的運行情況,以及 CPU、內存、磁盤 I/O、網絡 I/O 等波動情況,還有 GC 情況、binlog 同步情況等等。那么,發現問題后,就需要通過告警系統根據業務規則進行多渠道(郵件、釘釘、電話)聯系故障處理人。要快速解決,怎么辦?首先,需要一套完備的日志排查系統(日志聚合 + 鏈路追蹤),此外,還需要對于 JVM 相關能快速 dump 堆棧信息,對于自助分析有 DevOps 平臺支撐。但是,最主要的還是需要有一套預案體系。什么是預案系統?就是針對不同的問題,有一套完整的預先方案來快速響應。例如,某個服務不可用了,筆者排查發現是由于進程假死了,那么就可以通過預案里面的執行方案執行 shell 腳本進行快速拉活。再比如,筆者通過監控告警感知到某個商家資金異常波動,那么此時通過預案里面的執行方案將其通過動態開關將其快速熔斷。
但是,如果這次線上故障沒有應急預案,又比較棘手,怎么辦?別無他法,只能因著頭皮來處理啦。注意的是,故障的評級一般根據業務的影響范圍面而定,事實上,最本質的資金損失,包括直接的資損,例如前段時間某知名電商的免額優惠劵導致非常嚴重的資損;間接的資損,例如挖斷電纜導致整個 app 無法使用,那么,轉化成正常的交易額也是大把大把的資損呀。然后呢,過了 2 個小時,故障還沒修復,可能原先定級 P2 的故障就會升級到 P1。
如果發生最差的情況,就是可能短時間內無法解決,那么就很可能需要停服維修了。例如,近期肆虐的新型冠狀病毒導致全國性的封城封路,事實上,也是因為我們沒有特效藥(計算機軟件領域的預案),也還沒有研制出新藥(計算機軟件領域的解決方案),所以只能封城封路(計算機軟件領域的停服)。
四、提早發現故障 - 故障演練
通常情況下,偶爾感冒會提供我們的免疫力。而在計算機領域,偶然采取故障演練也可以盡可能確保在線上運行的系統沒有缺陷和故障。這里,Netflix 為應對不確定性的領域帶來了一種全新的思維模式:混沌工程。事實上,混沌工程提倡我們正面接受系統一定會存在缺陷和故障,然后我們通過一系列實驗找出可能發生問題的風險點,進而不斷地加固系統。
故障演練可以模擬 CPU 滿載、殺掉指定進程、域名訪問不通、網絡延遲、網絡丟包、填充磁盤、磁盤 IO 高等場景,如下所示。
總結一下,故障就像潛伏于計算機軟件的病毒,由于技術的復雜性和業務的復雜性導致了其排查和解決的困難性,我們可以采取監控、告警、預案,以及故障演練提早發現故障并解決故障。