可伸縮性最佳實踐之來自eBay的經驗

       在eBay,可伸縮性是我們每天奮力抵抗的一大架構壓力。我們所做的每一項架構及設計決策,身前身後都能看到它的蹤影。當我們面對的是全世界數以億計的用戶,每天的頁面瀏覽量超過10億,系統中的數據量要用皮字節(1015或250)來計算——可伸縮性是生死交關的問題。

     在一個可伸縮的架構中,資源的消耗應該隨負載線性(或更佳)上升,負載可由用戶流量、數據量等測量。如果說性能衡量的是每一工作單元所需的資源消 耗,可伸縮性則是衡量當工作單元的數量或尺寸增加時,資源消耗的變化情況。換句話說,可伸縮性是整個價格-性能曲線的形狀,而不是曲線上某一點的取值。

     可伸縮性有很多側面——事務的方面、運營的方面、還有開發的方面。我們在改善一個Web系統的事務吞吐量的過程中學到了很多經驗,本文總結了其中若 幹關鍵的最佳實踐。可能很多最佳實踐你會覺得似曾相識,也可能有素未謀面的。這些都是開發和運營eBay網站的衆人的集體經驗結晶。

     最佳實踐 #1:按功能分割

     相關的功能部分應該合在一起,不相關的功能部分應該分割開來——不管你把它叫做SOA、功能分解還是工程祕訣。而且,不相關的功能之間耦合程度越鬆散,就越能靈活地獨立伸縮其中的一部分。

     在編碼層次,我們無時不刻都在運用這條原則。JAR文件、包、Bundle等等,都是用來隔離和抽象功能的機制。

     在應用層次,eBay將不同的功能劃分成幾個應用程序池。銷售功能由一組應用服務器運行,投標功能由另一組負責,搜索又是另外一組服務器。我們把總 共約16,000臺應用服務器分成220個池。這樣就可以根據某項功能的資源消耗,單獨地伸縮其中一個池。我們也因此得以進一步隔離及合理化資源依賴關係 ——比如銷售池只需要訪問後臺資源的一個相對較小的子集。

     在數據庫層次,我們也採取同樣的做法。eBay沒有無所不包的單一數據庫,相反我們有一組數據庫主機存放用戶數據、一組存放商品數據、一組存放購買數據……總共1000個邏輯數據庫分佈在400臺物理主機上。同樣,這種做法讓我們得以單獨爲某一類數據伸縮其數據庫設施。

     最佳實踐 #2:水平切分

     按功能分割對我們的幫助很大,但單憑它還不足以得到完全可伸縮的架構。即使將功能一一解耦,單項功能的資源需求隨着時間增長,仍然有可能超出單一系 統的能力。我們常常提醒自己,“沒有分割就沒有伸縮”。在單項功能內部,我們需要能把工作負載分解成許多我們有能力駕馭的小單元,讓每個單元都能維持良好 的性能價格比。這就是水平分割出場的時候了。

     在應用層次,由於eBay將各種交互都設計成無狀態的,所以水平分割是輕而易舉之事。用標準的負載均衡服務器來路由進入的流量。所有應用服務器都是 均等的,而且任何服務器都不會維持事務性的狀態,因此負載均衡可以任意選擇應用服務器。如果需要更多處理能力,只需要簡單地增加新的應用服務器。

     數據庫層次的問題比較有挑戰性,原因是數據天生就是有狀態的。我們會按照主要的訪問路徑對數據作水平分割(或稱爲“sharding”)。例如用戶 數據目前被分割到20臺主機上,每臺主機存放1/20的用戶。隨着用戶數量的增長,以及每個用戶的數據量增長,我們會增加更多的主機,將用戶分散到更多的 機器上去。商品數據、購買數據、帳戶數據等等也都用同樣的方式處理。用例不同,我們分割數據的方案也不同:有些是對主鍵簡單取模(ID尾數爲1的放到第一 臺主機,尾數爲二的放到下一臺,以此類推),有些是按照ID的區間分割(1-1M、1-2M等等),有些用一個查找表,還有些是綜合以上的策略。不過具體 的分割方案如何,總的思想是支持數據分割及重分割的基礎設施在可伸縮性上遠比不支持的優越。

     最佳實踐 #3:避免分佈式事務

     看到這裏,你可能在疑惑按功能劃分數據和水平劃分數據的實踐如何滿足事務要求。畢竟,幾乎任何有意義的操作都要更新一個以上的實體——立即就可以舉 出用戶和商品的例子。正統的廣爲人知的答案是:建立跨資源的分佈式事務,用兩段式提交來保證要麼所有資源全都更新,要麼全都不更新。很不幸,這種悲觀方案 的成本很可觀。伸縮、性能和響應延遲都受到協調成本的反面影響,隨着依賴的資源數量和客戶數量的上升,這些指標都會以幾何級數惡化。可用性亦受到限制,因 爲所有依賴的資源都必須就位。實用主義的答案是,對於不相關的系統,放寬對它們的跨系統事務的保證。

     左右逢源是辦不到的。保證跨多個系統或分區之間的即時的一致性,通常既無必要,也不現實。Inktomi的Eric Brewer十年前提出的CAP公理是這樣說的:分佈式系統的三項重要指標——一致性(Consistency)、可用性(Availability)和 分區耐受性(Partition-tolerance)——在任意時刻,只有兩項能同時成立。對於高流量的網站來說,我們必須選擇分區耐受性,因爲它是實 現可伸縮的根本。對於24x7運行的網站,選擇可用性也是理所當然的。於是只好放棄即時一致性(immediate consistency)。

      關於項目管理的相關知識,推薦去查閱中國軟件工程網(rjgc.net).

     在eBay,我們絕對不允許任何形式的客戶端或者分佈式事務——因此絕不需要兩段式提交。在某些經過仔細定義的情形下,我們會將作用於同一個數據庫 的若干語句捆綁成單個事務性的操作。而對於絕大部分操作,單條語句是自動提交的。雖然我們故意放寬正統的ACID屬性,以致不能在所有地方保證即時一致 性,但現實的結果是大部分系統在絕大部分時間都是可用的。當然我們也採用了一些技術來幫助系統達到最終的一致性(eventual consistency):周密調整數據庫操作的次序、異步恢復事件,以及數據覈對(reconciliation)或者集中決算(settlement batches)。具體選擇哪種技術要根據特定用例對一致性的需求來決定。

     對於架構師和系統的設計者來說,關鍵是要明白一致性並非“有”和“沒有”的單選題。現實中大多數的用例都不要求即時一致性。正如我們經常根據成本和其他壓力因素來權衡可用性的高低,一致性也同樣可以量體裁衣,根據特定操作的需要而保證適當程度的一致性。

     最佳實踐 #4:用異步策略解耦程序

     提高可伸縮性的另一項關鍵措施是積極地採取異步策略。如果組件A同步調用組件B,那麼A和B就是緊密耦合的,而緊耦合的系統其可伸縮性特徵是各部分 必須共同進退——要伸縮A必須同時伸縮B。同步調用的組件在可用性方面也面臨着同樣的問題。我們回到最基本的邏輯:如果A推出B,那麼非B推出非A。也就 是說,若B不可用,則A也不可用。如果反過來A和B的聯繫是異步的,不管是通過隊列、多播消息、批處理還是什麼其他手段,它們就可以分別地伸縮。而且,此 時A和B的可用性特徵是相互獨立的——即使B受困或者死掉,A仍然能夠繼續前進。

     整個基礎設施從上到下都應該貫徹這項原則。即使在單個組件內部也可通過SEDA(分階段的事件驅動架構,Staged Event-Driven Architecture)等技術實現異步性,同時保持一個易於理解的編程模型。組件之間也遵守同樣的原則——儘可能避免同步帶來的耦合。在多數情況下, 兩個組件在任何事件中都不會有直接的業務聯繫。在所有的層次,把過程分解爲階段(stages or phases),然後將它們異步地連接起來,這是伸縮的關鍵。

     最佳實踐 #5:將過程轉變爲異步的流

     用異步的原則解耦程序,儘可能將過程變爲異步的。對於要求快速響應的系統,這樣做可以從根本上減少請求者所經歷的響應延遲。對於網站或者交易系統, 犧牲數據或執行的延遲時間(完成全部工作的實踐)來換取用戶的延遲時間(用戶得到響應的時間)是值得的。活動跟蹤、單據開付、決算和報表等處理過程顯然都 應該屬於後臺活動。主要用例過程中常常有很多步驟可以進一部分解成異步運行。任何可以晚點再做的事情都應該晚點再做。

     還有一個同等重要的方面認識到的人不多:異步性可以從根本上降低基礎設施的成本。同步地執行操作迫使你必須按照負載的峯值來配備基礎設施——即使在 任務最重的那一天裏任務最重的那一秒,設施也必須有能力立即完成處理。而將昂貴的處理過程轉變爲異步的流,基礎設施就不需要按照峯值來配備,只需要滿足平 均負載。而且也不需要立即處理所有的請求,異步隊列可以將處理任務分攤到較長的時間裏,因而起到削峯的作用。系統的負載變化越大,曲線越多尖峯,就越能從 異步處理中得益。

     最佳實踐 #6:虛擬化所有層次

     虛擬化和抽象化無所不在,計算機科學裏有一句老話:所有問題都可以通過增加一個間接層次來解決。操作系統是對硬件的抽象,而許多現代語言所用的虛擬 機又是對操作系統的抽象。對象-關係映射層抽象了數據庫。負載均衡器和虛擬IP抽象了網絡終端。當我們通過分割數據和程序來提高基礎設施的可伸縮性,爲各 種分割增加額外的虛擬層次就成爲重中之重。

     在eBay,我們虛擬化了數據庫。應用與邏輯數據庫交互,邏輯數據庫再按照配置映射到某個特定的物理機器和數據庫實例。應用也抽象於執行數據分割的 路由邏輯,路由邏輯會把特定的記錄(如用戶XYZ)分配到指定的分區。這兩類抽象都是在我們自己開發的O/R層上實現的。這樣虛擬化之後,我們的運營團隊 可以按需要在物理主機羣上重新分配邏輯主機——分離、合併、移動——而完全不需要接觸應用程序代碼。

     搜索引擎同樣是虛擬化的。爲了得到搜索結果,一個聚合器組件會在多個分區上執行並行的查詢,但這個高度分割的搜索網格在客戶看來只是單一的邏輯索引。

     以上種種措施並不只是爲了程序員的方便,運營上的靈活性也是一大動機。硬件和軟件系統都會故障,請求需要重新路由。組件、機器、分區都會不時增減、 移動。明智地運用虛擬化,可使高層的設施對以上變化難得糊塗,你也就有了騰挪的餘地。虛擬化使基礎設施的伸縮成爲可能,因爲它使伸縮變成可管理的。

     最佳實踐 #7:適當地使用緩存

     最後要適當地使用緩存。這裏給出的建議不一定普遍適用,因爲緩存是否高效極大地依賴於用例的細節。說到底,要在存儲約束、對可用性的需求、對陳舊數 據的容忍程度等條件下最大化緩存的命中率,這纔是一個高效的緩存系統的最終目標。經驗證明,要平衡衆多因素是極其困難的,即使暫時達到目標,情況也極可能 隨着時間而改變。

     最適合緩存的是很少改變、以讀爲主的數據——比如元數據、配置信息和靜態數據。在eBay,我們積極地緩存這種類型的數據,並且結合使用“推”和“ 拉”兩種方法保持系統在一定程度上的更新同步。減少對相同數據的重複請求能達到非常顯著的效果。頻繁變更、讀寫兼有的數據很難有效地緩存。在eBay,我 們大多有意識地迴避這樣的難題。我們一直不對請求間短暫存在的會話數據作任何緩存。也不在應用層緩存共享的業務對象,比如商品和用戶數據。我們有意地犧牲 緩存這些數據的潛在利益,換取可用性和正確性。在此必須指出,其他網站採取了不同的途徑,作了不同的取捨,也同樣取得了成功。

     好東西也會過猶不及。爲緩存分配的內存越多,能用來服務單個請求的內存就越少。應用層常常有內存不足的壓力,因此這是非常現實的權衡。更重要的一 點,當你開始依賴於緩存,那麼主要系統就只需要滿足緩存未命中時的處理要求,自然而然你就會想到可以削減主要系統。但當你這樣做之後,系統就完全離不開緩 存了。現在主要系統沒辦法直接應付全部流量,也就是說網站的可用性取決於緩存能否100%正常運行——潛在的危局。哪怕是例行的操作,比如重新配置緩存資 源、把緩存移動到別的機器、冷啓動緩存服務器,都有可能引發嚴重的問題。

     做得好,緩存系統能讓可伸縮性的曲線向下彎曲,也就是比線性增長還要好——後續請求從緩存中取數據比從主存儲取數據成本低廉。反過來,緩存做得不好 會引入相當多額外的經常耗費,也會妨礙到可用性。我還沒見過哪個系統沒機會讓緩存大展拳腳的,關鍵是要根據具體情況找到適當緩存策略。

  總結

  可伸縮性有時候被叫做“非功能性需求”,言下之意是它與功能無關,也就比較不重要。這麼說簡直錯到了極點。我的觀點是,可伸縮性是功能的先決條件——優先級爲0的需求,比一切需求的優先級都高。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章