Categories
程式開發

Facebook如何用分片來擴展服務?


多年來,隨著我們在規模和功能上的擴展,Facebook已經從單一基本的web服務器架構演進成一個包含數千個在後台工作的服務的複雜架構。擴展Facebook產品所需的各種後端服務並不是一件小事。而且,我們發現我們許多團隊都在構建自己的具有重疊功能的定制化分片解決方案。為解決這個問題,我們將Shard Manager構建為一個通用平台,它能促進可靠的分片應用程序的高效開發和運維。

Facebook如何用分片來擴展服務? 1

事實上,用分片來擴展服務的概念並不新鮮。然而,據悉,我們是業界唯一在我們的規模內得到廣泛採用的通用分片平台。 Shard Manager管理著成百上千萬的分片,這些分片託管在成百上千個服務器上,覆蓋數百個線上應用程序。

分片

在最基本的形式中,人們熟悉分片是它作為一種擴展服務的方法來支持高吞吐量。下圖展示了一個典型的web技術棧的擴展。其中,web層通常是無狀態的,並且易於擴展。由於任何服務器都可以處理任何請求,因此可以使用很多種流量路由策略,例如循環策略或隨機策略。

Facebook如何用分片來擴展服務? 2

Facebook的應用程序棧

另一方面,由於數據庫部分是有狀態的,因此對其進行擴展不容易。我們需要使用一種方案來確定地在服務器之間傳播數據。像hash(data_key) % num_servers這樣的簡單哈希方案可以傳播數據,但是新增服務器時會存在數據混亂的問題。一致性哈希方案通過將一小部分數據從現有服務器重新分發到新服務器來解決這個問題。

然而,這個方案要求應用程序具有細粒度的秘鑰,以便有效地進行統計負載均衡。一致性哈希支持基於約束的分配(例如,歐盟用戶的數據應該存儲在歐洲數據中心以降低延遲)的能力也因其這一天然屬性而受到限制。因此,只有某些應用程序(如分佈式緩存)才採用這種方案。

一種備選方案是顯式地將數據劃分到分配在各服務器的分片。數十億用戶的數據存儲在多個數據庫實例中,每個實例都可以看作一個分片。為提高容錯性,每個數據庫分片可以有多個拷貝(也稱為副本),每一個副本能根據一致性要求扮演不同的角色(例如,主副本或次副本)。

分片到服務器的分配,是針對協調各種約束的能力(例如局部性偏好)進行顯式計算過的,而哈希解決方案沒法支持這些約束。我們發現,分片方案比哈希方案更靈活,適合更廣泛的分佈式應用程序的需要。

採用這種分片方案的應用程序通常需要一定的分片管理能力,才能可靠地進行大規模運維。最基本的能力是故障轉移能力。在發生硬件或軟件故障時,系統可以將客戶端流量從故障的服務器轉移出去,甚至可能需要在正常服務器上重建受影響的副本。在大型數據中心,通常有計劃的服務器停機時間來執行硬件或軟件維護。分片管理系統需要確保每個分片都有足夠的健康副本,這通過主動將副本從有必要關閉的服務器轉移出去來實現。

另外,可能不均衡和不斷變化的分片負載需要負載均衡,這意味著每個服務器託管的分片必須動態調整,以實現統一的資源利用,提高整體的資源效率和服務可靠性。最後,客戶端流量的波動需要分片擴展,系統根據每個分片動態調整副本因子,以確保其平均每個副本的負載保持最佳。

我們發現,Facebook不同服務的團隊已經在構建自己的定制化解決方案,其完整性程度各不相同。能處理故障轉移的服務比較常見,但很少有負載均衡能力。這導致可靠性不能達到最優效果和較高的運維開銷。這就是為什麼我們要將Shard Manager設計為通用的分片管理平台。

使用Shard Manager作為平台分片

多年來,已有數百個分片應用程序被構建或遷移到Shard Manager。經過長期的快速發展,有上千萬分片副本分配在成百上千萬台服務器上,如下圖所示。

這些應用程序協助各種面向用戶的產品的順利運行,包括Facebook app、Messenger、WhatsApp和Instagram。

Facebook如何用分片來擴展服務? 3

應用服務器總數量的增長

除了数量庞大的应用程序外,它们的用例在复杂度和规模上都有显著不同,从简单的拥有几十台服务器的柜台服务,到拥有数万台服务器的复杂的基于Paxos的全局存储服务。

下圖展示了代表性應用程序的範圍,用字體大小表明它們的規模。

Facebook如何用分片來擴展服務? 4

Shard Manager上的代表性應用程序

各種因素促成了廣泛採納。首先,與Shard Manager集成意味著簡單地實現一個由add_sharddrop_shard原始命令組成的小接口。其次,每個應用程序都可以通過基於intent的規範來聲明其可靠性和效率要求。第三,通用約束優化求解器的應用讓Shard Manager能提供多功能的負載均衡功能,並輕鬆添加對新均衡策略的支持。

最後值得一提的是,通過完全集成到整個基礎設施生態系統中,包括容量和容器管理,Shard Manager不僅支持分片應用程序的高效開發,而且還支持安全運維,這是沒有相似平台提供的端到端解決方案。 Shard Manager比類似平台(例如 阿帕奇螺旋)支持更複雜的用例,包括基於Paxos的存儲系統用例。

Shard Manager應用程序的類型

我們從Shard Manager上的應用程序中抽取出一些共性,並將它們分為以下三類:只有主副本、只有次副本、兼具主副本和次副本。

只有主副本:

每個分片只有單個副本,稱為主副本。這種類型的應用程序通常將狀態存儲在外部系統中,例如存儲在數據庫和數據倉庫中。一個常見的範例是,每個分片代表一個工人,獲取指定的數據,處理他們,選擇性的響應客戶端請求,並通過可選的優化手段(例如批處理)來寫回結果。

流處理是一個真實例子,從一個輸入流中處理數據並將結果寫入到一個輸出流中。 Shard Manager提供了一個“最多一個主副本”的保證來幫助避免由於數據重複處理導致的數據不一致,就像傳統的ZooKeeper基於鎖的方法一樣。

只有次副本:

每個分片都有多個角色相同的副本,稱為次副本。多個副本的冗餘性提供了更好的容錯性。

此外,還能根據工作負載調整副本因子:熱門分片可以有更多副本來分散負載。通常,這種類型的應用程序是只讀的,沒有很強的一致性要求。它們從外部存儲系統獲取數據,有選擇地處理數據,本地緩存結果,並根據本地數據響應查詢。

一個實際的例子是機器學習推理系統,它從遠程存儲器下載訓練好的模型並響應推理請求。

兼具主副本和次副本:

每個分片都有兩種角色的多個副本——主副本和次副本。這些類型的應用程序通常是對數據一致性和持久性有嚴格要求的存儲系統,其中主副本接受寫入請求並驅動所有副本之間的複制,次副本提供了冗餘性並可以選擇性地響應讀取請求來減少主副本上的負載。其中一個例子是 ZippyDB,這是一個基於Paxos副本的全局鍵值存儲系統。

我們發現,以上三中類型能代表大部分Facebook的分片應用程序。截至2020年8月的百分比分佈,如下圖所示:67%的應用程序是只有主副本的,這是由於架構的簡單性以及與傳統ZooKeeper基於鎖的解決方案在概念上的相似性。

然而,就服務器數量而言,只有主副本的為17%,這意味著只有主副本的應用程序平均比其它兩種類型的應用程序小。

Facebook如何用分片來擴展服務? 5

截至2020年8月的應用程序數量和服務器數量百分比分佈

使用Shard Manager構建應用程序

在應用程序所有人決定如何將他們的工作負載或數據分割到分片中以及哪種應用程序類型適合他們的需求後,有三個簡單直接的標準化步驟可以在Shard Manager上構建一個分片應用程序,無論是哪種用例。

  1. 應用程序連接Shard Manager庫,然後實現分片狀態轉換接口,同時插入他們的業務邏輯。
  2. 應用程序所有者提供基於intent的規範來配置約束。 Shard Manager提供了4個開箱即用的主要功能:容錯、負載均衡、分片擴展和安全運維。
  3. 應用程序客戶端使用一個通用的路由庫來路由分片請求。

分片狀態轉換接口

我們的分片狀態轉換接口由一組如下所示的短小精煉的原始命令組成,通過這些原始命令插入特定的應用程序邏輯:

status add_shard(shard_id)
status drop_shard(shard_id)

add_shard調用指示一個服務器加載由傳入的分片ID標識的分片。返回值標識轉換的狀態,例如分片加載是否在進行中或者運行出錯。相反地​​,drop_shard調用指示一個服務器拋棄某個分片並停止響應客戶端請求。

這個接口給予應用程序完全的自由,來將分片映射到它們特定域的數據。對於存儲服務,add_shard調用通常觸發結點副本的數據傳輸;對於一個機器學習推理平台,add_shard調用觸發模型從遠程存儲加載到本地主機。

基於以上原始命令,Shard Manager構建了一個高級的分片轉移協議,如下圖所示。 Shard Manager決定將一個分片從高負載的服務器A轉移到一個負載相對較輕的服務器B,從而實現負載均衡。首先,Shard Manager向服務器A發出一個drop_shard調用並等待它成功完成。然後,它向服務器B發出一個add_shard調用。這個協議提供了最多一個主副本的保證。

Facebook如何用分片來擴展服務? 6

分片跨服務器轉移

以上兩個基本原始命令是典型應用程序變得切片化並實現伸縮擴展所需的全部內容。對於復雜的應用程序,Shard Manager支持更強大的接口,下面將詳細介紹這些接口。

在上述協議中,處於轉移過程中的分片的客戶端在分片不在任何服務器上的那段時間內會經歷短暫的不可用,而這對於面向用戶的應用程序來說,這是不可接受的。因此,我們開發了一個更完善的協議,支持無縫的所有權移交並最大限度地減少分片的停機時間。

對於兼具主副本和次副本的應用程序,提供了兩種傳統的原始命令,如下所示:

status change_role(shard_id, primary  secondary)
status update_membership(shard_id, [m1, m2, ...])
  • change_role用來轉換副本的主次角色。
  • update_membership指示一個分片的主副本驗證和執行副本成員關係的變更,這對於基於Paxos的應用程序最大化數據正確性非常重要。

以上接口是我們深入分析和處理分片應用程序經驗的結果。結果證明它們足夠通用,可以支持大部分應用程序。

各種功能基於intent的規範

容錯能力

對於分佈式系統,故障是常事而非異常,而知道如何準備和從故障中恢復,這對於實現高可用性是至關重要的。

副本:通過副本實現冗餘,這是提升容錯能力的一種常見策略。 Shard Manager支持在每個分片基礎上配置副本因子。如果單個容錯域的故障可以關閉所有冗余副本,那麼副本的好處是微乎其微的。 Shard Manager支持跨可配置的容錯域(例如,用於區域應用程序的數據中心建築和用於全球應用程序的區域)傳播副本。

自動故障檢測和分片故障轉移:Shard Manager能夠自動化檢測服務器故障和網絡隔斷。在檢測到一個故障後,立即構建替代副本並不總是理想的。 Shard Manager通過配置故障檢測延遲和分片故障轉移延遲,讓應用程序能在構建新副本的開銷與可接受的不可用性之間做出適當的權衡。

此外,當網絡隔斷發生時,應用程序可以在可用性和一致性之間做選擇。

故障轉移限流:為了防止級聯故障,Shard Manager支持故障轉移限流,它限制了分片故障轉移的頻率,並保護其它正常服務器在重大停機情況下不會突然過載。

負載均衡

負載均衡是指在一個連續的基准上將分片及其工作負載均勻地分佈在應用服務器上的過程。它可以有效利用資源並避免熱點。

異構硬件和分片:在Facebook,我們有多種類型和代際的硬件。大部分應用程序需要運行在異構硬件上。由於應用程序的工作負載或數據不能均勻地分片,因此分片的大小和負載會有所不同。 Shard Manager的負載均衡算法考慮了每台服務器和每個分片(副本)的細粒度信息,因此支持異構硬件和分片。

動態負載收集:一個分片的負載會在使用中隨著時間而變化。如果應用程序的可用容量與動態資源(比如可用磁盤空間)綁定,那麼它可能會有所不同。 Shard Manager定期從應用程序收集每個分片的負載和每個服務器的容量,並進行負載均衡。

多資源均衡:根據用戶配置不同的優先級,Shard Manager支持同時平衡多種資源,如計算、內存和存儲。這保證了瓶頸資源的利用率在可接受的範圍內,並儘最大可能平衡非關鍵資源的使用。

限流:與故障轉移限流類似,負載均衡生成的分片移動的數量在總移動數粒度和每個服務器的移動數粒度上進行限流。

上述對空間和時間負載變化的多功能支持滿足了分片應用程序的不同平衡需求。

分片擴展

Facebook的许多应用程序响应直接或间接来自用户请求。因此,流量呈现出一种日间模式,在高峰期和非高峰期之间,请求频率显著下降。

彈性計算,基於工作負載的變化動態調整資源分配,是一種不需要犧牲可靠性就能提升資源效率的解決方案。為了響應實時負載變化,Shard Manager可以執行分片擴展,這意味著當一個分片的平均每個副本的負載偏離了用戶配置的可接受範圍,它能動態調整副本因子。分片擴展限流可以配置在給定期限內新增或廢棄的副本數量。

下圖展示了一個分片的擴展過程。最初,所有副本的總負載增加,每個副本的負載增加。一旦每個副本的負載超過了閾值上限,分片擴展就會開始,並添加足夠數目的新副本來使每個副本的負載回到一個可接受的範圍。稍後,分片負載開始減少,那麼分片擴展會減少副本的數量來釋放不需要的資源,以供其它熱點分片或應用程序使用。

Facebook如何用分片來擴展服務? 7

分片擴展過程的圖示

安全運維

除了故障,運維事件也是常態而不是異常,要被視為頭號問題來盡量減少它們對可靠性的影響。常見的運維事件包括字節碼更新、硬件修復和維護以及內核升級。

分片經理與容器管理系統Twine進行了合作設計,以實現無縫事件處理。 Twine聚集事件,將它們轉換成容器生命週期事件,例如容器停止/重啟/移動,並且通過TaskControl接口將它們通信給Shard ManagerScheduler。

Shard Manager Scheduler評估事件的破壞性和長度,並採取必要的主動分片移動來防止事件影響可靠性。 Shard Manager保證每個分片必須擁有至少一個健康的副本。

對於具有多數法定人數規則的基於Paxos的應用程序,Shard Manager支持另一種保證,即保證大多數副本是健康的。運維安全與效率之間的權衡是隨著應用程序變化的,而且可以通過配置調整,例如同時受影響的分片上限。

下圖展示了一個應用程序的例子,包含4個容器和3個分片。首先,一個短期的維護操作(例如內核升級或者影響容器4的安全補丁請求),Shard Manager允許操作立即進行,因為所有的分片在其它服務器上還有其余副本。接下來,對容器1到容器3請求二進制更新。由於並行更新任何兩個容器都會導致分片不可用,因此Shard Manager串行更新這些容器,即每次只更新一個。

Facebook如何用分片來擴展服務? 8

運維事件處理的一個例子

客戶端請求路由

我們使用了一個通用的路由庫來路由Facebook的請求。這個路由庫使用了一個應用程序的名字和分片ID作為輸入,返回一個RPC客戶端對象,通過該對象可以簡單地進行RPC調用,如下面的代碼所示。發現分片的分配位置的秘訣被隱藏在create_rpc_client

rpc_client = create_rpc_client(app_name, shard_id)
rpc_client.foo(...)

Shard Manager的設計和實現

在本節,我們將深入介紹Shard Manager是如何被設計,用來支持我們所討論的那些功能。我們將從基礎設施層次開始分享,特別是Shard Manager的角色。

基礎設施棧的層次

在Facebook,我們的整體基礎設施是用一種分層的方案構建的,各層次之間的關注點明顯分離。這讓我們能獨立而穩健地演進和擴展每一層。

下圖展示了我們基礎設施的層次。每一層分配和定義了相鄰上層操作的範圍。

Facebook如何用分片來擴展服務? 9

基礎設施棧

  1. 主機管理:資源管理系統(Resource Allowance System)管理著所有的物理服務器,然後給各個組織和團隊分配定量資源。
  2. 容器管理:Twine從資源管理系統獲取資源,將它分配給容器單元內的單獨的應用程序。
  3. 分片管理:對於分片的應用程序,Shard Manager用Twine提供的容器分配分片。
  4. 分片的應用程序:在每個分片中,應用程序分配並運行相關的工作負載。
  5. 產品:這些都是面向用戶的產品,例如移動應用程序,由分片的後端應用程序支撐。

除了每層對相鄰的較低層的向下功能依賴,整個基礎設施棧是通過向上傳播的信號和事件進行聯合設計和協同工作的。特別是對於Shard Manager層, TaskControl是我們實現協同調度的機制。

設計

中央控制面板

Shard Manager是一個純粹的控制面板服務,監控應用程序狀態並協調應用程序的數據在跨服務器不同分片上的移動。集中式的全局視圖讓Shard Manager能計算全局最優的分片分配,並通過整體協調所有計劃的運維事件來保證高可用性。在這個中央控制面板關閉的情況下,應用程序可以使用現有的分片分配繼續以降級模式運行。

擁有狀態轉換接口的不透明分片

對Shard Manager來說,分片是不透明的,用戶可以在他們的應用程序中將它映射成任何實體,例如數據庫實例、日誌集和數據集。我們定義了每個應用程序都必須實現的分片狀態轉換接口。

這種清晰的劃定讓Shard Manager與特定於應用程序的數據面板區分開來,並在可以利用Shard Manager的用例方面提供巨大的靈活性。

分片最佳粒度

分片粒度是很重要的。太粗糙的粒度會導致負載均衡較差,而太精細的粒度會導致對底層設施不必要的管理負擔。我們特意選擇了一個最佳選擇,即為每個應用程序服務器分配數百個分片,並在負載均衡質量和基礎設施成本上達到很好的平衡。

通用約束優化

用例多樣性的一個表現形式就是應用程序希望通過分配分片來實現容錯性和效率的各種方法。我們採用了一個通用約束優化求解器來實現可擴展性。

當增加對新需求的支持時,Shard Manager只需要在內部將它描述為約束,並將它們輸入到求解器中就能計算出最佳的分片分配,而我們的代碼庫幾乎沒有增加複雜性。

架構

這裡展示了Shard Manager的架構,包括如下所示的各種組件。

Facebook如何用分片來擴展服務? 10

Shard Manager的架構

應用程序所有者會向Shard Manager Scheduler提供一份規範,包含管理應用程序所需的所有信息。

Shard Manager Scheduler是協調分片轉換和移動的中心服務。它收集應用程序狀態;監控狀態變化,例如服務器加入、服務器故障以及負載變化;調整分片分配;通過對應用服務器的RPC調用來驅動分片狀態轉換。 Shard Manager Scheduler內部進行分片從而實現水平擴展。

應用程序連接Shard Manager庫,這個庫通過連接ZooKeeper提供服務器成員信息和活動狀態檢查。應用程序實現分片狀態轉換接口,並由Shard Manager Scheduler指示進行狀態轉換。應用程序可以測量和公開由Shard Manager Scheduler收集的動態負載信息。

Shard Manager Scheduler將分片分配的公共視圖發佈到一個高度可用和可伸縮的服務發現系統,該系統將信息傳播到應用程序客戶端,以便對請求進行路由。

應用程序客戶端連接一個通用路由庫,該庫以每個分片為基礎封裝了服務器端點發現信息。在端點發現後,客戶端請求被直接發送到應用服務器。因此,Shard Manager Scheduler不在請求相應的關鍵路徑上。

總結

Shard Manager為構建分片應用程序提供了一個通用平台。用戶只需要實現一個分片狀態轉換接口並通過基於intent的規範來表達分片約束。這個平台與Facebook生態系統的其餘部分完全集成,這將底層基礎設施的複雜性隱藏在一個整體合同背後,並讓我們的工程師能聚焦於應用程序和產品的核心業務邏輯。

Shard Manager從九年前開始的時候就一直在演進,但這一過程還遠未完成。我們將繼續努力為Facebook構建分片服務提供一流的解決方案。

儘管取得了一些成功,但是我們仍在多個方面擴展Shard Manager的規模和功能。以下是我們計劃在未來幾年應對的挑戰:

  1. 通過將應用程序在內部劃分為較小的獨立分區,支持每個應用程序數千萬個分片來滿足不斷增長的大型應用程序的需求。
  2. 通過為用戶定制化提供更高程度的模塊化和插件化的同時,使Shard Manager保持簡單,來支持更複雜的應用程序。
  3. 為當前抽象太過繁重的小型簡單應用程序簡化試用體驗。

原文鏈接:

https://engineering.fb.com/production-engineering/scaling-services-with-shard-manager