高併發整體可用性:大規模集羣下的分片管理策略

大規模系統的分片部署是一個難點,既要考慮容災和故障轉移,又要考慮負載均衡和資源利用率。本文就從服務狀態、故障轉移、負載及資源利用率等幾個方面來闡述下他們的關係,並帶大家一起看下,facebook 面對這種挑戰是怎麼做系統架構的~

1 有狀態 & 無狀態的服務部署

應用服務,根據其類型一般可以分爲兩種:無狀態服務有狀態服務

無狀態的服務,擴展起來其實比較容易,用流量路由等負載均衡方式即可實現;

但是有狀態服務不太容易,讓所有服務器一直能夠持有全部數據時不現實的。

-- 散列是一種方案 --

如一致性散列策略,將數據散列部署。但是,一致性散列會存在一些不可避免的問題,主要有數據傾斜、數據漂移等。雖然我們可以通過將某節點的一部分數據移到其他節點來解決,但這需要非常細粒度的負載統計標尺來進行發現和衡量。

另外,由於一致性散列對多數據中心的支持不太友好,比如,希望讓某些區域的用戶走特定數據中心,以降低延遲的話,用該策略不好實現。

-- 分片是另一種方案 --

其目的是支持業務增長、應對系統的高併發及吞吐量。並且加載和使用更加靈活。

數以萬計、億計的數據分散存儲在多個數據庫實例中,則每個實例都叫一個分片。另外,爲了容錯,每個分片都可以有多個副本,而每個副本根據不同的一致性要求可以分爲主副本和從副本。

然後,分片又通過多種策略規範,顯示計算出到服務器的映射,以向用戶提供完整的服務。這些策略規範包括:不同用戶 ID 選擇不同的服務分區、不同地理位置的請求分散到較近的數據中心等等。

從這個要求來看,分片的方式要比散列更靈活,更適用大型服務部署。

2 故障是一種常態

在分佈式環境下,我們對故障需要有清晰的認知 -- 故障的發生不應該被當成是小概率事件,而應該被當成一種常態。

這也是系統設計應該遵守的一個前提條件。這樣系統纔有可能更加穩固。

所以,服務的容錯能力、從故障中恢復的能力,是實現服務高可用的關鍵。

那可以有哪些措施呢?

-- 複製 --

數據、服務的冗餘部署,是提高容錯能力的常用手段。比如服務主備和數據庫主從。

但是有些情況下,這種方式是可以打商量的。如果單個容錯域的故障會導致所有冗餘副本宕機,那複製還有啥作用?

-- 自動檢測 & 故障轉移 --

想要高可用,故障的發現和自動檢測機制是前提。然後纔是故障轉移。

故障轉移的快慢,決定了程序的可用性高低。那麼,所有的故障都必須立馬進行轉移才能達到最好的效果麼?

也不竟然。

如果新副本的構建成本非常大呢?比如要加載龐大的數據量資源等等,指不定還沒構建完,原故障機器就恢復了說不定。

-- 故障轉移限流 --

一個服務掛了不是最可怕的,最可怕的是它能把全鏈路都帶崩。就比如上一篇文章中寫的《Zookeeper 引發的全鏈路崩潰》一樣,級聯問題,絕對不容小覷!

因此,在進行故障轉移時,要給予這個問題足夠的重視,以免引發正常服務在脈衝流量下崩盤。

3 資源是寶貴的,不浪費才最好

就算是阿里的財大氣粗,每個季度也會有運維同學和業務同學,因爲 1% 的 CPU 利用率,在服務容器數量分配上來回博弈好多次。

業務同學當然是想機器越多越好,因爲機器越多,單節點的故障幾率就可以小些;

而運維同學的任務則是儘可能提高資源利用率。畢竟有那麼多要買 718、911 的。。。不都得錢麼~

-- 負載均衡 --

負載均衡是指在應用服務器之間持續均勻地分佈分片及其工作負載的過程。它可以有效地利用資源,避免熱點。

-- 彈性擴展 --

很多時候,我們的很多應用都是爲用戶提供服務的。而用戶的行爲隨着作息呈現一定的規律性,如白天的訪問量大,晚上的訪問量小。

因此,彈性計算其實是在不影響可靠性的前提下提高資源效率的一種解決方案,即根據負載變化動態調整資源分配。

4Facebook 怎麼平衡上述訴求 [1]

我們平常見到,其實不同的團隊有不同的分片管理策略。比如,本地生活有一套自己的外賣服務分片的管理容災方案、大文娛也有一套自己視頻服務的分片管理。

facebook 早期也是如此。不同團隊間各有各的方案,但是這些方案都更偏重於解決故障轉移,保障系統可用性,對負載均衡考慮較少。

這樣的方式,其實對集團資源利用率、操作性上有些不適合。基於這樣的考慮,facebook 設計了一個通用的分片管理平臺 Shard Manager,簡稱 SM 。。。

距今,已經有成百上千的應用程序被構建或遷移到分片管理器上,在幾十萬的服務器上構建出總計超過千萬的分片副本。。。

那麼,我們來一起看下,他們是怎麼做的。

1、基礎架構分層設計

facebook 基礎設施架構

Host management: 即主機管理,使用資源配額系統,管理所有物理服務器,並給各團隊分配容量。

Container management: 每個團隊從下層獲取到容量後,以容器爲單位將其分配給各個應用程序。

Shard management: 在下層提供的容器內爲分片應用程序分配分片。

Sharded applications: 在每個分片中,應用程序分配並運行相關的工作負載。

Products: 上層應用程序。

2、Shard Management 整體架構

應用程序實現分片狀態轉換接口(該接口由 SM library 定義,由應用程序實現),並根據調度程序的調用進行狀態轉換。

應用程序向 ZooKeeper 上報服務器成員資格和活動狀態。由 Shard Manager Scheduler 收集應用程序監測的自身動態負載信息。

Shard Manager Scheduler 是協調分片轉換和移動的中央服務。它收集應用程序狀態;監控服務器加入、服務器故障和負載變化等狀態變化;通過對應用程序服務器的 RPC 調用來驅動分片狀態轉換,從而實現分片分配的調整。

Shard Manager Scheduler 將分片分配的公共視圖發佈到服務發現系統 (該系統保障了自己的高可用和可擴展);

服務發現系統將信息傳遞到應用程序的客戶端用於計算請求路由。

那麼,Shard Manager Scheduler 掛了怎麼辦呢?

一是,其內部可以進行分片擴展,來保證其高可用;

二是,由上述設計可知,中央處理程序不在客戶端調用的關鍵路徑上,即使掛了,應用程序還是可以以現有的分片來執行運轉,業務不會受中央處理器宕機的影響。

2、標準化分片管理

爲了實現上述架構,facebook 定義了幾個原語:

//分片加載
status add_shard(shard_id)
//分片刪除
status drop_shard(shard_id)
//主從切換
status change_role(shard_id, primary <-> secondary)
//驗證和變更副本成員關係
status update_membership(shard_id, [m1, m2, ...])
//客戶端路由計算和直連調用
rpc_client create_rpc_client(app_name, shard_id)
rpc_client.execute(args)

根據上述原語,通過組合,即可以合成高級分片移動協議:

如,我們希望將將分片 A,從當前負載較高的服務器 A,移動到負載較低的服務器 B:

   Status status= A.drop_share(xx);
   if(status == success){
      B.add_share(xx)
   }

不僅是普通服務,對於主從切換、paxos 協議的服務管理也可以用上述原語滿足,有興趣的同學可以自己試試組合一下,這個對我們工作中的系統設計時非常有幫助的。

那麼,針對文章前半部分涉及到的一些分片管理的難點和問題,facebook 在該架構下是怎麼應對的呢?

對於容錯中的複製,Shard Manager 允許在每個分片上配置複製因子,以實現合理的複製策略,如果不需要複製,則可以通過複製因子來控制;

對於故障轉移,Shard Manager 通過配置故障轉移延遲策略,來權衡新副本的構建成本和服務的不可用時間;

對於故障節流,Shard Manager 支持限制分片故障轉移速度,來保護其餘健康服務器不會被沖垮;

對於負載均衡,Shard Manager 支持根據硬件規格定製負載因子;通過定期收集應用程序每個副本的負載,來實現各實例間的平衡;並支持多資源平衡,確保短板資源可用。

對於彈性,Shard Manager 支持分片縮放和擴展,針對不同流量實行不同的擴展速率。

參考資料

[1]

fb engineering: "使用 ShardManager 擴展服務"

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/8sAy0MohrnD5P73cCcmhvw