AKF 擴展立方體
AKF 擴展立方體(AKF Scale Cube)是一個描述從單體應用到可擴展架構的模型,見 (https://akfpartners.com/growth-blog/scale-cube)[https://akfpartners.com/growth-blog/scale-cube]。這個模型在 Scalability Rules (架構真經)一書中進行了描述。
AKF 擴展立方體把單體應用擴展到可擴展架構的方式的過程划分為3個正交維度:
x 軸:水平復制
y 軸:業務拆分
z 軸:數據分片
這 3 個維度描述了從單體到可擴展架構的方法,在實踐中有比較大的作用,x、y、z組合而成的向量大小正是系統的擴展性。
x 軸擴展
x 軸擴展描述了比較簡單的擴展方式,又稱水平擴展,通過復制節點,實現多個節點同時提供服務,從而大大提高系統的總體容量、解決單點問題等。
水平擴展是比較理想的擴展方式,一般來說開發難度不大,但增加部署復雜度。這種擴展最為典型的例子是數據庫的主從復制和讀寫分離。
典型的 web 應用讀寫比例非常懸殊,通常是 10:1、100:1 甚至是 1000:1 ,因此需要更多的資源來支持讀操作。同時,從數據的時效性上看,大部分數據並不需要非常高的實時性,3、5s 的少量的延遲往往是可被接受的。從數據的訪問頻率上看,基本上數據會遵守局部性法則(8020 法則),即大部分訪問都是訪問熱點數據。從這些業務上的特點出發,往往有 2 類的手段來進行數據層面上的優化:加緩存、復制節點讀寫分離。
加緩存是非常有效的優化手段。數據庫本身也帶有緩存,像 mysql 的 innodb_buffer_pool ,用提高響應速度,除此之外,還可以:
本地內存、文件緩存,如 ehcache
中央緩存,如 redis / memcached
既然有緩存,就有緩存的有效性、有效時間,就會有緩存相關的問題,包括:
緩存穿透,用不存在的 id 訪問系統,達成攻擊
緩存擊穿,特別的熱的某個數據失效,所有的流量瞬間到了后端
緩存雪崩,某個時間緩存批量失效,所有流量到后端
讀寫分離通過加強基礎設施的容量進行擴展。大部分的數據庫,mysql、oracle、sqlserver 會支持某種主從備份(甚至是雙主),這使得在基礎設施層面能實施非常有效的擴展。以 mysql 為例,可把事務型的sql 放到主庫執行,簡單查詢型的 sql 分布到多個從庫讀取數據,這一下子就把原來的負載分配到了幾個數據庫實例來承擔,容量放大 n 倍。這種方式需要實現上做一些改動,在數據庫訪問層上需要支持某種形式的 sql 路由,而這些在成熟的 orm 、sqlmap 框架都已經提供,可看對應的 mybaits、hibernate 等文檔。
當然 x 軸擴展不單只是加緩存或讀寫分離,像 dns 負載均衡、多接入服務器等都是 x 軸擴展方法。
y 軸擴展
y 軸擴展從業務層面來考慮擴展方式,因此又稱為業務擴展或資源擴展,這點基本上就等同於微服務化。系統從業務層面拆分為多個子系統,各子系統由單獨的團隊負責整個生命周期的維護,單獨部署運行,子系統間具備故障隔離的能力。
拆分的方式有水平的拆分和垂直拆分。垂直拆分是把接入、前端、安全、監控等不同技術層面的組件進行拆分,讓各組件更加專注於自己的工作,體現在結構圖上就是在垂直方向上進行了划分;水平拆分是按照不同的業務線,各業務線拆分開來,結構圖上是按照水平方向上進行划分。y 軸的擴展更多是水平拆分這種方式。
每個子系統擁有自己的運行資源,可以有針對性地采用更加激進的優化手段。像與文檔相關的子系統,可以減少使用關系式數據庫,轉而采用對分布式支持更好的文檔型數據庫,進行熱點預測,對熱點數據提前進行預處理等等。
拆分子系統后,通常數據庫要進行拆分(分庫)。分庫帶來了事務處理能力的直接提升,對於高事務型應用,可在數據庫層面用一些比較高的配置,非事務型應用使用低點的配置,使得整體的運行成本更加合理,讓好鋼用到刀刃處。
需要注意的是,分庫將無可避免地導致需要使用分布式事務。典型的分布式事務模式,目前常見的有數據庫自帶的 XA,java 中的 JTA,TCC,支持長時間業務的 Saga 等。這里給一個警告,盡可能避免分布式事務,目前並沒有 100% 可靠而又高效的分布式事務解決方案。因此在進行 y 軸擴展拆分時,一個很有效的方法是事務應該盡量集中在子系統中,只有必須無可避免的時候才采用分布式事務,同時對分布式事務必須使用先寫 undo_log 日志,以便在必要時進行自動甚至人工恢復。
拆分后同時帶來了跨業務集成的問題。在《微服務設計》書中提到有兩種風格的集成方式:請求響應、事件。請求響應有 RPC、REST方式,事件式可用各種消息隊列(kafka、rabbitmq、activemq)等進行事件消息傳遞(openstack 里面的集成方式比較有意思,采用事件隊列來進行 RPC)。
y 軸擴展需要比 x 軸擴展花費更多的精力,從開發、運維甚至組織架構都需要做出相應的調整。大家熟知的康威定律,系統的架構反映了組織的溝通結構,以技術至上的角度來實現 y 軸擴展,到頭來只會讓業務層面無法適應新系統而導致效率低下。
z 軸擴展
z 軸擴展是基於數據集本身的特性來進行擴展的方式,也即數據分片,在實踐中,就是分表了。
對於關系型數據庫而言,拆分數據意味着需要進行反模式的設計,依賴於數據庫自身機制的完整性約束(主鍵、外鍵、域、唯一性)的設計也需要拆開。
數據的拆分需要對業務領域有較高的認識才好處理,拆分的代價相當高,需要引入一系列支持拆分的底層框架,像 sharding-jdbc、mycat 等,在數據層面需要配置相應的分片邏輯。正確的拆分對提高系統的容量有很大的幫助,失敗的拆分可能會造成熱點集中,得不償失。
數據拆分可以和業務擴展共同使用,讓業務跑在部分的數據上,實現故障隔離。在升級時,先對小范圍進行升級,在出錯時小范圍的數據更加容易修復,待驗證之后再進行整個系統層面的升級,這種控制范圍的灰度發布在實踐中非常實用,在各種雲平台、 k8s 上都可以實現。
最后...
所有的擴展都會帶了復雜度、成本提升的代價,要視業務的真正需要來進行擴展設計,避免過度的擴展。
通常來說,在設計階段要設計 10 到 100 倍左右的邏輯容量,因為在后期修改設計會帶來很高的難度,所以要提前設計好冗余容量。在實現階段,通常要按照 5 到 10 倍左右的容量進行實現,實現階段需要付出一定的成本,沒有必要為非常長遠的將來實現,可以隨業務的進展而逐步實現設計時最大容量。部署階段,以 2 到 5 倍的容量進行部署,可以應付一段時間內的容量增長,但如果是預期的增長非常大,就要采用更加靈活的雲部署手段,並為突發峰值做好預案准備。
