當前位置:商標查詢大全網 - 網遊競技 - 對軟件系統的壹些理解

對軟件系統的壹些理解

這篇文章是想表達我對系統軟件的壹些理解,風格跟之前的不太壹樣,整體偏“務虛”。我自己其實是不太擅長“務虛”的,甚至是有點排斥。就跟相比起看論文,我更喜歡看code,當然我也看論文,只不過相對來說少些。 畢業以來壹直在數據庫存儲引擎領域工作,過去5年主要精力集中在阿裏自研LSM-Tree存儲引擎X-Engine研發上,並且在過去兩年多時間我們完成了X-Engine的雲原生架構升級和商業化,在公有雲上承接壹定規模的客戶並穩定運行,在業界應該也是首個基於LSM-Tree架構實現雲原生能力的TP存儲引擎。完整經歷壹個TP存儲引擎的架構規劃、設計研發、落地上線,穩定性運維的全周期,並且得益於從我進入數據庫領域壹路以來經歷的高水平團隊、technology leader以及整個團隊成員的出色工程能力和技術視野,加上我自己在此過程中的壹些思考,階段性的形成了壹些自己的心得體會。 另外,跟業界壹些優秀的架構師和工程師交流,發現對於系統工程的理解有很多的***鳴,也收到很多非常有價值的輸入,當然也存在壹些不同的觀點。這也是促使我寫這篇文章的主要原因,希望能將我自己的壹些理解表達清楚,這些觀點並不fashion,更談不上創新,更多的是壹些自己的思考和經驗之談。

觀點1:軟件的本質是對硬件資源的消耗。不同軟件的區別在於,消耗硬件資源去解決什麽問題以及如何分配硬件資源的消耗。軟件架構設計中經常提到"抽象"和“trade-off”,抽象本質上的就是"解決什麽問題","trade-off"其實就是"如何分配硬件資源"。 舉個例子TP存儲引擎和AP存儲引擎,從實現上可以列舉出壹大堆不同的地方,行存 VS 列存、二級索引 VS ZoneMap索引、強事務 VS 弱事務等等。這些不同之處其實都是結果,導致這些的根本原因是:

1)兩者解決的問題不同,TP場景主要是online實時業務,這些業務的特征是整體數據規模相對較小(真正需要online處理的數據, 歷史 數據可能很多)、請求短平快、數據locality明顯、高並發低時延等,而AP場景整體的數據規模大、計算密度高、高吞吐等。(解決什麽問題)

2)TP引擎的完整事務支持使得業務的並發控制簡化很多,其實就是把業務系統本來需要做的事情,TP引擎自己做了,當然也就意味著TP引擎需要為此消耗壹部分硬件資源。而AP引擎為了加快數據入庫的速度,事務的支持比較弱,這部分工作還是由業務系統來完成(比如ETL),也就不需要為此消耗硬件資源。(如何分配硬件資源) 觀點2: 系統軟件的重大變革,背後基本都是硬件發展所推動的。這跟觀點1)是相呼應的,系統軟件領域的理論在進入21世紀之前,學術界已經做了廣泛深入的研究。從最開始計算機的出現,到大型機和小型機,再到家庭PC和廉價通用服務器,以及現在的雲計算IAAS服務,基本上系統軟件發展也是跟隨這個脈絡在發展。系統軟件的再次火熱,本質上也是因為IAAS這個“新硬件”所推動的。整個IAAS的on-demand獲取,打破了系統軟件之前在物理資源受限的背景下很多設計,這也就是為什麽雲原生系統軟件會迎來新的機會。 觀點3: 幾乎不存在某壹種系統架構全面領先另外壹種架構。這跟觀點1)2)是相呼應的,不同的架構選擇背後都是不同的trade-off,所謂有得必有舍。經常聽到壹些說法,妳看這篇論文、這篇文章,他們這種架構就沒有某問題,我們這種架構就有這個問題。我聽到這些觀點的第壹反應是質疑,這裏邊主要有三個原因:

1)很多論文和文章的實驗結果是沒法復現的,也就說很有可能他的結論就有問題;

2)很多時候只會強調“得”的部分,而“舍”的部分是沒有講的。

3)我們系統所存在的問題到底影響有多大,是不是可以解決的,這些需要量化的數據才能確定。輕易地被各種論文和文章的結論影響,很有可能會做出壹個不倫不類的系統。就像習武之人各個門派的武功都學學,最終很容易走火入魔。 觀點4:條條大路通羅馬,最終系統對外呈現的區別,更多的是工程實現的原因,而非架構的原因。不同的系統架構需要解決的大部分問題本質上其實是壹樣的,並且組成壹個系統的零部件都差不多,只是根據需要選擇哪些零部件來構建系統。只有躬身入局,真正地去面對問題、分析問題、解決問題,才能認清楚其中的本質,否則很容易變成紙上談兵。 舉個例子:經常有人問我LSM-Tree架構中持續寫入數據時,compaction問題對性能影響很大。這個問題我是這麽看的,首先LSM-Tree架構上寫入吞入優勢的其中壹個原因是,相比於innodb這種磁盤B+ Tree在寫入的時候直接sort on write(page內有序,全局有序),LSM-Tree架構選擇將壹部分sort轉移到sort on compaction、sort on read,本質上是將寫入時排序的資源消耗,轉移到了compaction或read。刷臟其實是包含兩個動作:生成臟頁,將臟頁刷盤。innodb相當於是在寫入的時候生成臟頁,在刷臟的時候就是單純的io操作。而compaction其實是同時做了生成“臟頁”和“臟頁”刷盤。innodb如果持續寫入的話,也會有刷臟來不及時導致影響寫入性能的問題。因為innodb刷臟和compaction之所以成為問題,本質上都是因為內存和磁盤寫入速度的差異,導致生產者消費者模型失衡。所以innodb的刷臟和LSM-Tree的compaction本質上是相同的問題,只是通過不同的方法來將這個過程對系統的影響降到最低。

接下來的內容,主要是在進行詳細設計的時候我認為比較重要的原則。這些原則的道理其實很容易理解,並且“軟件工程”這門學科已經研究的很充分,但是實際操作的時候其實是蠻困難的,可能是 歷史 包袱的原因,也有可能是外界環境的原因,需要根據實際情況做出不同的trade-off。值得註意的是,我們做出的trade-off壹定是要經過仔細考慮的,而不是草率的,否則很容易出現“有舍沒有得”。另外遵守這些原則設計實現出來的系統和不完全遵守這些原則設計實現出來的系統,結果其實是“好和更好的區別”,但是“好多少”這個量在系統做出來之前,其實很難衡量。這七個原則不是獨立存在的,而是相輔相成的。 面向場景: 首先我們需要明確要解決什麽問題,這是整個系統構建的出發點。one size fit all的系統在過去是不存在的,在未來也不壹定存在。系統的完善,必然是要靠不斷的叠代來完成的,那麽如何叠代本質上就是我們在那些階段解決哪些問題。壹個系統可以有遠大的目標去解決很多問題,但是所有問題的路標需要有相對清晰的規劃,以達到既可以快速滿足需求,同時保留向未來演進和擴展的基礎。 實際研發過程中,可能發生的兩類錯誤是:

1)想采用敏捷開發的方式來進行工程管理,以滿足整個叠代的需求。敏捷開發本質上先定義最小功能集,也就是首先想清楚解決什麽問題,然後快速的叠代擴充功能,有點像小步快走。在實操上,很容易把敏捷開發搞成了"快、糙、猛",有點大幹30天趕英超美的味道。

2)問題定義不清楚,系統的“不變式”設置就容易草率。每個系統都有壹些“不變式”,隨後很多設計都是基於這些不變式進行展開的,比如在LSM-Tree系統中壹個常見的“不變式”是更新版本的數據在更低的層次,同壹行的數據的多個版本如果同時在memtable、level0、level1中存在,那麽必然memtable中對應的版本是最新的,level0中的版本也比level1中的更新。如果在叠代的過程中發現之前設置的“不變式”不合理的,那麽進行改動的代價是非常之大的。 面向解耦:無論是自上而下的去設計系統,還是自下而上的去設計系統,很重要的壹個思考邏輯就是將各個模塊間的耦合度降到最低。解耦做地比較好的系統,往往意味著:

1)每個模塊的功能是考慮的比較清楚,方案的完整度是比較高的;

2)有利於專註的將某個模塊實現的更加高效,避免其他模塊的影響;

3)有利於之後的叠代,影響面可控;

4)出了問題好排查,單個模塊的問題是比較好排查,真正那些難搞的問題往往是問題在各個模塊間傳導後才暴露出來,比如A模塊出問題,經過模塊B、C、D,最後在模塊E暴露出來。 有些質疑的觀點會說,面向解耦的思路去設計,有可能會犧牲系統的整體性能。其實這個跟不要壹開始就為性能做過度的設計是壹樣的道理,真到了某些解耦的設計影響了性能,那麽該耦合的就去耦合。把兩個模塊耦合在壹起的難度往往是低於把耦合在壹起的兩個模塊拆開。 面向防禦:這個就是防禦性編程的邏輯,要假設調用的函數都是有可能出錯的, ,比如內存分配可能出錯,io可能出錯,基礎庫的調用可能出錯等等,基於此來考慮如果出錯,系統的行為是什麽。有壹個非常簡單的原則就是"fail stop", 如果沒有完整的防禦,那麽即使fail了也很難立即stop,最終造成壹些很奇怪的表象。 通常的質疑是:

1)妳看這個函數的邏輯肯定不會失敗的。也許從當前來看這個函數確實不會失敗,但是很難保證隨著叠代增加邏輯,之後沒有失敗的可能性。

2)加了這麽多防禦,防禦代碼比實際邏輯的代碼還多,會影響性能。首先,現在cpu的分支預測能力,基本上可以做到絕大部分情況下防禦代碼不會影響性能。另外跟對於面向耦合的質疑壹樣,真到某些防禦代碼成為了性能瓶頸,該優化就優化。優化壹個防禦,總比去解決壹個因為沒有防禦而導致的問題代價更低吧。 面向測試:在測試階段修復問題的代價是遠低於在生產環境修復問題的代價,因此讓系統變得可測試是非常重要的。系統可測試的標準就是,能方便的進行單元測試、集成測試,並覆蓋絕大部分的代碼路徑。可測試的系統,隨著不斷的叠代,會累積越來越多的測試case,不斷的夯實穩定性基礎。面向測試跟面向解耦、面向防禦是相輔相成的。只有模塊間耦合度足夠的低,才有可能做更多的測試,否則做壹個模塊的測試需要mock很多亂七八糟的東西。面向防禦會使得測試的行為可以更好的預期,不然輸入了壹個異常的參數,具體怎麽失敗是不確定的,那測試case就很難寫了。 面向運維:bug是壹定會有的,對於復雜的系統,不管前期做多少準備都很難避免生產環境中遇到未知的問題。面向運維的主要目的是,遇到問題的時候,能用代價最低的手段去及時止損。遇到線上問題,動態調參數就能解決比需要重啟才能解決的代價更低,重啟能解決比需要發版才能解決的代價更低。面向運維不僅僅是加幾個參數,加幾個開關那麽簡單,而是需要把“面向運維”作為設計方案的重要組成部分來考慮,保證出了問題有運維手段,有運維手段敢用,用了以後有效果。 面向問題本質:當去解決壹個問題的時候,壹定要多思考這個問題的本質原因是什麽,簡單的問題復雜化和復雜的問題簡單化,都是因為沒有抓住本質。如果能思考清楚其背後的本質原因,從源頭避免掉是更加徹底的解決方式,否則很容易陷入不斷打補丁的狀態,我壹直有個觀點:“沒有抓住問題本質去解決問題,結果往往是在制造問題”。另外壹個經驗是,如果壹個模塊連續出了好幾次問題,那麽就要想想是不是在最開始的設計上就有需要改進的地方。 面向可視化:可視化的目標主要是以更加直觀的形式,來展現系統運行狀況,這對於系統調優和診斷是非常重要的。當系統異常時,可視化的方式可以幫助快速定位到系統哪裏出了問題。另外壹方面是,可以提供接口給監控系統做 歷史 狀態的追蹤。比如oracle的診斷監控就是壹個非常優秀的案例,而SnowFlake對於內部狀態的打點監控也是近乎瘋狂。

說了這麽多,最終系統還是靠壹行行的code實現出來的,保持匠心、嚴謹、較真的態度去打造系統是非常樸素正確,但又很難做到的事情,***勉!

原文鏈接: /m/1000349863/