《架構真經:互聯網技術架構的設計》分而治之
分而治之
2004年,ServiceNow的創始團隊(最初稱為Glidesoft)構建了一個稱為“滑翔”(Glide)的通用工作流平台。在尋找可以應用該平台的行業時,團隊發現建立在信息技術基礎設施庫(ITIL)上的信息技術服務管理(ITSM)領域有機會可以通過PaaS服務(平台即服務)一展身手。在這個領域裏已經存在著競爭對手,像Remedy這樣的以本地軟件形式存在的潛在替代者,團隊認為像Salesforce公司這樣成功的客戶關係管理(CRM)解決方案對在線ITSM解決方案有很好的啟發意義。
2006年,為了能更好地代表其在ITSM解決方案領域為買家需求提供解決方案,公司更名為ServiceNow並在2007年開始盈利。與許多初創公司不同,ServiceNow在其創立初期就體會到了麵向擴展設計、實施和部署的價值。最初的解決方案設計包括故障隔離(參見第9章)和Z軸客戶拆分(本章將涉及)。這種故障隔離和客戶拆分允許公司通過擴展獲得早期的盈利能力,同時避免了很多早期SaaS和PaaS產品常見的噪音臨近問題。此外,該公司重視由多租戶模式所帶來的成本效益,盡管他們沿著客戶邊界創建故障隔離區,但是他們仍然為不需要完全隔離的較小客戶設計解決方案,使這些客戶可以在數據管理係統(DBMS)內利用多租戶係統。最後,該公司既重視外部視角提供的洞察力,也珍惜經驗豐富的員工所固有的價值。
ServiceNow與AKF合作夥伴通過多項合作來幫助他們思考未來的架構需求,最終聘請了AKF的一位創始合夥人湯姆·凱文來充實他們已經很有才華的技術團隊。“我們從產品發布之日起就擁有令人難以置信的可擴展性。”湯姆說,“沿著客戶邊界使用AKF的Z軸擴展去拆分,以確保我們能夠滿足早期的需求。但隨著客戶基數的增長,客戶平均規模的不斷增加而且超越了早期的小型使用者,我們開始服務更大的財富500強公司,工作量的特征也在改變,每個客戶的平均用戶數量急劇增加。所有這些導致每個客戶要執行更多的事務並且存儲更多的數據。此外,我們不斷擴展功能的範圍,每次發布都為客戶帶來更大的價值。這種功能擴展意味著對大型和小型客戶的係統都提出了更大的需求。最後,我們在MySQL的單個數據庫下運行多個模式或數據庫遇到了一個小問題。具體地說,在每個數據庫實例上有30個大容量租戶時,MySQL中的目錄功能(有時在技術上稱為信息模式)開始出現資源爭用現象。
湯姆·凱文在構建基於網絡的產品方麵積累了獨特的經驗,從如日中天的Gateway電腦到像eBay和PayPal這樣瘋狂的互聯網初創公司,同時他還有數個其他AKF客戶的經驗,這些積累使他特別適合幫助解決ServiceNow的挑戰。湯姆解釋道,“數據庫目錄問題很容易解決。對於非常大的客戶,我們直接為每個客戶分配一個專用的數據庫,從而減少隔離區中的故障突發半徑。中型客戶可能有30個以下的租戶,小型客戶可以繼續使用大量租戶共享的係統(更多內容參見第9章)。AKF擴展立方體既有助於抵消日益增長的客戶規模,也能滿足急劇膨脹的快速功能擴展和價值創造的需求。對於具有海量事務處理需求的大客戶,我們通過將數據複製到隻讀數據庫整合了X軸。報表通常隻讀不寫,屬於計算密集型和I/O密集型,利用隻讀數據庫的配置,我們可以在複製的數據庫上執行SQL語句,這對在線事務處理(OLTP)數據庫沒有任何影響。報告功能代表Y軸拆分(服務/功能或基於資源),我們通過Y軸的服務拆分,實現額外的基於服務的故障隔離、更大的數據緩存、更快的研發人員吞吐量。所有這些X、Y和Z軸拆分使我們在基礎設施和為任何類型的客戶購買類似的商業化係統中保持一致。需要更多的處理能力嗎?X軸將允許我們輕鬆而且快速地擴展以增加交易量。如果發現數據庫的數據操作開始變得遲緩,架構允許我們降低租戶的密度(Z軸),或者通過拆分(Y軸)把某些服務遷移到其他類似的硬件上。
本章討論通過克隆和複製的方法擴展數據庫和服務,分離功能或服務以及跨存儲和應用分拆相似的數據集係統。有了這三種方法,就能夠把幾乎任何係統或數據庫擴展到接近無限的水平。在這裏使用“接近”一詞略顯保守,但是在我們跨越數百家公司和數千個係統的經驗中,目前這些技術還沒有過失敗的先例。為了幫助大家更加直觀地了解這三種擴展的方法,我們采用了AKF擴展立方體來幫助討論,這個立方體是我們專門抽象出來用於解釋係統擴展方法的。圖2-1顯示了AKF擴展立方體,它是以我們合作夥伴的名字(AKF Partner)來命名的。
圖2-1 AKF擴展立方體
AKF擴展立方體的核心是三條簡單的軸,每條軸都有一套相關的可擴展性規則。立方體是表示從最小規模(立方體的左前下角)到接近無限可擴展性(立方體的右後上角)的擴展路徑的好方法。有時,去掉立方體受限的空間可以更容易看到這三條軸。圖2-2顯示了這三條軸及其配套的規則。本章將對三個規則進行詳細的討論。
並不是每個公司都需要AKF擴展立方體所有的能力(所有三條軸)。對於我們的許多客戶,X、Y或Z軸之一的拆分就可以滿足他們十多年的需要。但是當你取得像ServiceNow這樣的病毒式快速增長產品的成功時,就很可能需要本章將要討論的兩個或多個拆分。
圖2-2 三軸擴展
規則7——X軸擴展
內容: 通常叫水平擴展,通過複製服務或數據庫以分散事務處理帶來的負載。
場景:
數據庫讀寫比例很高(可以達到至少5∶1甚至更高——越高越好)。
事務增長超過數據增長的係統。
用法:
克隆服務的同時配置負載均衡器。
確保使用數據庫的代碼清楚讀和寫之間的區別。
原因: 以複製數據和功能為代價獲得事務的快速擴展。
要點: X軸拆分實施速度快,研發成本低,事務處理擴展效果好。然而,從運維角度來看,數據的運營成本比較高。
在擴展問題的解決方案中最困難的部分經常是數據庫或持久存儲層。這個問題的起源可以追溯到埃德加·康德在1970年發表的論文“大型共享數據銀行的數據關係模型”[1],關係型數據庫管理係統(RDBMS)概念的引入歸功於此。顧名思義,當今最流行的關係型數據庫(如Oracle、MySQL和SQL Server)允許數據元素之間存在著關係。這些關係既可以存在於表內,也可以存在於表與表之間。OLTP係統中的大多數表都可以規範為第三範式[2],每個表的所有記錄都有相同的字段,非關鍵字段必須完全依賴於主鍵,而不能隻依賴於主鍵的一部分,而且所有的非關鍵字段都必須直接依賴於主鍵。每個數據都和表中的其他數據相關聯。表與表之間也會存在著外鍵關係。因為ACID屬性,許多應用都依靠數據庫來支持和強製這些關係(見表2-1)。要求數據庫保持和強製這些關係意味著如果不投入大量的技術資源,這種數據庫將很難拆分。
表2-1 數據庫的ACID屬性
屬性 描 述
原子性 要麼完全執行事務的所有操作,要麼完全不執行任何操作
一致性 當事務開始執行操作時,數據庫將處於一致的狀態
隔離性 事務執行時,好像是數據庫中唯一在執行的操作
持久性 當事務執行完成後,它對數據庫的所有操作都是不可逆轉的
數據庫擴展的一個技巧是利用大多數應用對數據庫的讀操作遠遠多於寫操作。我們有一個處理訂票業務的客戶,平均每完成一個訂票交易需要400個查詢。每個訂票交易是數據庫的一個寫操作,而每個查詢是數據庫的一個讀操作,由此得出400∶1的讀寫比例。這種類型的係統可以通過複製隻讀數據的辦法實現擴展。
根據數據對時間的敏感性,我們有幾種不同的方法來分散隻讀數據。時間敏感性指的是與寫數據庫相比,隻讀數據庫的拷貝有多麼新鮮或者有多大比例完全準確。在你大聲要求數據必須是即時、實時、同步和完全準確之前,先喘口氣估算一下這種係統的成本。盡管完全同步的數據是理想的,但是它成本巨高。另外,它並不是總能帶給你所期待的回報。第5章中的規則19將會深度討論成本與結果對產品可擴展性的影響。
讓我們來重新審視那個客戶的訂票係統,其中每個寫操作伴隨著400次讀操作。因為他們是提供訂票服務的,所以你可能會認為顯示給客戶看的數據將是完全同步的。新手為此可能會準備400份數據拷貝,並與客戶訂票所需要的那份數據同步。如果隻因為與主交易數據庫不同步,存在著3秒、30秒或者90秒的時間差,這並不意味著錯誤,隻是有可能不準確。在任意一個時刻,我們那位客戶的係統中可能有10萬條數據,每天的訂票會涉及10%的數據。假設這些訂票活動均勻地分布在一天的時間範圍裏,那麼平均每秒鍾(0.86秒)會有一個訂票業務發生。上天對每個人都是公平的,某個客戶想要預訂的位置被其他客戶搶走了的概率是0.104%(假設數據每90秒同步一次)。當然,即使隻有0.1%的概率,客戶還是有可能選中已經被人搶走的位置,盡管這不太理想,但是仍然可以在客戶把選擇的座位放入購物車之前,以在應用中做最終檢查的方式來進行規避。誠然,每個應用的數據需求都是不一樣的,但是我們希望通過這個討論,理解如何拒絕所有數據都必須實時同步的想法。
討論完了數據的時間敏感性,讓我們來看看分散數據的幾種方法。一種方法是在數據庫的前麵加緩衝層。讓讀操作從對象緩存中取數據,而不是通過應用反複查詢數據庫。隻有當相關的數據被標注為過期時,才會查詢主要事務型數據庫,從而檢索數據和更新緩存。如果係統的可用性非常好,我們強烈推薦第一步采用開源的鍵值存儲作為對象緩存方式。
下一步是數據庫複製,這超越了應用層和數據庫層之間的對象緩存。大多數的關係型數據庫係統都能非常好地支持某種類型的複製。許多數據庫通過主從方式來實現複製,主數據庫是負責寫入的主要事務型數據庫,而從數據庫是主數據庫的隻讀副本。主數據庫不斷地跟蹤數據的更新、插入、刪除,並把記錄存入一個二進製日誌。從數據庫從主數據庫那裏獲得二進製日誌後,在從數據庫上重新執行這些命令。這是一個異步的過程,數據之間的時間延遲,取決於主數據庫插入和更新的數據量。以我們的客戶為例,每秒同步一次就可以應對每天10%的數據變化。這個數據的變化量足夠低,可以維持低延遲的從數據庫。這種實施經常包括配置在負載均衡器後麵的幾個從數據庫或者讀數據庫拷貝。應用向負載均衡器發出讀取請求,負載均衡以輪詢或者最少連接數的策略把請求傳遞給從數據庫。有些數據庫更進一步允許以主主的概念進行複製,其中任意數據庫都可以用來讀或寫。同步的進程有助於確保主數據庫之間數據的一致性與連貫性。雖然這項技術已經存在很久,我們更喜歡依賴於單個寫數據庫的解決方案,這有助於消除混淆和避免數據庫之間的邏輯爭用。
我們把這種拆分(複製)稱為X軸拆分,在圖2-1的AKF擴展立方體上標為X軸——水平複製。舉個熟悉網絡應用托管的許多研發人員都會明白的例子,我們在一個係統的網絡層或應用層,可以在負載均衡器的後麵運行多個具有相同代碼的服務器。請求進入負載均衡器後被分配到眾多網絡或應用服務器中的任何一個來完成後續的處理。這種應用層上的分布式模型的好處在於你可以在負載均衡器的後麵配置幾十、幾百甚至幾千台服務器,而所有這些機器都運行著相同的代碼同時處理著類似的請求。
X軸拆分不僅僅可以應用於是數據庫。通常可以很容易地克隆網絡服務器和應用服務器。這種克隆允許事務在係統之間均勻地分布以實現水平擴展。克隆應用或網絡服務相對來說比較容易,允許我們擴大事務的處理量。不幸的是,它並沒有真正地幫助我們,當試圖擴大數據規模時,我們必須要巧妙地控製才能夠處理這些事務。內存中的緩存數據與幾個獨特客戶或一些獨特功能相關聯可能就會出現瓶頸,結果使我們無法在不顯著影響客戶響應時間的情況下有效地擴展服務。為了解決這些內存約束,我們將著眼於擴展立方體的Y軸和Z軸。
規則8——Y軸拆分
內容: 有時也稱為服務或者資源擴展,本規則聚焦在沿著動詞(服務)或名詞(資源)的邊界拆分數據集、交易和技術團隊。
場景:
數據之間的關係不是那麼必要的大型數據集。
需要專業化拆分技術資源的大型複雜係統。
用法:
用動詞來拆分動作,用名詞拆分資源,或者兩者混用。
沿著動詞/名次定義的邊界拆分服務和數據。
原因: 不僅允許事務及其相關的大型數據集有效擴展,也支持團隊的有效擴展。
要點: Y軸或者麵向數據/服務的拆分允許事務和大型數據集的有效擴展,有益於故障隔離。Y軸拆分也有助於減少團隊之間的非必要溝通。
當你放下了關於以服務為導向(SOA)和以資源為導向(ROA)架構的狂熱辯論,轉而深入了解其基礎前提時,就會發現二者至少有一個共同點。這兩個概念都在迫使架構師和工程師思考他們在架構內的職責分工。宏觀地看,他們想通過動詞(服務)和名詞(資源)的概念做到這一點。規則8與擴展立方體的第二個軸不謀而合,采取了相同的方法。簡單地說,規則8是通過在網站內部拆分不同的功能和數據從而實現擴展的方法。規則8的簡單方法告訴我們用名詞或動詞或二者的組合來拆分產品。
首先,我們用動詞的方法來拆分。如果是相對簡單的電子商務網站,我們可以把網站分解為注冊、登錄、搜索、瀏覽、查看、添加到購物車和購買幾個必要的動詞。執行某種事務所需要的數據可以明顯不同於執行其他事務所需要的數據。例如,雖然注冊和登錄需要相同的數據,但各自也存在一些獨特的數據。注冊可能需要檢查用戶的首選ID是否已被其他人選用,而登錄可能不需要完整地了解每一個其他用戶的ID。注冊可能需要把大量的數據寫入永久性的數據存儲,但登錄可能是一個驗證用戶憑據的讀取密集型的應用。注冊會要求用戶存儲大量的包括信用卡號碼在內的個人可識別信息(PII),而登錄不太可能需要訪問所有這些信息。
當我們分析搜索和登錄這樣迥然不同的功能時,這種擴展方法的差異和由此而產生的機會變得甚至更加明顯。在登錄的情況下,我們主要關注的是驗證用戶的憑據和建立可能像會話這樣的一些東西(第10章中的規則40會詳細解釋為什麼我們要選擇“會話”而非“狀態”)。登錄與用戶有關,因此需要緩存和該用戶有關的交互數據。另一方麵,搜索關注的是尋找一個商品,最關心的是用戶的意圖(通常在一個搜索框裏輸入搜索字符串、查詢或搜索條件)和目錄列出的商品。拆分這些數據可以使我們在有限的內存中緩存更多的數據,因此緩存的命中率更高,係統可以更快地處理事務。在後端的持久化係統(如數據庫)中拆分這些數據,將使我們能夠在這些係統中投入更多的“內存”空間,更快地響應客戶的請求(應用服務器)。更好地利用係統資源促使這兩個係統有更快的響應速度。顯然,我們現在可以很容易地擴展這些係統而且較少受內存的限製。此外,通過和規則7(X軸擴展)一樣的方式拆分事務,Y軸增加了事務的可擴展性。
等一下!在推薦產品的情況下,如果我們想要合並用戶和產品信息,應該怎麼辦?請注意,我們剛剛添加了另一個動詞——推薦。這給了我們另一個進行數據和事務拆分的機會。我們或許會增加一個推薦服務,根據過去的購買行為進行異步評估有類似購買行為的用戶。這可能會在登錄功能或搜索功能中相應地準備數據,當用戶與係統交互時把這些數據顯示出來。或者它是一個來自於瀏覽器的獨立的同步調用,其結果顯示在推薦專區。
現在我們來看看如何使用名詞進行拆分。繼續以電子商務為例,可以確定某些最終將采取行動的資源(不是我們采取的行動)。假設我們的電子商務網站是由產品目錄、產品庫存、用戶賬戶信息和營銷信息等組成。使用名詞的方法,我們可能決定把數據按類拆分,然後定義一組有關操作的高層次的原語,如創建、讀取、更新和刪除。
Y軸拆分對數據集的擴展最有價值,但同時對代碼庫的擴展也很有用途。隨著服務或資源的拆分,我們所執行的操作和支持這些操作所必需的代碼也會被拆分。這意味著研發複雜係統的非常大的技術團隊可以變成這些係統子集的專家,而且再也不需要擔心所有其他的子係統或者成為所有其他子係統的專家。每個團隊可以在其服務中建立接口(如API)。如果每個團隊都“擁有”自己的代碼庫,我們就可以減少與布魯克斯定律相關的通信開銷。布魯克斯定律中的一條是研發者的生產力隨著團隊規模的增加而減少[3]。協調團隊努力的溝通成本是團隊規模的平方。因此,隨著團隊規模的不斷加大,研發人員的生產力不斷降低,因為研發人員把越來越多的時間花費在協調上。我們可以通過拆分團隊和行使所有權來降低這樣的開銷。當然正是因為拆分了服務,所以才可能相當容易地擴展事務。
規則9——Z軸拆分
內容: 經常根據客戶的獨特屬性(例如ID、姓名、地理位置等)進行拆分。
場景: 非常大而且類似的數據集,如龐大而且增長快速的客戶群,或者當響應時間對在地理上廣泛分布的客戶變得很重要的時候。
用法: 根據所知道的客戶的屬性(例如ID、名,地理位置或設備)對數據和服務進行拆分。
原因: 客戶的快速增長超過了其他形式的數據增長,或者在擴展時,需要在某些客戶群之間進行必要的故障隔離。
要點: Z軸拆分對擴大客戶基數的效果明顯,也用在其他那些無法使用Y軸拆分的大型數據集上。
規則9通常被稱為分片,是關於把一個數據集或服務分割成幾塊的。通常這些塊的大小相同,如果保持幾個大小不一的塊或碎片有價值,那麼也可以這麼做。保持塊的尺寸大小不一的一個理由是為了適應應用發布,這樣可以通過先把代碼發布到含有少量客戶的一個小塊來控製風險,當感覺已經發現並解決了主要的問題後,再發布給含有大量客戶的其他塊。這也是讓我們觀察和發現問題的一個重要方法——先把代碼發布到規模較小的塊上,如果所發布的產品沒有達到預期的效果(或者你想通過早期的發布了解用戶對某個功能的使用情況),可以在大規模發布之前迅速修正和優化產品。
分片經常通過我們已知的請求者或客戶的某些信息特征來完成。假設我們是一個考勤卡和提供考勤跟蹤服務的SaaS提供商。我們負責跟蹤客戶每名員工的時間和出勤情況,這些都是超過1000名員工的企業級客戶。我們或許能夠確定很容易地把係統按照公司分片,這意味著每個公司都可以擁有自己專用的網絡、應用和數據庫服務器。假設我們想利用多租戶模式來降低成本,同時還想讓多個小公司共享一個分片。擁有許多員工的大公司可能有專用硬件,而員工較少小的小公司可能聚集在大量分片上。我們已經借助員工和公司之間存在著關聯的事實建立了可擴展的分片係統,這將使我們可以通過采用更小、成本更低的硬件來實現水平擴展(我們將在下一章進一步討論規則10中提到的水平擴展)。
也許我們是手機廣告服務的提供商。在這種情況下,我們很有可能知道一些關於用戶的終端設備和運營商的信息。我們可以根據這兩種數據的顯著特點來進行數據的分片。如果我們是一個電子商務公司,可能會根據用戶的地理位置分片,以便更有效地利用配送中心的可用庫存,並使電子商務網站給予最快的響應時間。或者,基於近因、頻率和購買變現來創建數據分片,使我們能夠均勻地分散用戶。或者,如果所有其他的方法都失敗了,也許我們可以通過對在注冊時指定給用戶的ID取模或散列算法來產生分片。
為什麼我們會決定把類似的東西分片?對於超高速增長的公司,答案顯而易見。響應任何用戶請求的速度至少部分是由本地或遠程緩存的命中率決定的。這個速度告訴我們在任意給定的係統上可以處理多少個事務,也決定了我們需要處理多少個這樣的係統。在極端情況下,如果沒有數據的分片,事務處理可能會因為要為單個用戶的一個答案試圖遍曆大量的單片數據而變得異常痛苦和緩慢。當速度變得最為重要,並且響應任何請求所涉及的數據量都很大時,拆分不同的東西(規則8)和拆分類似的東西(規則9)就很有必要了。
拆分類似的東西顯然不僅局限於客戶,客戶隻是在我們的谘詢實踐中最常見和最容易按照規則9實施的。有時候,我們也建議拆分產品目錄。但是,當拆分不同的目錄,比如草坪座椅和尿布時,我們經常會將它們歸為拆分不同的東西。我們也曾幫助客戶通過取模或哈希算法拆分他們的係統事務ID。在這些情況下,我們真的對請求者一無所知,但我們知道單調遞增的數字。可以在記錄事務以留待未來參考的係統中進行這種類型的拆分,比如保留錯誤記錄以待未來評估的係統。
總結
我們認為使用三個簡單的規則就可以助你的擴展所向無敵。沿著X、Y和Z軸的擴展都各有優勢。從軟件設計和研發的角度考慮,通常X軸擴展的成本最低,Y和Z軸的擴展方案的設計有點兒挑戰性,但有更多的靈活性,可以進一步拆分服務、客戶甚至技術團隊。毋庸置疑,係統和平台的擴展還有更多方法,但是如果武裝了這三條規則,幾乎沒有可擴展性相關的問題會擋你的路。
通過克隆擴展——通過克隆或複製數據和服務使你很容易地擴展事務。
通過拆分不同的東西來擴展——通過名詞或動詞標識來拆分數據和服務。如果正確完成,可以有效地擴展事務和數據集。
通過拆分類似的東西來擴展——通常是客戶數據集。依據客戶屬性拆分客戶數據,形成獨特和隔離的數據分片或者泳道(參見第9章),以實現事務和數據的擴展。
注釋
1. Edgar F. Codd, “A Relational Model of Data for Large Shared Data Banks,” 1970, www.
seas.upenn.edu/~zives/03f/cis550/codd.pdf.
2. Wikipedia, “Third Normal Form,” https://en.wikipedia.org/wiki/Third_normal_form.
3. Wikipedia, “Brooks’ Law,” https://en.wikipedia.org/wiki/Brooks’_law.
最後更新:2017-05-19 15:32:18