高擴展軟件架構設計

從最簡單的水平來看,可伸縮性就是做更多的事情。更多的事情可以是響應更多的用戶請求,執行更多的工作,或處理更多的數據。設計軟件這件事本身是複雜的,而讓軟件做更多的工作也有其特有的問題。這篇文章針對構建可伸縮軟件系統提出了一些原則和方針。
  1. 減少處理時間
  增加應用所做工作數量的一個方法就是減少完成單項工作所花費的時間。舉例來說,減少處理一個用戶請求所需的時間意味着你能在同樣長的時間內處理更多的用戶請求。這裏有一些本原則適用的例子和一些可能的實現策略。
並置(Collocation):通過並置數據和代碼,減少因獲取所需數據而產生的必要開銷。
緩存:如果數據和代碼不能並置,就緩存數據,以減少反覆取數據的開銷。
池化:通過池化昂貴的資源,減少與其使用相關的開銷。
並行化:通過分解問題、並行化獨立的步驟,減少完成一個工作單元所需的時間。
分區處理:通過分割處理代碼、並置相關的分區,儘可能將相關的處理過程集中在一起。
遠程處理:減少訪問遠程服務所花費的時間,比如可以通過更粗粒度地劃分接口。遠程還是本地是明確的設計決策,不能隨意來回更動,這一點應當牢記。還要考慮分佈式計算的第一準則——不要分佈你的對象。
  軟件開發人員總愛在不需要的地方引入抽象和層。是的,這些概念對軟件組件之間的解耦來說是很好的工具,但它們可能會增加複雜性、影響性能,尤其是在每層的數據表示之間都需要轉換的情況下。因此,減少處理時間還要注意保證抽象不要過於抽象化,並且沒有過多的分層。另外,對於我們視爲理所當然的運行時服務,有必要理解其成本,因爲除非它們提供了特定的服務水平協議,否則很有可能最終會成爲應用中的瓶頸。
  2. 分區
  減少單個工作單元的處理時間能達到不錯的效果,但當你達到單進程方案的極限,最終還是需要對系統作水平伸縮。在典型的Web應用中,水平伸縮可能很簡單,只要加入更多的Web服務器來處理用戶請求,再給它們加上負載均衡就行了。但是,你可能會發現總體架構的某些部分會成爲資源爭用的焦點,因爲一切東西都會在同一時間變得忙碌起來。一個很好的例子就是所有Web服務器後端的單一數據庫服務器。當這個單一的數據庫服務器變成瓶頸時,你必須改變方法,其中一種方式就是採用分區策略。簡而言之,這涉及到將架構的單個部分分解成更小、更容易管理的部分。將單個的元素分割成更小的部分能實現水平伸縮,這恰恰也是 eBay這樣的大型網站採用、以此來確保它們的架構可伸縮的技術。分區是一個很好的解決辦法,儘管你可能會發現犧牲了一致性。
  至於如何分割你的系統,那要看情形而定。真正無狀態的組件能簡單地作水平伸縮,將工作負載分散到所有實例上,讓組件的所有實例都能有效地運行。另一方面,如果需要維護某狀態,你需要找到一種工作量分割策略能允許有狀態組件的多重實例,讓每個實例負責工作和/或數據的一個獨特的子集。
  3. 可伸縮性在於併發
  可伸縮性天生就和併發聯繫在一起;畢竟,它就是要在同樣的時間內做更多的工作。像EJB早期版本這樣的技術試圖提供一種簡化的編程模型,鼓勵我們編寫單線程的組件。遺憾的是,組件往往要依賴於其它組件,還是導致了併發問題。如果沒有考慮併發,系統中的數據會很容易被損壞。另一方面,圍繞併發做了太多的保護會導致系統實質上變成串行的,限制了伸縮的能力。併發編程不是很難做到,在構建可伸縮系統的時候,有一些簡單的原則會有所幫助。
如果你確實需要持有鎖(比如本地對象、數據庫對象等),試着儘可能短地持有它們。
設法減少對共享資源的爭用,並儘可能是爭用避開關鍵處理路徑(比如通過異步調度工作)。
任何針對併發的設計都需要預先完成,以便能被充分地理解哪些資源可以被安全共享、哪裏可能會是潛在的可伸縮性瓶頸。
  4. 必須知道需求
  爲了構建一個成功的軟件系統,你需要知道你的目標是什麼、你針對什麼去做。儘管功能性需求往往是明確的,但常常會缺少非功能性需求(或系統質量需求)。如果你真是需要構建一套高可伸縮的軟件,那你首先需要調查清楚關鍵組件/工作流的以下特質:
目標平均性能和峯值性能(即響應時間、延遲等)。
目標平均負荷和峯值負荷(即併發用戶、信息量等)。
性能和可伸縮性可接受的極限。
  性能也許不是最緊要的方面,但你必須儘早知道這個信息,因爲處理可伸縮性的方法會由性能需求決定。
  5. 持續測試
  理解了需求就可以開始設計和構建解決方案。我們提出的設計、編寫的代碼實際上都是靜態的,所以你在執行之前不能完全斷定它會怎樣運轉。此外,所有關於性能和可伸縮性的決策應該由證據支持的原因也在於此,而且應當從項目一開始就收集和審覈這些證據,此後也要一直繼續。換句話說,就是設立貫穿系統的可度量目標,證實並度量實際的性能,並在項目的各個階段考慮性能。
  最常犯的錯誤之一是,我們對系統性能和可伸縮性的見解會被我們自己的經驗或道聽途說所混淆。你可能要審覈對工程做出的其它決策,這樣做的原因之一是要滿足系統的非功能性特性。比如說,非功能性需求可能會影響你選擇不使用標準,改用非主流/流行的一些東西。非功能性需求可能會打破僵化的教條,證據勝過教條。
  6. 架構先行
  或許對構建可伸縮的系統來說最重要的原則是,如果你需要使系統具備這樣的性質,就必須預先設計出這樣的性質。很多人(包括我自己)陷入的陷阱,就是以爲可以構建一個應用,它會自動地垂直伸縮(scale up)或水平伸縮(scale out),尤其是在J2EE剛出現的時候。設計爲可水平伸縮的應用幾乎總能垂直伸縮,但是設計爲垂直伸縮的應用幾乎不可能水平伸縮。大多數應用能通過在更加強大的硬件上運行來垂直伸縮,但水平伸縮卻是一個更爲複雜的問題。比如說,你怎麼確保數據在應用實例之間保持一致性?你如何使你的單例和同步代碼塊跨線程工作?
  當然,預先思考這件事情不一定等同於做一個瀑布式的、預先的大設計。迭代和敏捷過程都是助力,它們能提供一個框架幫助我們可以做出剛好夠用的設計來解決問題。要務實。哦,不管我們自認爲是多麼擅長於設計可伸縮的應用,不要相信自己、儘早地編寫/測試代碼纔是最好的舉動。
  7. 着眼於全局
  最後,記着要着眼於全局——看到樹木之前先看看森林。對我們來說,在細粒度代碼級別調整組件確實很容易,但最終需要優化的卻是作爲一個整體的系統。關注每一個環節的性能和可伸縮性,必要時犧牲局部的優化。如果你需要使用性能分析工具確定瓶頸,不妨去做,但在對全局的性能有所認識之前先不要急於動手。由於性能與整個系統所有等待時間的集合成反比,任何等待時間增加得比負載還快的操作都會成爲問題。儘管說了這麼多,但我還想指出,如果你發現滿足性能和可伸縮性目標很困難,那就有必要懷疑一下是否選擇了正確的架構。還是那句話,着眼於全局,確保有人在承擔架構師的責任。
  總結
  這篇文章針對構建可伸縮應用提出了一些原則和方針,覆蓋了軟件開發過程中許多不同的方面。無論誰要構建可伸縮的系統,我能給他的最好建議就是你需要明確地考慮並設計你的系統。可伸縮性不是魔術,它也不會無償獲得。最後一點,更快的硬件也許能救你於一時,但還是不要依賴它爲妙!

發佈了43 篇原創文章 · 獲贊 14 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章