架構(gòu)設(shè)計思想AKF拆分原則
當(dāng)我們需要分布式系統(tǒng)提供更強的性能時,該怎樣擴展系統(tǒng)呢?什么時候該加機器?什么時候該重構(gòu)代碼?擴容時,究竟該選擇哈希算法還是最小連接數(shù)算法,才能有效提升性能?在面對 Scalability 可伸縮性問題時,我們必須有一個系統(tǒng)的方法論,才能應(yīng)對日益復(fù)雜的分布式系統(tǒng)。這一講我將介紹 AKF 立方體理論,它定義了擴展系統(tǒng)的 3 個維度,我們可以綜合使用它們來優(yōu)化性能。
什么是AKF
AKF 立方體也叫做scala cube,它在《The Art of Scalability》一書中被首次提出,旨在提供一個系統(tǒng)化的擴展思路。AKF 把系統(tǒng)擴展分為以下三個維度:
- X 軸:直接水平復(fù)制應(yīng)用進程來擴展系統(tǒng)。也就是”加機器解決問題”,集群
- Y 軸:將功能拆分出來擴展系統(tǒng)。
- Z 軸:基于用戶信息擴展系統(tǒng)。也就是數(shù)據(jù)分區(qū)
如下圖所示:
圖片
如何基于 AKF X 軸擴展系統(tǒng)?
我們?nèi)粘R姷降母鞣N系統(tǒng)擴展方案,都可以歸結(jié)到 AKF 立方體的這三個維度上。而且,我們可以同時組合這 3 個方向上的擴展動作,使得系統(tǒng)可以近乎無限地提升性能。為了避免對 AKF 的介紹過于抽象,下面我用一個實際的例子,帶你看看這 3 個方向的擴展到底該如何應(yīng)用。假定我們開發(fā)一個博客平臺,用戶可以申請自己的博客帳號,并在其上發(fā)布文章。最初的系統(tǒng)考慮了 MVC 架構(gòu),將數(shù)據(jù)狀態(tài)及關(guān)系模型交給數(shù)據(jù)庫實現(xiàn),應(yīng)用進程通過 SQL 語言操作數(shù)據(jù)模型,經(jīng)由 HTTP 協(xié)議對瀏覽器客戶端提供服務(wù),如下圖所示:
圖片
在這個架構(gòu)中,處理業(yè)務(wù)的應(yīng)用進程屬于無狀態(tài)服務(wù),用戶數(shù)據(jù)全部放在了關(guān)系數(shù)據(jù)庫中。因此,當(dāng)我們在應(yīng)用進程前加 1 個負載均衡服務(wù)后,就可以通過部署更多的應(yīng)用進程,提供更大的吞吐量。而且,初期增加應(yīng)用進程,RPS 可以獲得線性增長,很實用,如下圖:
圖片
這就叫做沿 AKF X 軸擴展系統(tǒng)。這種擴展方式最大的優(yōu)點,就是開發(fā)成本近乎為零,而且實施起來速度快!在搭建好負載均衡后,只需要在新的物理機、虛擬機或者微服務(wù)上復(fù)制程序,就可以讓新進程分擔(dān)請求流量,而且不會影響事務(wù) Transaction 的處理。當(dāng)然,AKF X 軸擴展最大的問題是只能擴展無狀態(tài)服務(wù),當(dāng)有狀態(tài)的數(shù)據(jù)庫出現(xiàn)性能瓶頸時,X 軸是無能為力的。例如,當(dāng)用戶數(shù)據(jù)量持續(xù)增長,關(guān)系數(shù)據(jù)庫中的表就會達到百萬、千萬行數(shù)據(jù),SQL 語句會越來越慢,這時可以沿著 AKF Z 軸去分庫分表提升性能。又比如,當(dāng)請求用戶頻率越來越高,那么可以把單實例數(shù)據(jù)庫擴展為主備多實例,沿 Y 軸把讀寫功能分離提升性能。下面我們先來看 AKF Y 軸如何擴展系統(tǒng)。
如何基于 AKF Y 軸擴展系統(tǒng)?
當(dāng)數(shù)據(jù)庫的 CPU、網(wǎng)絡(luò)帶寬、內(nèi)存、磁盤 IO 等某個指標(biāo)率先達到上限后,系統(tǒng)的吞吐量就達到了瓶頸,此時沿著 AKF X 軸擴展系統(tǒng),是沒有辦法提升性能的。在現(xiàn)代經(jīng)濟中,更細分、更專業(yè)的產(chǎn)業(yè)化、供應(yīng)鏈分工,可以給社會帶來更高的效率,而 AKF Y 軸與之相似,當(dāng)遇到上述性能瓶頸后,拆分系統(tǒng)功能,使得各組件的職責(zé)、分工更細,也可以提升系統(tǒng)的效率。比如,當(dāng)我們將應(yīng)用進程對數(shù)據(jù)庫的讀寫操作拆分后,就可以擴展單機數(shù)據(jù)庫為主備分布式系統(tǒng),使得主庫支持讀寫兩種 SQL,而備庫只支持讀 SQL。這樣,主庫可以輕松地支持事務(wù)操作,且它將數(shù)據(jù)同步到備庫中也并不復(fù)雜,如下圖所示:
圖片
當(dāng)然,上圖中如果讀性能達到了瓶頸,我們可以繼續(xù)沿著 AKF X 軸,用復(fù)制的方式擴展多個備庫,提升讀 SQL 的性能,可見,AKF 多個軸完全可以搭配著協(xié)同使用。拆分功能是需要重構(gòu)代碼的,它的實施成本比沿 X 軸簡單復(fù)制擴展要高得多。在上圖中,通常關(guān)系數(shù)據(jù)庫的客戶端 SDK 已經(jīng)支持讀寫分離,所以實施成本由中間件承擔(dān)了,這對我們理解 Y 軸的實施代價意義不大,所以我們再來看從業(yè)務(wù)上拆分功能的例子。當(dāng)這個博客平臺訪問量越來越大時,一臺主庫是無法扛住所有寫流量的。因此,基于業(yè)務(wù)特性拆分功能,就是必須要做的工作。比如,把用戶的個人信息、身份驗證等功能拆分出一個子系統(tǒng),再把文章、留言發(fā)布等功能拆分到另一個子系統(tǒng),由無狀態(tài)的業(yè)務(wù)層代碼分開調(diào)用,并通過事務(wù)組合在一起,如下圖所示:
圖片
這樣,每個后端的子應(yīng)用更加聚焦于細分的功能,它的數(shù)據(jù)庫規(guī)模會變小,也更容易優(yōu)化性能。比如,針對用戶登錄功能,你可以再次基于 Y 軸將身份驗證功能拆分,用 Redis 等服務(wù)搭建一個基于 LRU 算法淘汰的緩存系統(tǒng),快速驗證用戶身份。然而,沿 Y 軸做功能拆分,實施成本非常高,需要重構(gòu)代碼并做大量測試工作,上線部署也很復(fù)雜。比如上例中要對數(shù)據(jù)模型做拆分(如同一個庫中的表拆分到多個庫中,或者表中的字段拆到多張表中),設(shè)計組件之間的 API 交互協(xié)議,重構(gòu)無狀態(tài)應(yīng)用進程中的代碼,為了完成升級還要做數(shù)據(jù)遷移,等等。解決數(shù)據(jù)增長引發(fā)的性能下降問題,除了成本較高的 AKF Y 軸擴展方式外,沿 Z 軸擴展系統(tǒng)也很有效,它的實施成本更低一些,下面我們具體看一下。
如何基于 AKF Z 軸擴展系統(tǒng)?
不同于站在服務(wù)角度擴展系統(tǒng)的 X 軸和 Y 軸,AKF Z 軸則從用戶維度拆分系統(tǒng),它不僅可以提升數(shù)據(jù)持續(xù)增長降低的性能,還能基于用戶的地理位置獲得額外收益。仍然以上面虛擬的博客平臺為例,當(dāng)注冊用戶數(shù)量上億后,無論你如何基于 Y 軸的功能去拆分表(即“垂直”地拆分表中的字段),都無法使得關(guān)系數(shù)據(jù)庫單個表的行數(shù)在千萬級以下,這樣表字段的 B 樹索引非常龐大,難以完全放在內(nèi)存中,最后大量的磁盤 IO 操作會拖慢 SQL 語句的執(zhí)行。這個時候,關(guān)系數(shù)據(jù)庫最常用的分庫分表操作就登場了,它正是 AKF 沿 Z 軸拆分系統(tǒng)的實踐。比如已經(jīng)含有上億行數(shù)據(jù)的 User 用戶信息表,可以分成 10 個庫,每個庫再分成 10 張表,利用固定的哈希函數(shù),就可以把每個用戶的數(shù)據(jù)映射到某個庫的某張表中。這樣,單張表的數(shù)據(jù)量就可以降低到 1 百萬行左右,如果每個庫部署在不同的服務(wù)器上(具體的部署方式視訪問吞吐量以及服務(wù)器的配置而定),它們處理的數(shù)據(jù)量減少了很多,卻可以獨占服務(wù)器的硬件資源,性能自然就有了提升。如下圖所示:
圖片
分庫分表是關(guān)系數(shù)據(jù)庫中解決數(shù)據(jù)增長壓力的最有效辦法,但分庫分表同時也導(dǎo)致跨表的查詢語句復(fù)雜許多,而跨庫的事務(wù)幾乎難以實現(xiàn),因此這種擴展的代價非常高。當(dāng)然,如果你使用的是類似 MySQL 這些成熟的關(guān)系數(shù)據(jù)庫,整個生態(tài)中會有廠商提供相應(yīng)的中間件層,使用它們可以降低 Z 軸擴展的代價。再比如,最開始我們采用 X 軸復(fù)制擴展的服務(wù),它們的負載均衡策略很簡單,只需要選擇負載最小的上游服務(wù)器即可,比如 RoundRobin 或者最小連接算法都可以達到目的。但若上游服務(wù)器通過 Y 軸擴展,開啟了緩存功能,那么考慮到緩存的命中率,就必須改用 Z 軸擴展的方式,基于用戶信息做哈希規(guī)則下的新路由,盡量將同一個用戶的請求命中相同的上游服務(wù)器,才能充分提高緩存命中率。Z 軸擴展還有一個好處,就是可以充分利用 IDC 與用戶間的網(wǎng)速差,選擇更快的 IDC 為用戶提供高性能服務(wù)。網(wǎng)絡(luò)是基于光速傳播的,當(dāng) IDC 跨城市、國家甚至大洲時,用戶訪問不同 IDC 的網(wǎng)速就會有很大差異。當(dāng)然,同一地域內(nèi)不同的網(wǎng)絡(luò)運營商之間,也會有很大的網(wǎng)速差。例如你在全球都有 IDC 或者公有云服務(wù)器時,就可以通過域名為當(dāng)?shù)赜脩艟徒峁┓?wù),這樣性能會高很多。事實上,CDN 技術(shù)就基于 IP 地址的位置信息,就近為用戶提供靜態(tài)資源的高速訪問。
下圖中,我使用了 2 種 Z 軸擴展系統(tǒng)的方式。首先是基于客戶端的地理位置,選擇不同的 IDC 就近提供服務(wù)。其次是將不同的用戶分組,比如免費用戶組與付費用戶組,這樣在業(yè)務(wù)上分離用戶群體后,還可以有針對性地提供不同水準(zhǔn)的服務(wù)。
圖片
沿 AKF Z 軸擴展系統(tǒng)可以解決數(shù)據(jù)增長帶來的性能瓶頸,也可以基于數(shù)據(jù)的空間位置提升系統(tǒng)性能,然而它的實施成本比較高,尤其是在系統(tǒng)宕機、擴容時,一旦路由規(guī)則發(fā)生變化,會帶來很大的數(shù)據(jù)遷移成本,[第 24 講] 我將要介紹的一致性哈希算法,其實就是用來解決這一問題的。
小結(jié)
這一講我們介紹了如何基于 AKF 立方體的 X、Y、Z 三個軸擴展系統(tǒng)提升性能。X 軸擴展系統(tǒng)時實施成本最低,只需要將程序復(fù)制到不同的服務(wù)器上運行,再用下游的負載均衡分配流量即可。X 軸只能應(yīng)用在無狀態(tài)進程上,故無法解決數(shù)據(jù)增長引入的性能瓶頸。Y 軸擴展系統(tǒng)時實施成本最高,通常涉及到部分代碼的重構(gòu),但它通過拆分功能,使系統(tǒng)中的組件分工更細,因此可以解決數(shù)據(jù)增長帶來的性能壓力,也可以提升系統(tǒng)的總體效率。比如關(guān)系數(shù)據(jù)庫的讀寫分離、表字段的垂直拆分,或者引入緩存,都屬于沿 Y 軸擴展系統(tǒng)。Z 軸擴展系統(tǒng)時實施成本也比較高,但它基于用戶信息拆分數(shù)據(jù)后,可以在解決數(shù)據(jù)增長問題的同時,基于地理位置就近提供服務(wù),進而大幅度降低請求的時延,比如常見的 CDN 就是這么提升用戶體驗的。但 Z 軸擴展系統(tǒng)后,一旦發(fā)生路由規(guī)則的變動導(dǎo)致數(shù)據(jù)遷移時,運維成本就會比較高。當(dāng)然,X、Y、Z 軸的擴展并不是孤立的,我們可以同時應(yīng)用這 3 個維度擴展系統(tǒng)。分布式系統(tǒng)非常復(fù)雜,AKF 給我們提供了一種自上而下的方法論,讓我們能夠針對不同場景下的性能瓶頸,以最低的成本提升性能。