Meta如何將其緩存一致性提高至99.99999999%

數據智能相依偎 2024-04-24 03:40:41

簡介

緩存是計算機系統中的一種強大技術,從硬件緩存到操作系統、Web浏覽器,尤其是後端開發中都有廣泛應用。對于像Meta這樣的公司,緩存非常重要,它有助于降低延遲、處理大量工作負載,並節省成本。由于Meta的應用場景非常緩存密集,這給他們帶來了另一組問題,即緩存失效。

多年來,Meta已將其緩存一致性水平從99.9999(六個九)提高到99.99999999(十個九),這意味著他們的緩存集群中不到十億次寫入中只有不到1次會導致不一致。

本文將重點討論以下四個主要部分:

1.緩存失效和緩存一致性是什麽?2.Meta爲什麽如此深刻關注緩存一致性,即使六個九還不夠?3.Meta的監控系統如何幫助他們改善緩存失效和緩存一致性,並解決Bug。

緩存失效和緩存一致性

根據定義,緩存不保存數據的真實來源,因此在源數據發生更改時,應主動使過期的緩存條目失效。如果在失效過程中出現問題,會導致緩存中的值與源數據不一致。

那麽我們如何使緩存失效?

我們可以使用TTL(生存時間)來保持數據的新鮮度,以確保沒有其他系統引起的緩存失效。但在本文中,我們將假設失效操作是由緩存之外的某個組件執行的。

首先讓我們看看如何引入緩存不一致性:

1*4Hrv7qAucDhNWFwhpd6rtw.jpeg

請假設1、2、3、4是遞增序列中的時間戳。

1.緩存首先嘗試從數據庫獲取值。2.但在值 x=42 到達緩存之前,某個操作更新了數據庫中的值爲 x=43。3.數據庫發送了 x=43 的緩存失效事件,並在 x=42 到達之前到達緩存,將緩存值設置爲43。4.現在事件 x=42 到達緩存,將緩存設置爲42,從而引入了不一致性。

爲了解決這個問題,我們可以使用版本字段來執行沖突解決,使舊版本永遠不會覆蓋當前版本。這種解決方案對于互聯網上幾乎99%的公司都有效,但是Meta操作的規模可能使其不足以解決問題,因爲其系統的複雜性。

爲什麽Meta如此關注緩存一致性?

從Meta的角度來看,緩存不一致性幾乎與數據庫數據丟失一樣嚴重,而從用戶的角度來看,可能會導致非常糟糕的用戶體驗。

當您在Instagram上向用戶發送私信時,在幕後,存在著將用戶映射到存儲其消息的主要存儲的過程。

在這裏假設有三個用戶:Bob、Mary和Alice。這些用戶都向Alice發送消息。Bob在美國,Alice在歐洲,Mary在日本。因此,系統將在接近用戶所在地區的最近區域進行查詢,以將消息發送到Alice的數據存儲區域。在這種情況下,當TAO副本在BOB和Mary所在的區域查詢時,它們都有不一致的數據,因此它將消息發送到區域,該區域沒有Alice的消息。

1*XvYNFbpG87Sq2ehcmfruQA.jpeg

在上述情況下,可能會導致消息丟失和糟糕的用戶體驗,因此這是Meta需要解決的重要問題之一。

監控

爲了解決緩存失效和緩存一致性問題,第一步是進行測量。如果我們能夠准確測量緩存的一致性,並在緩存中出現不一致的條目時發出警報,Meta確保他們的測量不包含任何誤報,因爲值班工程師會學會忽略它,這個指標將失去信任並變得無用。

在深入探討Meta實施的實際解決方案之前,最簡單的解決方案可能是記錄和跟蹤每個緩存狀態的變化。但是,對于大型工作負載的情況,Meta的系統每天處理超過10萬億次的緩存填充。記錄和跟蹤所有緩存狀態將會使本來已經很重的緩存工作負載變得極其繁重,更不用說調試了。

Polaris

Polaris在非常高的層面上,作爲客戶端與一個有狀態服務進行交互,並且假設沒有對服務內部的了解。Polaris的工作原理是“緩存應該最終與數據庫一致”。Polaris接收失效事件並查詢所有副本,以驗證是否存在任何其他違反約束的情況。例如:

如果Polaris接收到一個失效事件,表示 x=4,版本爲4,它會作爲客戶端檢查所有緩存副本,以驗證是否存在任何不變量的違反情況。如果一個副本返回 x=3 @ 版本3,Polaris會將其標記爲不一致,並重新排隊以稍後對其進行相同目標緩存主機的檢查。Polaris會在一分鍾、五分鍾或十分鍾的時間範圍內報告不一致性。

1*6bbUmGV0hL0EiGGlgdK8_g.jpeg

這種多時間尺度設計不僅允許Polaris在內部具有多個隊列來有效地實現退避和重試,而且對于防止産生誤報至關重要。

讓我們通過一個例子來理解:

假設Polaris接收到一個失效事件,表示 x=4,版本爲4。但是當Polaris檢查緩存時,找不到鍵 x 的條目,這應該被標記爲不一致。在這種情況下,有兩種可能性:

1.在版本3時 x 是不可見的,但版本4的寫入是密鑰的最新寫入,並且確實存在緩存不一致性。2.可能存在版本5的寫入刪除了鍵 x,也許Polaris只是看到了比失效事件中的更近期的數據視圖。

現在,我們如何確保這兩種情況中的哪一種是正確的?

爲了驗證,在這兩種情況中,Polaris需要通過查詢數據庫來檢查。繞過緩存的查詢可能需要大量計算資源,並且可能會使數據庫面臨風險,因爲保護數據庫和擴展讀取重負載是緩存的兩個最常見用例。因此,我們不能向系統發送太多查詢。

Polaris通過延遲執行此類檢查並直到不一致樣本超過設置的阈值(例如1分鍾或5分鍾)時才對數據庫進行調用來解決此問題。Polaris生成的指標是“M分鍾內緩存寫入的 N 個九的一致性”。因此,目前Polaris提供了一個指標,即緩存在五分鍾的時間尺度上的一致性達到99.99999999。

現在讓我們看看Polaris如何幫助Meta使用編碼示例解決Bug。

讓我們通過一個編碼示例來理解流程:

假設一個緩存維護一個鍵到元數據映射和鍵到版本映射。

1*b0OLg-8qs95RIZ4glFefbQ.jpeg

cache_data = {}cache_version = {}meta_data_table = {"1": 42}version_table = {"1": 4}def read_value(key): value = read_value_from_cache(key) if value is not None: return value else: return meta_data_table[key]def read_value_from_cache(key): if key in cache_data: return cache_data[key] else: fill_cache_thread = threading.Thread(target=fill_cache(key)) fill_cache_thread.start() return Nonedef fill_cache(key): fill_cache_metadata(key) fill_cache_version(key)def fill_cache_metadata(key): meta_data = meta_data_table[key] print("Filling cache meta data for", meta_data) cache_data[key] = meta_datadef fill_cache_version(key): time.sleep(2) version = version_table[key] print("Filling cache version data for", version) cache_version[key] = versiondef write_value(key, value): version = 1 if key in version_table: version = version_table[key] version = version + 1 write_in_databse_transactionally(key, value, version) time.sleep(3) invalidate_cache(key, value, version)def write_in_databse_transactionally(key, data, version): meta_data_table[key] = data version_table[key] = versiondef invalidate_cache(key, metadata, version): try: cache_data = cache_data[key][value] ## To produce error except: drop_cache(key, version)def drop_cache(key, version): cache_version_value = cache_version[key] if version > cache_version_value: cache_data.pop(key) cache_version.pop(key)read_thread = threading.Thread(target=read_value, args=("1"))write_thread = threading.Thread(target=write_value, args=("1",43))print_thread = threading.Thread(target=print_values)

在緩存失效過程中,如果由于某種原因導致失效操作失敗,並且異常處理程序具有在這種情況下刪除緩存的條件。

請記住,這只是可能觸發Bug的非常簡化的示例,實際的Bug還涉及數據庫複制和跨區域通信。該Bug只有在以上所有步驟按特定順序發生時才會觸發。該Bug隱藏在交錯操作和瞬態錯誤背後的錯誤處理代碼中。

一致性追蹤

現在您是值班工程師,收到了Polaris的緩存不一致性警報,最重要的是檢查日志以確定問題可能出現在哪裏。正如之前討論的,記錄每個緩存數據更改幾乎是不可能的,但是如果我們只記錄有可能導致更改的數據呢?

1*1hy6s_dJTiFT8qkaRRHgdg.jpeg

如果我們看一下上面實現的代碼,問題可能在于如果緩存未收到失效事件或失效操作未生效。從值班工程師的角度來看,我們需要檢查以下內容:

•緩存服務器是否接收到了失效操作?•服務器是否正確處理了失效操作?•項目是否在此後變

得不一致?

Meta構建了一個有狀態追蹤庫,在這個小窗口中記錄和跟蹤緩存變異,所有有趣和複雜的交互觸發導致緩存不一致性的Bug。

結論

對于任何分布式系統來說,可靠的監控和日志系統至關重要,以確保我們能夠捕獲Bug,一旦捕獲到Bug,我們就能夠快速找到根本原因,從而減輕問題。借鑒Meta的例子,Polaris識別出了異常並立即觸發了警報。有了一致性追蹤的信息,值班工程師們不到30分鍾就找到了Bug的位置。

參考鏈接:https://engineering.fb.com/2022/06/08/core-infra/cache-made-consistent/

0 阅读:2

數據智能相依偎

簡介:感謝大家的關注