Consul流式傳輸引發Roblox停機事後分析

banq發表於2022-01-28

從 10 月 28 日開始並於 10 月 31 日完全解決,Roblox 經歷了 73 小時的中斷。¹每天有 5000 萬玩家定期使用 Roblox,為了創造玩家期望的體驗,我們的規模涉及數百個內部線上服務。
我們分享這些技術細節是為了讓我們的社群瞭解問題的根本原因、我們如何解決它,以及我們正在採取哪些措施來防止未來發生類似問題。
中斷在持續時間和複雜性上都是獨一無二的。該團隊必須依次解決一系列挑戰,以瞭解根本原因並恢復服務。
  • 停電持續了 73 小時。
  • 根本原因是由於兩個問題。在異常高的讀寫負載下在 Consul 上啟用相對較新的流式傳輸功能會導致過度爭用和效能下降。此外,我們特定的負載條件在 BoltDB 中引發了異常的效能問題。Consul 中使用開源 BoltDB 系統來管理用於領導選舉和資料複製的預寫日誌。 
  • 支援多個工作負載的單個 Consul 叢集加劇了這些問題的影響。
  • 在診斷這兩個主要不相關的問題時,深埋在 Consul 實施中的挑戰是導致停機時間延長的主要原因。 
  • 能夠更好地瞭解中斷原因的關鍵監控系統依賴於受影響的系統,例如 Consul。這種組合嚴重阻礙了分類過程。
  • 我們在將 Roblox 從擴充套件的完全關閉狀態中恢復過來的方法上經過深思熟慮和謹慎,這也花費了相當長的時間。
  • 我們加快了工程設計工作,以改進我們的監控、消除可觀察性堆疊中的迴圈依賴關係,以及加速我們的引導過程。 
  • 我們正在努力遷移到多個可用區和資料中心。
  • 我們正在修復 Consul 中導致此事件的根本原因的問題。

 

叢集環境和 HashiStack
Roblox 的核心基礎設施在 Roblox 資料中心執行。我們部署和管理我們自己的硬體,以及基於該硬體的我們自己的計算、儲存和網路系統。我們的部署規模巨大,擁有超過 18,000 臺伺服器和 170,000 個容器。
為了在多個站點上執行數千臺伺服器,我們利用了一種通常稱為“ HashiStack ”的技術套件。Nomad ConsulVault是我們用來管理世界各地的伺服器和服務的技術,它們使我們能夠編排支援 Roblox 服務的容器。
Nomad 用於安排工作。它決定哪些容器將在哪些節點上執行以及它們可在哪些埠上執行。它還驗證容器執行狀況。所有這些資料都被中繼到一個服務註冊中心,它是一個 IP:Port 組合的資料庫。Roblox 服務使用服務註冊來相互查詢,以便進行通訊。這個過程稱為“服務發現”。我們使用Consul進行服務發現、健康檢查、會話鎖定(用於構建在頂部的 HA 系統)以及作為 KV 儲存。
Consul 部署為具有兩個角色的機器叢集。“Voters”(5 臺機器)權威持有叢集的狀態;“非投票者”(另外 5 臺機器)是隻讀副本,有助於擴充套件讀取請求。在任何給定時間,其中一個選民被叢集選舉為領導者。領導者負責將資料複製給其他投票者,並確定寫入的資料是否已完全提交。Consul 使用一種稱為Raft的演算法進行領導者選舉,並以確保叢集中每個節點都同意更新的方式在叢集中分配狀態。領導者在一天內透過領導者選舉多次改變的情況並不少見。
 
在 10 月事件發生前的幾個月裡,Roblox 從 Consul 1.9 升級到Consul 1.10,以利用新的流媒體功能。此流式傳輸功能旨在顯著減少跨大型叢集(如 Roblox 的叢集)分發更新所需的 CPU 和網路頻寬。
 
10 月 28 日下午,Vault 效能下降,單個 Consul 伺服器 CPU 負載過高。Roblox 工程師開始調查。
 
初步調查表明,Vault 和許多其他服務所依賴的 Consul 叢集執行狀況不佳。具體來說,Consul 叢集指標顯示 Consul 儲存資料的底層 KV 儲存的寫入延遲增加。這些操作的第 50 個百分位延遲通常低於 300 毫秒,但現在為 2 秒。硬體問題在 Roblox 的規模上並不少見,Consul 可以在硬體故障中倖存下來。但是,如果硬體只是緩慢而不是失敗,它可能會影響 Consul 的整體效能。在這種情況下,團隊懷疑硬體效能下降是根本原因,並開始更換 Consul 叢集節點之一。這是我們第一次嘗試診斷該事件
大約在這個時候,HashiCorp 的工作人員加入了 Roblox 工程師的行列,以幫助診斷和修復。從現在開始,所有對“團隊”和“工程團隊”的提及均指 Roblox 和 HashiCorp 員工。
 
即使有了新硬體,Consul 叢集的效能仍然受到影響。16點35分,線上玩家數量下降到正常的50%。
 
這一下降恰逢系統健康狀況顯著下降,最終導致系統完全中斷。為什麼?當一個 Roblox 服務想要與另一個服務對話時,它依賴於 Consul 來獲取它想要與之對話的服務的位置的最新資訊。但是,如果 Consul 不健康,伺服器將難以連線。此外,Nomad 和 Vault 依賴於 Consul,因此當 Consul 不健康時,系統無法排程新容器或檢索用於身份驗證的生產機密。簡而言之,系統失敗是因為 Consul 是單點故障,Consul 不健康。
在這一點上,團隊提出了一個關於問題所在的新理論:流量增加。也許 Consul 很慢,因為我們的系統達到了臨界點,而 Consul 執行的伺服器無法再處理負載?這是我們第二次嘗試診斷事件的根本原因。
 
鑑於事件的嚴重性,團隊決定將 Consul 叢集中的所有節點替換為新的、更強大的機器。這些新機器有 128 個核心(增加了 2 倍)和更新、更快的 NVME SSD 磁碟。到 19:00,團隊將大部分叢集遷移到新機器上,但叢集仍然不健康。叢集報告大多數節點無法跟上寫入速度,KV 寫入的 50% 延遲仍然在 2 秒左右,而不是典型的 300 毫秒或更短。
 
將 Consul 叢集恢復到健康狀態的前兩次嘗試均未成功。我們仍然可以看到增加的 KV 寫入延遲以及一個我們無法解釋的新的莫名其妙的症狀:Consul 領導者經常與其他選民不同步。 
 
團隊決定關閉整個 Consul 叢集並使用幾個小時前的快照重置其狀態 - 中斷的開始。我們知道這可能會導致少量系統配置資料丟失(而不是使用者資料丟失)。鑑於中斷的嚴重性以及我們相信如果需要我們可以手動恢復此係統配置資料,我們認為這是可以接受的。 
 
我們預計從系統健康時拍攝的快照恢復會使叢集進入健康狀態,但我們還有一個額外的擔憂。儘管此時 Roblox 沒有任何使用者生成的流量流經系統,但內部 Roblox 服務仍然有效,並盡職盡責地聯絡 Consul以瞭解其依賴項的位置並更新其健康資訊。這些讀取和寫入在叢集上產生了很大的負載。我們擔心即使叢集重置成功,這種負載也會立即將叢集推回不健康狀態。為了解決這個問題,我們配置了iptables在叢集上阻止訪問。這將使我們能夠以受控的方式恢復叢集,並幫助我們瞭解我們在 Consul 上施加的獨立於使用者流量的負載是否是問題的一部分。
 
重置進展順利,最初,指標看起來不錯。當我們移除iptables塊時,來自內部服務的服務發現和健康檢查負載按預期返回。然而,Consul 的效能再次開始下降,最終我們回到了我們開始的地方:KV 寫入操作的第 50 個百分位又回到了 2 秒。依賴於 Consul 的服務開始將自己標記為“不健康”,最終,系統又回到了現在熟悉的問題狀態。現在是 04:00。很明顯,我們的Consul 的負載造成了問題,在事件發生 14 多個小時後,我們仍然不知道它是什麼。
 
我們已經排除了硬體故障。更快的硬體沒有幫助,正如我們後來瞭解到的那樣,可能會損害穩定性。重置 Consul 的內部狀態也無濟於事。沒有使用者流量進來,但 Consul 仍然很慢。我們利用iptables讓流量緩慢返回叢集。叢集是否只是被成千上萬個試圖重新連線的容器推回到不健康狀態?這是我們第三次嘗試診斷事件的根本原因
 

工程團隊決定減少 Consul 的使用,然後仔細系統地重新引入它。為了確保我們有一個乾淨的起點,我們還阻止了剩餘的外部流量。我們收集了一份詳盡的使用 Consul 的服務列表,並推出了配置更改以禁用所有非必要的使用。由於目標系統和配置更改型別繁多,此過程需要幾個小時。通常執行數百個例項的 Roblox 服務被縮減到個位數。健康檢查頻率從 60 秒減少到 10 分鐘,為叢集提供了額外的喘息空間。10 月 29 日 16:00,在中斷開始 24 小時後,團隊開始第二次嘗試讓 Roblox 重新上線。再一次,這次重啟嘗試的初始階段看起來不錯,但只持續到 10 月 30 日 02:00 。
在這一點上,很明顯,Consul 的整體使用並不是我們在 28 日首次注意到的效能下降的唯一因素。鑑於這一認識,團隊再次轉向。團隊沒有從依賴於它的 Roblox 服務的角度來看待 Consul,而是開始從 Consul 內部尋找線索。
 
在接下來的 10 個小時裡,工程團隊深入挖掘了除錯日誌和作業系統級別的指標。該資料顯示 Consul KV 寫入被長時間阻塞。換句話說,“爭用”。爭用的原因並不是很明顯,但有一種理論認為,在中斷早期從 64 個 CPU 核心伺服器轉移到 128 個 CPU 核心伺服器可能會使問題變得更糟。團隊得出結論,值得回到與中斷前使用的類似的 64 臺核心伺服器。團隊開始準備硬體:安裝了 Consul,對作業系統配置進行了三次檢查,並且機器以儘可能詳細的方式準備好服務。然後團隊將 Consul 叢集轉換回 64 CPU Core 伺服器,但這種改變並沒有幫助。這是我們第四次嘗試診斷事件的根本原因。
  

找到根本原因 (10/30 12:00 – 10/30 20:00)
幾個月前,我們在部分服務上啟用了新的 Consul 流式傳輸功能。此功能旨在降低 Consul 叢集的 CPU 使用率和網路頻寬,按預期工作,因此在接下來的幾個月中,我們逐步在更多後端服務上啟用了該功能。10 月 27 日 14:00,即中斷前一天,我們在負責流量路由的後端服務上啟用了此功能。
透過對來自 Consul 伺服器的效能報告和火焰圖的分析,我們看到了流式程式碼路徑對導致 CPU 使用率高的爭用負責的證據。我們禁用了所有 Consul 系統的流式傳輸功能,包括流量路由節點。配置更改在 15:51 完成傳播,此時 Consul KV 寫入的第 50 個百分位降低到 300 毫秒。我們終於有了突破。
 
為什麼流式傳輸是一個問題?
HashiCorp 解釋說,雖然流式傳輸總體上更有效,但與長輪詢相比,它在實現中使用的併發控制元素(Go 通道)更少。
在非常高的負載下——特別是非常高的讀取負載和非常高的寫入負載——流的設計會加劇單個 Go 通道上的爭用量,這會導致寫入過程中出現阻塞,從而顯著降低效率。
這種行為也解釋了更高核心數伺服器的影響:這些伺服器是具有 NUMA 記憶體模型的雙插槽架構。因此,在這種架構下,共享資源的額外爭用變得更加嚴重。透過關閉流式傳輸,我們顯著改善了 Consul 叢集的健康狀況。
 
儘管取得了突破,但我們還沒有走出困境。我們看到 Consul 間歇性地選舉新的叢集領導者,這是正常的,但我們也看到一些領導者表現出與我們在禁用流式傳輸之前看到的相同的延遲問題,這是不正常的。在沒有任何明顯線索指向緩慢領導者問題的根本原因的情況下,並且有證據表明只要某些伺服器沒有被選為領導者,叢集是健康的,團隊做出了務實的決定,透過防止出現問題來解決問題領導者留在選民。這使團隊能夠專注於將依賴 Consul 的 Roblox 服務恢復到健康狀態。
 
 HashiCorp 的工程師在中斷後的幾天內確定了根本原因。Consul 使用一個流行的開源永續性庫 BoltDB 來儲存 Raft 日誌。它用於儲存 Consul 中的當前狀態,而是用於儲存正在應用的操作的滾動日誌。為了防止 BoltDB 無限增長,Consul 會定期執行快照。快照操作將 Consul 的當前狀態寫入磁碟,然後從 BoltDB 中刪除最舊的日誌條目。 

但是,由於 BoltDB 的設計,即使刪除了最舊的日誌條目,BoltDB 在磁碟上使用的空間也不會縮小。
相反,所有用於儲存已刪除資料的頁面(檔案中的 4kb 段)都被標記為“空閒”並重新用於後續寫入。BoltDB 在稱為“freelist”的結構中跟蹤這些空閒頁面。通常,更新 freelist 所需的時間不會顯著影響寫入延遲,但 Roblox 的工作負載暴露了 BoltDB 中的一個病態效能問題,這使得 freelist 維護非常昂貴。 
 

恢復快取服務(10/30 20:00 – 10/31 05:00)
Roblox 為其後端使用典型的微服務模式。微服務“堆疊”的底部是資料庫和快取。這些資料庫沒有受到中斷的影響,但快取系統在正常系統執行期間定期處理其多個層每秒 1B 的請求,但執行狀況不佳。由於我們的快取儲存可以輕鬆從底層資料庫重新填充的瞬態資料,因此使快取系統恢復健康狀態的最簡單方法是重新部署它。
快取重新部署過程遇到了一系列問題: 

  1. 可能是由於之前執行的 Consul 叢集快照重置,快取系統儲存在 Consul KV 中的內部排程資料不正確。 
  2. 小型快取的部署花費的時間比預期的要長,而且大型快取的部署還沒有完成。事實證明,有一個不健康的節點,作業排程程式將其視為完全開啟而不是不健康。這導致作業排程程式嘗試在此節點上積極排程快取作業,但由於節點不健康而失敗。 
  3. 快取系統的自動部署工具旨在支援對已經在大規模處理流量的大規模部署進行增量調整,而不是從頭開始引導大型叢集的迭代嘗試。 

該團隊通宵工作以識別和解決這些問題,確保正確部署快取系統並驗證正確性。10 月 31 日 05:00,在中斷開始 61 小時後,我們擁有了一個健康的 Consul 叢集和一個健康的快取系統。我們已準備好提出 Roblox 的其餘部分。

相關文章