作者 Randy Shoup譯者 郭曉剛 發(fā)布于 2008年6月12日 下午7時(shí)5分
- Architecture
- 主題
- 設(shè)計(jì),
- 性能和可伸縮性
- 標(biāo)簽
- eBay
在eBay,可伸縮性是我們每天奮力抵抗的一大架構(gòu)壓力。我們所做的每一項(xiàng)架構(gòu)及設(shè)計(jì)決策,身前身后都能看到它的蹤影。當(dāng)我們面對(duì)的是全世界數(shù)以?xún)|計(jì)的用戶,每天的頁(yè)面瀏覽量超過(guò)10億,系統(tǒng)中的數(shù)據(jù)量要用皮字節(jié)(1015或250)來(lái)計(jì)算——可伸縮性是生死交關(guān)的問(wèn)題。
在一個(gè)可伸縮的架構(gòu)中,資源的消耗應(yīng)該隨負(fù)載線性(或更佳)上升,負(fù)載可由用戶流量、數(shù)據(jù)量等測(cè)量。如果說(shuō)性能衡量的是每一工作單元所需的資源消 耗,可伸縮性則是衡量當(dāng)工作單元的數(shù)量或尺寸增加時(shí),資源消耗的變化情況。換句話說(shuō),可伸縮性是整個(gè)價(jià)格-性能曲線的形狀,而不是曲線上某一點(diǎn)的取值。
可伸縮性有很多側(cè)面——事務(wù)的方面、運(yùn)營(yíng)的方面、還有開(kāi)發(fā)的方面。我們?cè)诟纳埔粋€(gè)Web系統(tǒng)的事務(wù)吞吐量的過(guò)程中學(xué)到了很多經(jīng)驗(yàn),本文總結(jié)了其中若 干關(guān)鍵的最佳實(shí)踐。可能很多最佳實(shí)踐你會(huì)覺(jué)得似曾相識(shí),也可能有素未謀面的。這些都是開(kāi)發(fā)和運(yùn)營(yíng)eBay網(wǎng)站的眾人的集體經(jīng)驗(yàn)結(jié)晶。
最佳實(shí)踐 #1:按功能分割
相關(guān)的功能部分應(yīng)該合在一起,不相關(guān)的功能部分應(yīng)該分割開(kāi)來(lái)——不管你把它叫做SOA、功能分解還是工程秘訣。而且,不相關(guān)的功能之間耦合程度越松散,就越能靈活地獨(dú)立伸縮其中的一部分。
在編碼層次,我們無(wú)時(shí)不刻都在運(yùn)用這條原則。JAR文件、包、Bundle等等,都是用來(lái)隔離和抽象功能的機(jī)制。
在應(yīng)用層次,eBay將不同的功能劃分成幾個(gè)應(yīng)用程序池。銷(xiāo)售功能由一組應(yīng)用服務(wù)器運(yùn)行,投標(biāo)功能由另一組負(fù)責(zé),搜索又是另外一組服務(wù)器。我們把總 共約16,000臺(tái)應(yīng)用服務(wù)器分成220個(gè)池。這樣就可以根據(jù)某項(xiàng)功能的資源消耗,單獨(dú)地伸縮其中一個(gè)池。我們也因此得以進(jìn)一步隔離及合理化資源依賴(lài)關(guān)系 ——比如銷(xiāo)售池只需要訪問(wèn)后臺(tái)資源的一個(gè)相對(duì)較小的子集。
在數(shù)據(jù)庫(kù)層次,我們也采取同樣的做法。eBay沒(méi)有無(wú)所不包的單一數(shù)據(jù)庫(kù),相反我們有一組數(shù)據(jù)庫(kù)主機(jī)存放用戶數(shù)據(jù)、一組存放商品數(shù)據(jù)、一組存放購(gòu)買(mǎi)數(shù)據(jù)……總共1000個(gè)邏輯數(shù)據(jù)庫(kù)分布在400臺(tái)物理主機(jī)上。同樣,這種做法讓我們得以單獨(dú)為某一類(lèi)數(shù)據(jù)伸縮其數(shù)據(jù)庫(kù)設(shè)施。
最佳實(shí)踐 #2:水平切分
按功能分割對(duì)我們的幫助很大,但單憑它還不足以得到完全可伸縮的架構(gòu)。即使將功能一一解耦,單項(xiàng)功能的資源需求隨著時(shí)間增長(zhǎng),仍然有可能超出單一系 統(tǒng)的能力。我們常常提醒自己,“沒(méi)有分割就沒(méi)有伸縮”。在單項(xiàng)功能內(nèi)部,我們需要能把工作負(fù)載分解成許多我們有能力駕馭的小單元,讓每個(gè)單元都能維持良好 的性能價(jià)格比。這就是水平分割出場(chǎng)的時(shí)候了。
在應(yīng)用層次,由于eBay將各種交互都設(shè)計(jì)成無(wú)狀態(tài)的,所以水平分割是輕而易舉之事。用標(biāo)準(zhǔn)的負(fù)載均衡服務(wù)器來(lái)路由進(jìn)入的流量。所有應(yīng)用服務(wù)器都是 均等的,而且任何服務(wù)器都不會(huì)維持事務(wù)性的狀態(tài),因此負(fù)載均衡可以任意選擇應(yīng)用服務(wù)器。如果需要更多處理能力,只需要簡(jiǎn)單地增加新的應(yīng)用服務(wù)器。
數(shù)據(jù)庫(kù)層次的問(wèn)題比較有挑戰(zhàn)性,原因是數(shù)據(jù)天生就是有狀態(tài)的。我們會(huì)按照主要的訪問(wèn)路徑對(duì)數(shù)據(jù)作水平分割(或稱(chēng)為“sharding”)。例如用戶 數(shù)據(jù)目前被分割到20臺(tái)主機(jī)上,每臺(tái)主機(jī)存放1/20的用戶。隨著用戶數(shù)量的增長(zhǎng),以及每個(gè)用戶的數(shù)據(jù)量增長(zhǎng),我們會(huì)增加更多的主機(jī),將用戶分散到更多的 機(jī)器上去。商品數(shù)據(jù)、購(gòu)買(mǎi)數(shù)據(jù)、帳戶數(shù)據(jù)等等也都用同樣的方式處理。用例不同,我們分割數(shù)據(jù)的方案也不同:有些是對(duì)主鍵簡(jiǎn)單取模(ID尾數(shù)為1的放到第一 臺(tái)主機(jī),尾數(shù)為二的放到下一臺(tái),以此類(lèi)推),有些是按照ID的區(qū)間分割(1-1M、1-2M等等),有些用一個(gè)查找表,還有些是綜合以上的策略。不過(guò)具體 的分割方案如何,總的思想是支持?jǐn)?shù)據(jù)分割及重分割的基礎(chǔ)設(shè)施在可伸縮性上遠(yuǎn)比不支持的優(yōu)越。
最佳實(shí)踐 #3:避免分布式事務(wù)
看到這里,你可能在疑惑按功能劃分?jǐn)?shù)據(jù)和水平劃分?jǐn)?shù)據(jù)的實(shí)踐如何滿足事務(wù)要求。畢竟,幾乎任何有意義的操作都要更新一個(gè)以上的實(shí)體——立即就可以舉 出用戶和商品的例子。正統(tǒng)的廣為人知的答案是:建立跨資源的分布式事務(wù),用兩段式提交來(lái)保證要么所有資源全都更新,要么全都不更新。很不幸,這種悲觀方案 的成本很可觀。伸縮、性能和響應(yīng)延遲都受到協(xié)調(diào)成本的反面影響,隨著依賴(lài)的資源數(shù)量和客戶數(shù)量的上升,這些指標(biāo)都會(huì)以幾何級(jí)數(shù)惡化。可用性亦受到限制,因 為所有依賴(lài)的資源都必須就位。實(shí)用主義的答案是,對(duì)于不相關(guān)的系統(tǒng),放寬對(duì)它們的跨系統(tǒng)事務(wù)的保證。
左右逢源是辦不到的。保證跨多個(gè)系統(tǒng)或分區(qū)之間的即時(shí)的一致性,通常既無(wú)必要,也不現(xiàn)實(shí)。Inktomi的Eric Brewer十年前提出的CAP公理是這樣說(shuō)的:分布式系統(tǒng)的三項(xiàng)重要指標(biāo)——一致性(Consistency)、可用性(Availability)和 分區(qū)耐受性(Partition-tolerance)——在任意時(shí)刻,只有兩項(xiàng)能同時(shí)成立。對(duì)于高流量的網(wǎng)站來(lái)說(shuō),我們必須選擇分區(qū)耐受性,因?yàn)樗菍?shí) 現(xiàn)可伸縮的根本。對(duì)于24x7運(yùn)行的網(wǎng)站,選擇可用性也是理所當(dāng)然的。于是只好放棄即時(shí)一致性(immediate consistency)。
在eBay,我們絕對(duì)不允許任何形式的客戶端或者分布式事務(wù)——因此絕不需要兩段式提交。在某些經(jīng)過(guò)仔細(xì)定義的情形下,我們會(huì)將作用于同一個(gè)數(shù)據(jù)庫(kù) 的若干語(yǔ)句捆綁成單個(gè)事務(wù)性的操作。而對(duì)于絕大部分操作,單條語(yǔ)句是自動(dòng)提交的。雖然我們故意放寬正統(tǒng)的ACID屬性,以致不能在所有地方保證即時(shí)一致 性,但現(xiàn)實(shí)的結(jié)果是大部分系統(tǒng)在絕大部分時(shí)間都是可用的。當(dāng)然我們也采用了一些技術(shù)來(lái)幫助系統(tǒng)達(dá)到最終的一致性(eventual consistency):周密調(diào)整數(shù)據(jù)庫(kù)操作的次序、異步恢復(fù)事件,以及數(shù)據(jù)核對(duì)(reconciliation)或者集中決算(settlement batches)。具體選擇哪種技術(shù)要根據(jù)特定用例對(duì)一致性的需求來(lái)決定。
對(duì)于架構(gòu)師和系統(tǒng)的設(shè)計(jì)者來(lái)說(shuō),關(guān)鍵是要明白一致性并非“有”和“沒(méi)有”的單選題。現(xiàn)實(shí)中大多數(shù)的用例都不要求即時(shí)一致性。正如我們經(jīng)常根據(jù)成本和其他壓力因素來(lái)權(quán)衡可用性的高低,一致性也同樣可以量體裁衣,根據(jù)特定操作的需要而保證適當(dāng)程度的一致性。
最佳實(shí)踐 #4:用異步策略解耦程序
提高可伸縮性的另一項(xiàng)關(guān)鍵措施是積極地采取異步策略。如果組件A同步調(diào)用組件B,那么A和B就是緊密耦合的,而緊耦合的系統(tǒng)其可伸縮性特征是各部分 必須共同進(jìn)退——要伸縮A必須同時(shí)伸縮B。同步調(diào)用的組件在可用性方面也面臨著同樣的問(wèn)題。我們回到最基本的邏輯:如果A推出B,那么非B推出非A。也就 是說(shuō),若B不可用,則A也不可用。如果反過(guò)來(lái)A和B的聯(lián)系是異步的,不管是通過(guò)隊(duì)列、多播消息、批處理還是什么其他手段,它們就可以分別地伸縮。而且,此 時(shí)A和B的可用性特征是相互獨(dú)立的——即使B受困或者死掉,A仍然能夠繼續(xù)前進(jìn)。
整個(gè)基礎(chǔ)設(shè)施從上到下都應(yīng)該貫徹這項(xiàng)原則。即使在單個(gè)組件內(nèi)部也可通過(guò)SEDA(分階段的事件驅(qū)動(dòng)架構(gòu),Staged Event-Driven Architecture)等技術(shù)實(shí)現(xiàn)異步性,同時(shí)保持一個(gè)易于理解的編程模型。組件之間也遵守同樣的原則——盡可能避免同步帶來(lái)的耦合。在多數(shù)情況下, 兩個(gè)組件在任何事件中都不會(huì)有直接的業(yè)務(wù)聯(lián)系。在所有的層次,把過(guò)程分解為階段(stages or phases),然后將它們異步地連接起來(lái),這是伸縮的關(guān)鍵。
最佳實(shí)踐 #5:將過(guò)程轉(zhuǎn)變?yōu)楫惒降牧?/h3>
用異步的原則解耦程序,盡可能將過(guò)程變?yōu)楫惒降摹?duì)于要求快速響應(yīng)的系統(tǒng),這樣做可以從根本上減少請(qǐng)求者所經(jīng)歷的響應(yīng)延遲。對(duì)于網(wǎng)站或者交易系統(tǒng), 犧牲數(shù)據(jù)或執(zhí)行的延遲時(shí)間(完成全部工作的實(shí)踐)來(lái)?yè)Q取用戶的延遲時(shí)間(用戶得到響應(yīng)的時(shí)間)是值得的。活動(dòng)跟蹤、單據(jù)開(kāi)付、決算和報(bào)表等處理過(guò)程顯然都 應(yīng)該屬于后臺(tái)活動(dòng)。主要用例過(guò)程中常常有很多步驟可以進(jìn)一部分解成異步運(yùn)行。任何可以晚點(diǎn)再做的事情都應(yīng)該晚點(diǎn)再做。
還有一個(gè)同等重要的方面認(rèn)識(shí)到的人不多:異步性可以從根本上降低基礎(chǔ)設(shè)施的成本。同步地執(zhí)行操作迫使你必須按照負(fù)載的峰值來(lái)配備基礎(chǔ)設(shè)施——即使在 任務(wù)最重的那一天里任務(wù)最重的那一秒,設(shè)施也必須有能力立即完成處理。而將昂貴的處理過(guò)程轉(zhuǎn)變?yōu)楫惒降牧鳎A(chǔ)設(shè)施就不需要按照峰值來(lái)配備,只需要滿足平 均負(fù)載。而且也不需要立即處理所有的請(qǐng)求,異步隊(duì)列可以將處理任務(wù)分?jǐn)偟捷^長(zhǎng)的時(shí)間里,因而起到削峰的作用。系統(tǒng)的負(fù)載變化越大,曲線越多尖峰,就越能從 異步處理中得益。
最佳實(shí)踐 #6:虛擬化所有層次
虛擬化和抽象化無(wú)所不在,計(jì)算機(jī)科學(xué)里有一句老話:所有問(wèn)題都可以通過(guò)增加一個(gè)間接層次來(lái)解決。操作系統(tǒng)是對(duì)硬件的抽象,而許多現(xiàn)代語(yǔ)言所用的虛擬 機(jī)又是對(duì)操作系統(tǒng)的抽象。對(duì)象-關(guān)系映射層抽象了數(shù)據(jù)庫(kù)。負(fù)載均衡器和虛擬IP抽象了網(wǎng)絡(luò)終端。當(dāng)我們通過(guò)分割數(shù)據(jù)和程序來(lái)提高基礎(chǔ)設(shè)施的可伸縮性,為各 種分割增加額外的虛擬層次就成為重中之重。
在eBay,我們虛擬化了數(shù)據(jù)庫(kù)。應(yīng)用與邏輯數(shù)據(jù)庫(kù)交互,邏輯數(shù)據(jù)庫(kù)再按照配置映射到某個(gè)特定的物理機(jī)器和數(shù)據(jù)庫(kù)實(shí)例。應(yīng)用也抽象于執(zhí)行數(shù)據(jù)分割的 路由邏輯,路由邏輯會(huì)把特定的記錄(如用戶XYZ)分配到指定的分區(qū)。這兩類(lèi)抽象都是在我們自己開(kāi)發(fā)的O/R層上實(shí)現(xiàn)的。這樣虛擬化之后,我們的運(yùn)營(yíng)團(tuán)隊(duì) 可以按需要在物理主機(jī)群上重新分配邏輯主機(jī)——分離、合并、移動(dòng)——而完全不需要接觸應(yīng)用程序代碼。
搜索引擎同樣是虛擬化的。為了得到搜索結(jié)果,一個(gè)聚合器組件會(huì)在多個(gè)分區(qū)上執(zhí)行并行的查詢(xún),但這個(gè)高度分割的搜索網(wǎng)格在客戶看來(lái)只是單一的邏輯索引。
以上種種措施并不只是為了程序員的方便,運(yùn)營(yíng)上的靈活性也是一大動(dòng)機(jī)。硬件和軟件系統(tǒng)都會(huì)故障,請(qǐng)求需要重新路由。組件、機(jī)器、分區(qū)都會(huì)不時(shí)增減、 移動(dòng)。明智地運(yùn)用虛擬化,可使高層的設(shè)施對(duì)以上變化難得糊涂,你也就有了騰挪的余地。虛擬化使基礎(chǔ)設(shè)施的伸縮成為可能,因?yàn)樗股炜s變成可管理的。
最佳實(shí)踐 #7:適當(dāng)?shù)厥褂镁彺?/h3>
最后要適當(dāng)?shù)厥褂镁彺妗_@里給出的建議不一定普遍適用,因?yàn)榫彺媸欠窀咝O大地依賴(lài)于用例的細(xì)節(jié)。說(shuō)到底,要在存儲(chǔ)約束、對(duì)可用性的需求、對(duì)陳舊數(shù) 據(jù)的容忍程度等條件下最大化緩存的命中率,這才是一個(gè)高效的緩存系統(tǒng)的最終目標(biāo)。經(jīng)驗(yàn)證明,要平衡眾多因素是極其困難的,即使暫時(shí)達(dá)到目標(biāo),情況也極可能 隨著時(shí)間而改變。
最適合緩存的是很少改變、以讀為主的數(shù)據(jù)——比如元數(shù)據(jù)、配置信息和靜態(tài)數(shù)據(jù)。在eBay,我們積極地緩存這種類(lèi)型的數(shù)據(jù),并且結(jié)合使用“推”和“ 拉”兩種方法保持系統(tǒng)在一定程度上的更新同步。減少對(duì)相同數(shù)據(jù)的重復(fù)請(qǐng)求能達(dá)到非常顯著的效果。頻繁變更、讀寫(xiě)兼有的數(shù)據(jù)很難有效地緩存。在eBay,我 們大多有意識(shí)地回避這樣的難題。我們一直不對(duì)請(qǐng)求間短暫存在的會(huì)話數(shù)據(jù)作任何緩存。也不在應(yīng)用層緩存共享的業(yè)務(wù)對(duì)象,比如商品和用戶數(shù)據(jù)。我們有意地犧牲 緩存這些數(shù)據(jù)的潛在利益,換取可用性和正確性。在此必須指出,其他網(wǎng)站采取了不同的途徑,作了不同的取舍,也同樣取得了成功。
好東西也會(huì)過(guò)猶不及。為緩存分配的內(nèi)存越多,能用來(lái)服務(wù)單個(gè)請(qǐng)求的內(nèi)存就越少。應(yīng)用層常常有內(nèi)存不足的壓力,因此這是非常現(xiàn)實(shí)的權(quán)衡。更重要的一 點(diǎn),當(dāng)你開(kāi)始依賴(lài)于緩存,那么主要系統(tǒng)就只需要滿足緩存未命中時(shí)的處理要求,自然而然你就會(huì)想到可以削減主要系統(tǒng)。但當(dāng)你這樣做之后,系統(tǒng)就完全離不開(kāi)緩 存了。現(xiàn)在主要系統(tǒng)沒(méi)辦法直接應(yīng)付全部流量,也就是說(shuō)網(wǎng)站的可用性取決于緩存能否100%正常運(yùn)行——潛在的危局。哪怕是例行的操作,比如重新配置緩存資 源、把緩存移動(dòng)到別的機(jī)器、冷啟動(dòng)緩存服務(wù)器,都有可能引發(fā)嚴(yán)重的問(wèn)題。
做得好,緩存系統(tǒng)能讓可伸縮性的曲線向下彎曲,也就是比線性增長(zhǎng)還要好——后續(xù)請(qǐng)求從緩存中取數(shù)據(jù)比從主存儲(chǔ)取數(shù)據(jù)成本低廉。反過(guò)來(lái),緩存做得不好 會(huì)引入相當(dāng)多額外的經(jīng)常耗費(fèi),也會(huì)妨礙到可用性。我還沒(méi)見(jiàn)過(guò)哪個(gè)系統(tǒng)沒(méi)機(jī)會(huì)讓緩存大展拳腳的,關(guān)鍵是要根據(jù)具體情況找到適當(dāng)緩存策略。
總結(jié)
可伸縮性有時(shí)候被叫做“非功能性需求”,言下之意是它與功能無(wú)關(guān),也就比較不重要。這么說(shuō)簡(jiǎn)直錯(cuò)到了極點(diǎn)。我的觀點(diǎn)是,可伸縮性是功能的先決條件——優(yōu)先級(jí)為0的需求,比一切需求的優(yōu)先級(jí)都高。
希望以上最佳實(shí)踐能對(duì)你有用,希望能幫助你從新的角度審視你的系統(tǒng),無(wú)論其規(guī)模如何。