Facebook 的全球網絡大揭秘:構建社交帝國的科技奇蹟

2023-10-15     InfoQ

原標題:Facebook 的全球網絡大揭秘:構建社交帝國的科技奇蹟

作者 | Engineer’s Codex

譯者 | Sambodhi

策劃 | 褚杏娟

在這篇文章中,我們將揭開 Facebook 如何構建並維護全球規模的社交網絡,同時保持卓越性能和可用性的神秘面紗。Facebook 的成功不僅源於創新理念,還有一系列架構決策和性能優化。

首先,我們將了解 Facebook 如何通過分布式集群、區域擴展和全球覆蓋滿足數十億用戶需求。他們的架構將緩存與持久性存儲系統分離,實現獨立擴展。我們還將看到 Facebook 如何注重簡單性,以便快速擴展、吸納新工程師並保持高效流程。

此外,我們將研究 Facebook 在性能優化方面採取的策略,包括對 Memcached 的定製、失效守護進程 mcsqueal 的實現,以及其他關鍵技術如緩存、通信和故障處理。

最後,我們還將了解 Facebook 的教訓和經驗,包括如何處理全球規模的競爭條件、持續貢獻開源社區以及在不斷演進的技術領域中保持簡單性的重要性。

如果你對構建和維護大規模分布式系統、性能優化和全球社交網絡感興趣,那麼這篇文章將揭示 Facebook 的秘密,帶你深入了解他們的成功之道。

Facebook 的資料庫如同浩瀚星海,每秒鐘接收數以億計的請求,存儲著海量的數據。傳統 Web 架構已無法滿足其巨大的需求。

Facebook 使用了一個稱為 Memcached 的簡單鍵值存儲系統,並將其擴展以高效處理每秒數十億次請求和數萬億的數據項。

回顧 2013 年,Facebook 自豪地宣布他們的系統已成為「全球最大的 Memcached 安裝」。

如果你對技術細節不感興趣,可以毫不猶豫地跳過前文,直接探尋文章末尾的「教訓總結」部分。

Facebook 的擴展之道

  • 以用戶為中心,任何更改都只能影響用戶介面或運營問題。
  • 不要追求完美,即使這意味著可能會獲取一些過時數據。

Facebook 的擴展策略

需求

Facebook 深知以下假設:

於是,他們採用了三個擴展層次:集群、區域和全球。

他們擴展了 Memcached 以實現這一目標。

簡要解釋 Memcached

它是一個基本的鍵值存儲系統,使用哈希表實現,存儲在內存中。它是資料庫上的緩存層

相較於資料庫的高昂讀取成本,內存的讀取顯得尤為昂貴。然而,Facebook 背負著數萬億的數據項,其資料庫存儲量可謂巨無霸級別。試想,在我開發的某款應用中,一個 1.2MB 的 JSON 響應竟需耗費約莫 1100 毫秒方能返回。然而,在 Memcached 的加持下,僅需 200 毫秒便可將其返回。

Facebook 在 Memcached 中存儲的內容

Memcached 存儲了各種請求的響應。

倘若用戶請求他們的個人資料信息,而且自上次請求以來沒有更改,那個請求的響應已經存儲在 Memcached 中。它還存儲了常見的中間產物,例如來自 Facebook 機器學習算法的預計算結果。

為何鍾情於 Memcached?

「Memcached 提供了一組簡單的操作(設置、獲取和刪除),使其成為大規模分布式系統中的基本組件。」(第 2 節,概述)

「Memcached 提供了一組簡單的操作(設置、獲取和刪除),使其成為大規模分布式系統中的基本組件。」(第 2 節,概述)

在論文中,他們使用 Memcached 作為基本的鍵值存儲,而使用 Memcache 作為他們正在運行的 Memcached 的分布式系統版本。這個分布式版本的 Memcached 具有額外的功能,比如用於伺服器間通信的特殊客戶端等。

我發現他們的命名有點令人困惑,因此在本文的其餘部分,我將稱之為他們的分布式系統版,即 memcache。

集群擴展

目標:

  • 降低讀取數據的延遲
  • 減輕讀取數據對資料庫的負載

在龐大的集群中,Facebook 坐擁成千上萬台伺服器。

每台伺服器都有一個 Memcache 客戶端,提供一系列功能(壓縮、序列化、壓縮等)。所有客戶端都有一個包含所有可用伺服器的映射。

加載一個熱門的 Facebook 頁面平均需要從 Memcache 中進行 521 次不同的讀取操作

通常情況下,一個 Web 伺服器必須與多位 Memcache 伺服器互通有無,方能圓滿處理一個請求。

降低延遲

請求必須以近乎實時的方式完成並返回。

Facebook 運用了三種策略:並行請求 + 批處理、更快捷的客戶端 - 伺服器通信和控制請求擁堵。

並行請求與批處理

使用並行請求和批處理的目標是減少網絡往返次數。

他們創建了一個數據依賴性的 DAG,用於最大程度地提高一次可以獲取的數據項數量,平均每次獲取 24 個鍵。

優化客戶端 - 伺服器通信

他們將複雜性放在了一個無狀態客戶端中,以便保持 Memcache 伺服器的簡單性

對於從 Memcache 發起的獲取操作,他們採用了 UDP,因為任何問題都會顯示為客戶端錯誤(因此用戶只需重新嘗試)。

而對於使用 mcrouter 實例的設置 / 刪除操作,他們選擇了 TCP 作為通信方式。

Mcrouter 猶如一位代理,為 Memcache 伺服器提供接口,並將請求 / 響應路由至 / 自其他伺服器。

管理擁塞

當眾多請求同時湧來時,Memcache 客戶端將運用滑動窗口機制,以控制未完成的請求數量。

他們確定了窗口大小,通過數據分析找到了用戶延遲過高或同時到來的請求過多之間的黃金平衡點。

減輕資料庫負擔

Facebook 通過三種策略減輕了資料庫的負載:租約、Memcache 池和池內複製。

租約

當客戶端遭遇緩存未命中時,Memcache 實例會提供臨時租約。時而,在伺服器寫回緩存之際,租約早已過期,意味著它已被更新的數據所取代。

租約化解了兩大難題:

  • 過時的設置(Web 伺服器在分布式 Memcached 中設置一個不正確的值)
  • 蜂擁而來的「獸群」(某個關鍵字有大量讀寫活動同時發生)

每個關鍵字每 10 秒僅分配一次租約。如此這般,峰值資料庫查詢速率從每秒 17000 次劇減至每秒 1300 次。

Memcache 池

一個龐大的 Memcache 伺服器集群被劃分為不同的池。

大多數關鍵字歸屬於默認池。其餘的則歸為「有問題」或「特殊」關鍵字之列,不在默認池中,例如頻繁訪問但緩存未命中不會對負載或延遲產生顯著影響的關鍵字。

池內複製

對於一類關鍵字,我們選擇在池內進行複製,原因包括:

(1)應用程式頻繁同時獲取多個關鍵字;

(2)整個數據集適合一個或兩個 Memcache 伺服器;

(3)請求速率遠高於單個伺服器的處理能力。詳情參見第 3.2.3 節。

(1)應用程式頻繁同時獲取多個關鍵字;

(2)整個數據集適合一個或兩個 Memcache 伺服器;

(3)請求速率遠高於單個伺服器的處理能力。詳情參見第 3.2.3 節。

區域擴展

然而,我們無法無限擴展一個集群。

多個(前端)集群組成一個區域,它們共同承載著多個 Web 和 Memcache 伺服器,以及一個存儲集群。

在區域內擴展 Memcache 時,採用了三種策略:失效守護進程、區域池以及快速啟動新集群的「冷啟動」機制。

失效守護進程

失效守護進程(mcsqueal)如同一位守護者,它負責在一個區域內將緩存失效操作複製到所有緩存中。

當存儲集群(資料庫)中的數據發生變化時,失效守護進程會立即將失效操作發送到自身所在的集群。

每個資料庫都有一個失效守護進程。

失效守護進程將刪除操作分批成較少的數據包,然後將它們發送到每個前端集群中的 mcrouter 伺服器,然後 mcrouter 伺服器將失效操作路由到正確的 Memcache 伺服器。

區域內的區域池

在區域內,所有前端集群共享著某些類型的 Memcache 伺服器。這些伺服器共同組成了一個區域池,為多個前端集群提供數據存儲和訪問服務。

由於複製操作的開銷較大,區域池採用了一些策略來存儲那些 「不常訪問」 的數據。例如,根據用戶的中位數數量、每秒讀取次數以及中位數值大小等因素,來決定哪些數據應該被保留在區域池中。

冷集群準備

一個 「冷集群」,或者說一個具有空緩存的前端集群,從一個 「溫暖的集群」 或者說一個具有正常命中率緩存的集群中檢索數據。

這將新集群的 「啟動時間」 從幾天縮短到僅僅幾個小時。

然而,或許會遇到一些緩存一致性的競態條件。為了解決這個問題,他們巧妙地在冷集群的刪除操作中添加了兩秒的延遲。一旦冷集群的緩存命中率下降,這個延遲便會自動關閉。

全球擴展

將區域布局於世界各地,背後有著諸多種緣由:

  • 更貼近用戶的距離;
  • 自然事件的緩解,如電力故障等;
  • 各地的經濟激勵措施,如低廉的電力和稅收優惠等。

在各個區域之間,一個區域擁有一個存儲集群和多個前端集群。

一個區域保存主資料庫,而其他區域包含只讀副本,這是通過使用 MySQL 的複製機制來實現的。

他們追求盡力而為的最終一致性,但強調性能和可用性。

在擴展到多個區域後,Facebook 的團隊實施了失效守護進程 mcsqueal,以便能夠立即編寫代碼來正確處理全球規模的競態條件。

他們使用了一種遠程標記機制,以降低讀取過時數據的機率,特別是當寫入發生在非主要區域時。你可以從論文中了解更多相關信息。

雜項性能優化

  • Facebook 對 Memcached 進行了精心調優:
    • 允許內部哈希表自動擴展;
    • 採用全局鎖技術,使伺服器多線程化;
    • 為每個線程分配了自己的 UDP 埠。
  • 前兩項優化成果已惠及開源社區。
  • Facebook 內部還有許多其他卓越優化措施。
  • 軟體升級一組 Memcached 伺服器所需時間超過 12 小時。
    • 他們對 Memcached 進行改造,將緩存值與其他數據結構存儲在共享內存區域,以降低中斷和停機的影響。
  • 允許內部哈希表自動擴展;
  • 採用全局鎖技術,使伺服器多線程化;
  • 為每個線程分配了自己的 UDP 埠。
  • 他們對 Memcached 進行改造,將緩存值與其他數據結構存儲在共享內存區域,以降低中斷和停機的影響。

故障處理

在 Facebook 的巨大規模下,伺服器、硬碟和其他硬體組件每分鐘都面臨故障的威脅。當主機不可用時,他們擁有一套自動化的修復系統。

為了替代一些故障伺服器,他們將約 1% 的 Memcache 伺服器納入「Gutter 池」之中。

總體架構

教訓總結

以下是 Facebook 總結的教訓,供我們參考:

1. 分離緩存與持久性存儲:將緩存和持久性存儲系統分開,使我們能夠獨立地擴展它們。

2. 重視監控與運維效率:功能與性能同樣重要,提高監控、調試和運維效率。

3. 無狀態組件的優勢:管理有狀態組件比無狀態組件更複雜。因此,將邏輯保持在無狀態客戶端有助於疊代功能並最小化中斷。

4. 逐步發布與回滾:系統必須支持新功能的逐步發布和回滾,即使這可能導致特性集的暫時不一致。

5. 追求簡單:簡單性至關重要。

1. 分離緩存與持久性存儲:將緩存和持久性存儲系統分開,使我們能夠獨立地擴展它們。

2. 重視監控與運維效率:功能與性能同樣重要,提高監控、調試和運維效率。

3. 無狀態組件的優勢:管理有狀態組件比無狀態組件更複雜。因此,將邏輯保持在無狀態客戶端有助於疊代功能並最小化中斷。

4. 逐步發布與回滾:系統必須支持新功能的逐步發布和回滾,即使這可能導致特性集的暫時不一致。

5. 追求簡單:簡單性至關重要。

主要收穫

  • 穩定性和可用性的重要性:Facebook 將穩定性和可用性放在首位,並且在權衡各種因素時進行深入的探討、測量和充分解釋。
  • 簡單性的關鍵角色:簡單性對於可擴展性和高效流程至關重要。它使 Facebook 能夠快速擴展、吸納新工程師並保持流程的順暢。
  • 使用經過驗證的技術:Facebook 長時間以來一直使用經過驗證的技術,並根據需求定製了 Memcached,而不是立即構建自己的自定義鍵值存儲。這種策略有助於招聘和引入新的工程師,因為大多數後端工程師都熟悉 Memcached。
  • 對研究社區的貢獻:即使在當時,Facebook 也為研究社區做出了貢獻。雖然現在這可能已經不那麼新穎,但在當時這是一個相當大的貢獻!
  • 支持開源文化:Facebook 積極支持開源文化,包括向開源 Memcached 貢獻了一些修改,並在其他領域如 React、LLaMA、PyTorch 等方面持續支持。儘管有關 TAO 論文描述了一個自定義構建的系統替代了部分功能,但不確定 Facebook 是否仍在使用 Memcached。

支持開源文化:Facebook 積極支持開源文化,包括向開源 Memcached 貢獻了一些修改,並在其他領域如 React、LLaMA、PyTorch 等方面持續支持。儘管有關 TAO 論文描述了一個自定義構建的系統替代了部分功能,但不確定 Facebook 是否仍在使用 Memcached。

原文連結

https://engineercodex.substack.com/p/how-facebook-scaled-memcached#circle=on

主力開發已經 68 歲了!「老齡化」嚴重的 Postgres 開源社區呼喚「年輕一代」

無服務計算,廠商究竟在打什麼算盤

放棄 React 改用 Web 組件,微軟這次重構讓開發者不解:沒有任何意義

AutoGPT 宣布不再使用向量資料庫!向量資料庫是小題大作的方案?

文章來源: https://twgreatdaily.com/zh-cn/2779ede6b51a71d68f62c815f00bcff0.html