作者丨Rajiv Prabhakar
譯者丨蓋磊
策劃丨田曉旭、Tina
系統出故障了。當年負責寫這個程序的開發者早在十五年前就去世了,現在已經沒有人能讀得懂他的代碼了......
現在一些關鍵系統的運行仍依賴于過時的軟件,但編寫他們的人要么離職要么已經去世。中間也缺少維護或更新,導致現在幾乎沒人能理解它們,而且一旦出現 Bug 就會給企業造成不可挽回的損失。
而現實中的這種例子,遠比你想象中的要多。
一個令人深思的故事
我的一位客戶負責數項世界排名前一百的養老基金,該公司在前幾個月成功的將程序搬到了云端。作為項目的主任架構師,前兩天我很意外地直接收到了 CIO 的短信:“抱歉打擾,我們出 S1X 級的大問題了。你能下午飛過來嗎?”。
“S1X”是他們對“比最嚴重級別還要糟,級聯影響到業務其它非直接相關部分”問題的定義。
事情看起來十萬火急,當天晚上我就飛到現場進行了診斷,發現是該客戶的系統中一個批處理任務發生了崩潰。
該任務每天晚上執行一次,通過寫一個 CSV 文件為某些養老金計算繳費率,再將計算的結果輸出到另一個收益(benefit)分配程序。原先收益分配程序設定為在繳費(contribution)低于預測(projection)時會向客戶發出報警。由于上一個處理任務已發生崩潰,不再產生輸出,因此程序認為“所有繳費為零”。
所以系統立即向該養老基金的經理們發出大量警報郵件,基金經理們被嚇得趕緊從該項目里撤離了。
起初沒有人找得出問題發生在哪里,在大家的記憶和工作記錄中,這個批處理任務也從未崩潰過。編寫該批處理程序的人已經在 15 年前離世了,并且數十年前就不再是該企業的員工了。盡管該批處理的程序代碼規模不大,但非常難讀。因為在編程時主要考慮的是提高計算效率,并未考慮如何適合他人閱讀。當然,程序也沒有留下任何測試。
事故發生的前一天,腳本在運行環境中的編排發生了一次更改。這被認為是導致事故的罪魁禍首。幸運的是,這個更改做了版本 push,工程部門據此回溯到先前的版本。但不幸的是,這只使事情變得更糟。
最后我們通過提供熱修補腳本的方式解決了這個問題。但實質上這次崩潰已經給該基金造成了 170 萬美元(約 1203 萬人民幣)的直接損失。
“數據潰爛”(Bit Rot)問題
事實上類似的故事并非孤例。我在 2012 年離開英特爾加入 Sun 公司,切身體會到他們的 SPARC 產品線做得是多么的糟糕。Sun 在互聯網泡沫時代的殺雞取卵行為,導致此后 SPARC 遠遠落后于英特爾的至強產品線。
我的經理都和我說,要在英特爾至強服務器上運行模擬程序,因為 SPARC 服務器“非常慢”。更嚴重的問題在于,英特爾不僅 CPU 性能更好,而且具有制造優勢,這意味著 CPU 的制造成本也大大降低。
隨之而來的問題很明顯:既然遠遠落后于競爭對手,那么為什么客戶還是會購買我們的 SPARC 芯片?一位高級架構師向我給出了令人震驚的答案。那是因為我們的客戶的軟件系統過于僵化,只能在 SPARC/Solaris 系統上運行。而遷移到 x86/Linux 對客戶而言是一項艱巨的任務。許多客戶甚至丟失了源代碼,無法重新編譯應用。
他們能做的最好選擇,就是升級到最新一代的 SPARC 處理器,無論這樣的處理器性能多慢,價格多么昂貴。
這就是問題所在。我們整個部門的業務模式,完全圍繞著這個國家中那些潰爛的軟件系統。
維持運轉的代價
我加入 Amazon 后,發現自己面對正是這樣一個為遺留系統而構建適用的原型。該系統是另一個團隊基于大量技術債務開發的,并且團隊早已解散。之后該項目的所有權就移交給我們的團隊……事實操明,這并非攬下一件好事。所以開發人員陸陸續續跳槽到其他的團隊。我加入時的十多名團隊成員中,一年后一位都沒有留下。
該系統從表面上看并非一無是處。它使用了現代編程語言和技術棧(Java 8)編寫,由拿著六位數收入的開發人員所組成的團隊進行著日常維護,并不斷更新以修復錯誤和添加新功能。盡管如此,測試修改(turnover)依然是拖累整個系統的顯著負擔。所有權變更和團隊更替導致整體代碼設計、端到端功能、最佳實踐和調試技術等大量實操知識的丟失。盡管我們一直努力保持項目的推進,但感覺就像進入了一片沼澤地,四處亡羊補牢,深陷戰爭迷霧(Fog of War)中。
設想一下,一個運行在第一版 Java 上的項目,開發活動幾乎為零,沒有開發人員負責。還有比這更糟的項目嗎?
如何預防發生災難性故障
軟件開發人員致力于構建健壯、無錯誤的系統,無需過多人工維護就能正常運行多年。據此標準,上面所說的養老金腳本無疑是非常成功的項目。
然而現實很嚴峻,再好的項目有發生崩潰的一天。最終,所有內容都需要做更新。導致原因可能是:
系統運行所基于的硬件系統停產了。
系統的依賴關系不再可用。
依賴關系中出現了嚴重安全漏洞,而唯一可用的安全補丁僅適用于并不后向兼容的版本。
應用開發基于一些已不再成立的假設。
甚至是整個世界發生了改變,軟件必需因勢而變。
無論出于何種原因,變更都是不可避免的。唯一的問題是,當最終需要變更時,它的代價有多大。
對于一個活躍維護的系統,變更就不會那么痛苦。但是,對于一個已有幾年甚至數十年沒有維護的系統,那么很多因素都可能會導致災難性的錯誤。例如:
構建系統的開發人員已經離職。
源碼丟失。
開發人員不了解如何正確地編譯源碼,并構建可執行文件。
開發人員不了解如何部署系統。
開發人員不了解如何正確地配置運行可執行文件。
開發人員對代碼的架構和實現一頭霧水。
開發人員不了解使代碼功能正常運作所依賴的常量和隱含假設。
開發人員不了解如何運行自動化測試。
開發人員不了解如何調試測試問題。
開發人員不了解如何調試生產故障。
開發人員不了解如何獲取生產日志和指標度量。
一種解決方案是對上述問題做盡量詳細的記錄。但文檔并非最優的解決方案,因為其中難免會有遺漏。再全面的文檔,也比不上自己親自動手操作。
理想的做法
一個好的開端,就是企業指定專門的開發人員全面負責上述所有問題。但這還不夠。
如果僅僅反復“閱讀文檔”,那么就會產生厭倦。人們并不能從中獲得實踐經驗,進而解決實際問題。
如果加上“績效審核”,那么人們更傾向于新的出彩項目,很有可能會簡單地抹去并掩蓋舊的問題,甚至直接從中剔除問題。如果沒有真正面對的可交付成果或挑戰,許多人自然會選擇一條最輕松的道路。
真正要想避免軟件發生潰爛,唯一的方法是確保項目的持續推進,即使看起來毫無必要,或是存在風險。建立、維護和驗證實操知識和能力的最佳方法,就是不斷做出變更,并測試這些變更是否能成功地執行。一旦項目停止推進,那么相關實操知識就會過時和消解。
即使原地打轉聽上去很可笑,但這對疏于維護而言仍是一種進步。事實上,維護人員總是可以做一些事情實現向前推進,雖然步伐可能很校
一種做法是使用所有依賴關系的最新版本去更新開發環境,例如:
從 JDK 8 遷移到 11。
更新 JVM,使用 G1 垃圾回收機制替代原先的 CMS。
將 GCC 編譯器從版本 5 更新到 7。
將數據庫從 Postgres 9.5 更新到 Postgres 11。
將 AWS SDK 從版本 1.10 更新到 1.11。
在生產環境中安裝最新的 Linux 發行版。
在一些情況下,依賴關系會過時。這時就需要考慮整體遷移到新的架構。例如:
從 SPARC 遷移到 x86;
從 Solaris 遷移到 Linux。
同樣,保持開發人員的戰斗力,可對應用做如下更新:
修復頑疾和一些邊緣用例。
加固自動測試套件。
清理技術債務。
做性能優化。
實現新特性。
增量重構代碼庫,改進可讀性。
上述變化勢必會帶來暫時性風險,并產生看似“不必要的”支出。開發人員難免會犯一些錯誤,甚至引入新的錯誤。面對這些代價時,人們很容易退而求其次,認為“如果還沒有破裂,就不要修復。”
對于一個業務價值不大的系統,這可能是合理的做法。但對于任何關鍵任務系統而言,忽略問題將僅會掩蓋較小的瞬態風險,而沒有解決永久的災難性風險。一旦有一天需要緊急調試或更新系統,那么企業將無所適從。
對于任何關鍵任務系統,至關重要的就是維持實操知識和能力。做到這一點的唯一方法,就是不斷地開展實操。企業的大腦和肌肉一樣,不使用,就會失效。
作者簡介
Rajiv Prabhakar,畢業于密歇根大學和斯坦福大學,之后曾在 Intel 和 Sun Microsystem 公司工作五年,參與了 Jaketown、Skylake 和 SPARC 設計團隊。之后轉向軟件相關工作,先后任職于 Amazon、Google、Engineers Gate,并數次自己組建創業公司。
參考閱讀:
https://software.rajivprab.com/2020/04/25/preventing-software-rot/
為你推薦
InfoQ Pro是 InfoQ 專為技術早期開拓者和樂于鉆研的技術探險者打造的專業媒體服務平臺。