如何提升 HBase 大規模叢集下的低延時效能

作者 | Bryan Beaudreault

譯者 | Sambodhi

策劃 | Tina

HubSpot 的資料基礎設施團隊,每天都要處理 2。5PB 以上的低延遲流量,他們親眼目睹了 Locality 對於 HBase 的效能有多麼重要。請繼續閱讀,以瞭解更多關於這些問題:什麼是 Locality ,為什麼如此重要,以及我們如何在不斷增長的 HBase 叢集中使保持 Locality 成為一個不成問題的問題。

HubSpot 的一些最大的資料集儲存在 HBase 中,這是一個開源的、分散式的、版本化的、非關係型的資料庫,模仿谷歌的 BigTable。我們擁有將近 100 個生產 HBase 叢集,包括亞馬遜雲科技兩個區域的 7000 多個 RegionServer。這些叢集每天能處理 2。5PB 以上的低延遲流量,由於亞馬遜雲科技的每個區域是由多個數據中心組成的,因此我們認為,Locality(譯註:即區域性性 、本地性,指將資料放在離需要者儘可能近的地方)是保持這些延遲的關鍵。

什麼是 Locality?

HBase 資料儲存在 HDFS 中,預設情況下,會有 3 種方式來複制你的資料。

如果可能的話,第一個副本會在本地寫入客戶端(HBase RegionServer)。

第二個副本被寫到與第一個副本不同機架上的主機上。

第三個副本被寫到第二個機架上的不同主機上。

所有這些都是很好的做法,但是 HBase 資料也被分割成了連續的小塊,稱為區域(regions)。區域必須能迅速地在不同的主機間移動,從而在託管 RegionServer 崩潰等情況下,能夠維持可用性。為了保證快速,當一個區域移動時,底層資料塊不會移動。HBase 依然可以輕鬆地從 3 個副本主機中仍然可用的任意一個遠端獲得資料,從而為該區域提供資料。

在高度最佳化的單一資料中心中,遠端主機的訪問對延遲的影響微乎其微。在雲中,這些副本主機可能與請求的 RegionServer 不在同一棟樓裡,甚至不在同一個地區。在執行一個對延遲敏感的應用程式時,這個附加的網路跳躍會對終端使用者的效能造成很大的影響。

Locality 是衡量 RegionServer 的資料在給定時間記憶體儲在本地的百分比,這也是我們在 HubSpot 非常密切監測的一個指標。HDFS 除了具有網路延遲之外,還具有“短路讀取”的特性。當資料在本地時,透過短路讀取,可以使客戶端(HBase)在不透過集中的 HDFS 資料節點處理的情況下,從磁碟上直接讀出資料檔案。這將會降低可能來自 TCP 棧或 DataNode 程序本身的延遲。

在過去的幾年中,我們一直在定期進行效能測試,以確認 Locality 對延遲的影響。下面是我們最近一次測試的一些結果

如何提升 HBase 大規模叢集下的低延時效能

所有這四個圖表都有相同的時間視窗。在這個時間視窗開始時,我打亂了處理叢集上的 Locality。正如你所看到的,單個 Get 延遲(左上圖)沒有受到很大的影響,但吞吐量(右上圖)卻顯著地下降了。MultiGets 在延遲(左下角)和吞吐量(右下角)方面受到明顯的影響。我們發現,一般來說,MultiGets 對延遲迴歸格外敏感,因為它們會擊中多個 RegionServer,通常至少與最慢的目標一樣慢。

下面是幾個月前我們遇到的一個生產事故的例子:

這個叢集有一個相對較小的資料集,但負載較重。左軸(淺紫色)是第 99 個百分點的延遲,而右軸(藍色虛線)是 Locality。在這次事件中 ,Locality 約為 10%,延遲在 2~8 秒之間。利用我們將在本文討論的工具,我們在 11 點左右解決了這個問題——在短短几分鐘內,我們就將 Locality 提高到 100%,並使延遲減少到不足 1 秒。

解決 Locality 問題

Locality 可能會因為不同的原因而下降,而所有這些原因都源於區域的移動:

RegionServer 可能會崩潰,所以它的所有區域會隨機地分佈在叢集中。

Balancer 可能會移動一些區域以更好地分配請求負載。

你可能會擴大或縮小叢集的規模,從而導致區域移動以適應新的規模。

以上三種理由在我們看來都是很普遍的。當 Locality 下降時,你有兩種選擇:

利用 Balancer 將區域移回它們有良好 Locality 的地方。這很少是一個好的選擇。

在本地重寫資料,使用“高度壓實”(major compaction)。

HBase 中的資料最初被寫到記憶體中。當記憶體中的資料達到一定的閾值時,它就會被刷到磁碟上,從而形成不可變的 StoreFile。由於 StoreFile 是不可變的,所以更新和刪除不會對資料進行直接的修改。取而代之的是,它們和其他新的資料一起,被寫入到新的 StoreFile 中。久而久之,你就會建立很多 StoreFile,在讀取時,這些更新需要跟舊資料進行協調。這種即時的稽核會減慢讀取速度,因此會執行後臺維護任務來合併 StoreFile。這些任務被稱為“壓實”(compaction),它們被分成兩種型別:輕度(minor)壓實和高度(major)壓實。

輕度壓實只是將較小的、相鄰的 StoreFile 合併成較大的 StoreFile,以減少在許多檔案之間尋找資料的需要。高度壓實則是重寫一個區域內的所有 StoreFile,將所有更新和刪除的資料合併成一個 StoreFile。

回到 Locality 的定義,我們的目標是確保新的託管伺服器對 StoreFile 中的每個塊都有一個本地副本。透過現有的工具,做到這一點的唯一方法是重寫資料,這要經過上述的塊放置策略。要做到這一點,高度壓實會非常重要,因為它們涉及重寫所有資料。不幸的是,它們也是非常昂貴的:

壓實必須讀取整個區域的資料,過濾掉不相干的資料,然後重寫資料。

讀取資料包括解壓和解碼,這需要 CPU。過濾也需要 CPU。寫入資料需要對新資料進行編碼和壓實,而這需要更多的 CPU。

寫入資料還涉及 3 倍的複製。因此,對 10GB 的區域進行壓實會導致 10GB 的讀取 + 30GB 的寫入。

無論我們的 Locality 目標是什麼,這種成本都會在每一個高度壓實中體現出來,而且會對長尾延遲產生影響。通常,你只想在非工作時間執行高度壓實操作,以儘量減少對終端使用者的影響。然而 ,Locality 在高峰期有最大的影響,所以這意味著在你等待非高峰期壓實工作開始時,可能會有幾個小時的痛苦。

就 Locality 而言,還有一個隱藏的成本——很有可能只有某個區域的部分 StoreFile,有非常糟糕的 Locality。一個高度壓實會壓實所有的 StoreFile,因此,如果一個 10GB 的區域中只有 1GB 是非本地的,那麼從 Locality 的角度來看,這就是浪費了 9GB 的努力。

下面是我們的一個叢集的圖表,我們試圖透過壓實來修復 Locality :

這張圖顯示了一個相對較大的 HBase 叢集,每條線是叢集中單個 Regionserver 的 Locality。在我們耗盡時間之前,我們花了大約 6 個小時,才慢慢將 Locality 提高到一個只有 85% 左右的峰值。幾個小時後,發生了一起事件,使我們的部分工作毀於一旦,而按照叢集的負載方式,我們要等到第二天晚上才能繼續執行壓實操作。

多年來,上述情景一再出現。當我們的規模變大時,我們發現,壓實並不能很好地對 SLO 進行足夠快的 Locality 修復。我們的相隔更好的辦法。

削減成本,將小時變為分鐘

我在 HBase 上斷斷續續地試用了好幾年,而用壓實來解決 Locality 的做法總是令人失望。我很久以前就瞭解過諸如 HDFS Balancer 和 Mover 之類的工具,它們可以進行低級別的塊移動。如果有一個類似的工具,可以利用低級別的塊移動來解決 Locality 問題,這將會很有吸引力,原因有以下幾點:

你只是把位元組從一個主機移到另一個主機,不需要處理壓實、編碼或昂貴的過濾。

你在做 1-1 的轉移,而不是寫新的塊。因此,3 倍的複製成本並沒有發揮作用。

你可以專門選擇只移動非本地塊的複製,而不去考慮其他所有的塊。

透過 dfsadmin -setBalancerBandwidth,可以實時地編輯塊傳輸頻寬,而且可以很好地根據叢集的大小進行擴充套件。

在這個專案中,我希望能看到我們能不能開發出相似的產品,從而提高低延時應用的 Locality。

現有元件

為了建立我們自己的塊 Mover,我必須採取的第一步是瞭解移動塊、讀取塊和計算 Locality 的所有各種元件。這一節有點深奧,所以如果你只想瞭解我們的解決方案和結果,請跳到下一節。

Dispatcher

Balancer 和 Mover 的核心是 Dispatcher。這兩個工具都將 PendingMove 物件傳遞給 Dispatcher,Dispatcher 處理在遠端 DataNodes 上執行 replaceBlock 呼叫。HBase 叢集的典型塊大小為 128MB,而區域通常為多個 GB。因此,一個區域可能有十幾個到幾百個塊,一個 RegionServer 可能承載 50 到數百個區域,而一個叢集可能有數百或數千個 RegionServer。Dispatcher 的工作是並行地執行許多這樣的 replaceBlock 呼叫,當遠端 DataNode 進行資料複製時,它會追蹤進度。這個 Dispatcher 不會處理所選定的要移動的副本,因此我必須建立一個流程來檢測低 Locality 區域,並將這些區域轉化為 PendingMove 物件。

替換塊

遠端 DataNode 從 Dispatcher 接收 replaceBlock 呼叫,其中包括 blockId、源和目標。然後,資料透過代理 DataNode 從源頭流向目標。

當目標 DataNode 完成接收塊時,它會透過 RECEIVED_BLOCK 狀態更新來通知 NameNode。該狀態更新包括對用於託管該塊的 DataNode 的引用。NameNode 會更新其記憶體中的塊記錄,並將舊的 DataNode 標記為 PendingDeletion。在這一點上,呼叫獲取塊的 Locality 將包括新的和舊的 DataNode。

當下一次舊的 DataNode 報告時,NameNode 會迴應:“謝謝,現在請刪除這個塊。” 當 DataNode 完成刪除塊時,它再次向 NameNode 發出 DELETED_BLOCK 狀態更新。當 NameNode 收到這個更新時,該塊被從其記憶體記錄中刪除。在這一點上,對塊 Locality 的呼叫將只包括新的 DataNode。

使用 DFSInputStream 讀取資料

HBase 在開啟每個 StoreFile 時都會建立一個持久的 DFSInputStream,用於服務該檔案的所有 ReadType。PREAD 讀取。當 STREAM 讀入時,它會開啟額外的 DFSInputStream,但 PREAD 對延遲最為敏感。當一個 DFSInputStream 被開啟時,它獲取檔案前幾個塊的當前塊位置。在讀取資料的時候,利用這些塊的位置來決定從何處獲取塊資料。

如果 DFSInputStream 試圖從一個崩潰的 DataNode 提供資料,該 DataNode 將被新增到一個 deadNode 列表,並將其排除在將來的資料請求之外。然後 DFSInputStream 將從 deadNodes 列表以外的其他塊位置之一重試。如果 DFSInputStream 試圖從不再提供塊服務的 DataNode 中獲取塊,也會出現類似的過程。在這種情況下,ReplicaNotFoundException 將被丟擲,並且該 DataNode 也同樣被新增到 deadNode 列表中。

一個單一的 StoreFile 可以從幾 KB 到十幾 GB 不等。由於塊的大小為 128MB,這意味著一個單一的 StoreFile 可能有數百個塊。如果一個 StoreFile 具有較低的 Locality (本地副本很少),這些塊就會分散在叢集的其餘部分。隨著 DataNode 被新增到 deadNode 列表中,你會越來越有可能遇到一個所有位置都在 deadNode 列表中的塊。在這一點上,DFSInputStream 會回退(預設為 3 秒),並從 NameNode 重新獲取所有塊的位置。

除非有一個更系統的問題,否則所有這些錯誤處理將導致暫時性的延遲增加,但不會引起客戶端的異常。不幸的是,延遲的影響是明顯的(尤其是 3 秒的回退),所以這裡還有改進的空間。

基於 Locality 作出報告和決策

在 RegionServer 上開啟 StoreFile 時,RegionServer 會呼叫 NameNode 自身來獲取該 StoreFile 中的所有塊位置。這些位置被累積到每個 StoreFile 的 HDFSBlockDistribution 物件中。這些物件被用來計算 localityIndex,並透過 JMX、RegionServer 的 Web UI 和管理介面報告給客戶。RegionServer 本身也將 localityIndex 用於某些與壓實有關的決策,並且它將每個區域的 localityIndex 報告給 HMaster。

HMaster 是 HBase 程序,執行 HBase 自身的 Balancer。Balancer 試圖根據許多成本函式來平衡整個 HBase 叢集中的區域:讀取請求、寫入請求、儲存檔案數量、儲存檔案大小等等。它試圖平衡的一個關鍵指標是 Locality。

Balancer 的工作方式是透過對叢集成本的計算,假裝將一個區域移動到一個隨機的伺服器,再對叢集成本進行重新計算。若成本降低,則接受這個移動。否則,嘗試不同的移動。為了透過 Locality 進行平衡,你不能簡單地使用 RegionServers 報告的 localityIndex,因為你需要能夠計算出,如果一個區域移動到一個不同的伺服器 ,Locality 成本會是多少。所以 Balancer 也維護它自己的 HDFSBlockDistribution 物件的快取。

LocalityHealer

在瞭解了現有元件之後,我就開始了一項新的守護程式的工作,我們親切地稱之為 LocalityHealer。透過深入研究 Mover 工具之後,我想出了一個設計來實現守護程式的工作方式。這項工作的關鍵在於兩個部分:

發現哪些區域需要癒合。

將這些區域轉換為 Dispatcher 的 PendingMoves。

HBase 提供了一個 Admin#getClusterMetrics() 方法,可以對叢集的狀態進行輪詢。返回值包括一堆資料,其中之一是叢集中每個區域的 RegionMetrics。這個 RegionMetric 包括一個 getDataLocality() 方法,而這正是我們想要監控的。因此,這個守護程序的第一個組成部分是一個監控執行緒,它不斷地輪詢哪些 getDataLocality() 低於我們閾值的區域。

一旦我們知道哪些區域是我們需要癒合的,我們就有一個複雜的任務,就是把它變成一個 PendingMove。一個 PendingMove 需要一個塊、一個源和一個目標。到目前為止,我們所擁有的是一個區域的列表。每個區域由 1 個或多個列族組成,每個列族有 1 個或多個 StoreFile。因此,下一步是在 HDFS 上遞迴搜尋該區域的目錄,尋找 StoreFile。對於每個 StoreFile,我們得到當前塊的位置,為每個塊選擇一個副本作為源,併為每個塊建立一個 PendingMove,目標是當前託管的 RegionServer。我們之所以選擇移動源,是為了保證我們遵循了 BlockPlacementPolicy,並且最大限度地降低了在不同機架之間的網路流量。

一旦我們把所有生成的 PendingMoves 移交給 Dispatcher,就只需要等待它完成。當它完成後,我們再等待一個寬限期,讓我們的 Locality 監視器注意到更新的 Locality 指標,然後重複這整個過程。這個守護程序一直持續這個迴圈,直到它關閉。如果 Locality 是 100%(現在經常是這樣),那麼在監視器執行緒發現下降之前,它會一直閒置。

確保讀取受益於新改進的 Locality

因此,這個守護程序是執行的,並且保證所有 RegionServer 上的 DataNode 都會為這個 RegionServer 上的全部區域託管一個塊副本。但 DFS InputStream 僅在開始時或者在特定的條件下才能獲得塊的位置,而在每一個 StoreFile 中都有一個持久的 DFS InputStream。實際上,如果你繼續追蹤,你也許會發現,如果塊不斷地移動,我們將會發現很多 ReplicaNotFoundException。這實際上是一種痛苦,最好能避免。

使用 V1 快速交付

當我在三月份最初構建這個系統時,就已經決定使用回撥函式來重新整理 HBase 中的讀取資料。我對 HBase 最熟悉,這也是阻力最小的方法。我為 HMaster 和 RegionServer 推送了新的 RPC 端點到我們的內部分叉。當 LocalityHealer 處理完一個區域的所有 StoreFile 後,它就會呼叫這些新的 RPC。RegionServer 的 RPC 特別棘手,需要進行一些複雜的鎖定。最後,它所做的是重新開啟儲存檔案,然後在後臺透明地關閉舊的儲存檔案。這個重新開啟的過程將建立一個新的 DFSInputStream,其中有正確的塊位置,並更新報告的 Locality 值。

自那以後,這個部署的系統取得了非常大的成功,但是我們目前正在進行一次重大的版本升級,需要讓它在新版本中工作。結果發現這個問題要複雜得多,所以我決定嘗試為這部分設計一種不同的方法。本部落格的其餘部分提到了新的 v2 方法,該方法自 10 月份以來已經全面部署。

迭代和適應

在調查我們的主要版本升級時,我發現 HDFS-15199 為 DFSInputStream 添加了一個特性,可以在開啟時週期性地重新讀取塊位置。這似乎正是我想要的,但是在閱讀實現時,我意識到,重新獲取是直接建立在讀取路徑上的,並且無論是否需要,它都會發生。對於這個問題的最初目標,即每隔幾小時重新整理一次位置,這似乎很好,但我最多隻需要每隔幾分鐘重新整理一次。在 HDFS-16262 中,我採納了這個想法,並使其成為非同步的和有條件的。

現在,DFSInputStream 將只在有 deadNode 或任何非本地塊的情況下重新獲取塊的位置。重新獲取的過程發生在任何鎖之外,而新的位置會被快速地與鎖交換到位。這對讀取的影響應該非常小,特別是相對於 DFSInputStream 中現有的鎖的語義。透過使用非同步方法,我覺得它可以在 30 秒的計時器上進行重新整理,這樣我們就能夠很快地適應塊移動。

負載測試

這種非同步重新整理塊位置的新方法,意味著一堆 DFSInputStream 在不同的時間都會影響到 NameNode。如果 Locality 良好,請求的數量應該是零或接近零。一般來說,當你執行 LocalityHealer 的時候,你可以期望你的整體叢集的 Locality 幾乎一直在 98% 以上。所以在正常情況下,我是不會擔心這個問題的。我所關心的一件事是,如果我們發生了一個完全的故障,並且 Locality 幾乎為零,那會是什麼樣子。

我們傾向於分割大型叢集,而不是讓它們變得過於龐大,所以我們最大的叢集有大約 350k 個 StoreFile。在最壞的情況下,所有這些檔案每 30 秒就會向 NameNode 發出一次請求。這意味著大約 12000 次 / 秒。我有一種預感,這不會是一個大問題,因為這些資料完全在記憶體中。我們用 8 個 CPU 和足夠的記憶體來執行我們的 NameNode,以覆蓋塊的容量。

HDFS 有一個內建的 NNThroughputBenchmark,可以準確地模擬出我所期望的工作負載。我首先在我們的 QA 環境中對一個 4 塊 CPU 的 NameNode 進行了測試,使用了 500 個執行緒和 50 萬個檔案。這個單一的負載測試例項能夠推動 22k req/s,但 NameNode 上仍有 30%-40% 的 CPU 閒置時間。這比我們最壞的情況下的兩倍還多,而且非常有希望。

我很好奇 prod 能做什麼,所以我在一個 8 塊 CPU 的 NameNode 上執行它。它很容易就能推送 24k req/s,但我注意到 CPU 幾乎是閒置的。在我使用的測試主機上,我已經達到了該基準的最大吞吐量。我在另一臺主機上針對同一個 NameNode 啟動了另一個併發測試,看到總吞吐量躍升至超過 40k req/s。我繼續擴大規模,最終在超過 60k req/s 時停止。即使在這個水平上,閒置的 CPU 仍然超過 30%~45%。我相信,對我們的 NameNode 來說,這樣的負載不會有任何問題。

減輕痛苦

早期部署的 locality healer 在執行時確實產生了一些小麻煩。這都要追溯到我之前提到的 ReplicaNotFoundException,它有時會導致昂貴的回退。當我第一次做這個工作時,我提交了 HDFS-16155,它增加了指數回退,使我們能夠將 3 秒減少到 50 毫秒。這對解決這個問題很有幫助,使它變得非常容易管理,而且為了長期改善 Locality 也是值得的。

作為我對 HDFS-16262 調查的一部分,我學到了更多的東西,說明了當一個塊被替換後,這個過程是無效的。我在描述上面的元件時簡要介紹了這一點,同時也讓我意識到,我可以完全消除這種痛苦。如果我可以在 NameNode 發出的“請刪除此塊”的訊息周圍增加一個寬限期呢?這個想法的結果就是 HDFS-16261,在那裡我實現了這樣一個寬限期。

有了這個特性,我在我們的叢集上配置了一個 1 分鐘的寬限期。這讓 DFSInputStream 中的 30 秒重新整理時間有足夠的時間來重新整理塊的位置,然後再把塊從它們的舊位置上移走。這就消除了 ReplicaNotFoundException,以及任何相關的重試或昂貴的回退。

在指標和 Balancer 中反映更新的 Locality

這裡的最後一塊拼圖是更新我提到的 localityIndex 指標,以及 Balancer 自己的快取。這一部分由 HBASE-26304 覆蓋。

對於 Balancer,我利用了 RegionServer 每隔幾秒鐘向 HMaster 報告它們的 localityIndex 這一事實。這被用來建立你在呼叫 getClusterMetrics 時查詢的 ClusterMetrics 物件,並且它也被注入到 Balancer 中。這個問題的解決方法很簡單:在注入新的 ClusterMetrics 時,將其與現有的進行比較。對於任何區域,其報告的 localityIndex 發生了變化,這是一個很好的訊號,表明我們的 HDFSBlockDistribution 快取已經過期。把它作為一個訊號來重新整理快取。

接下來是確保 RegionServer 首先報告正確的 localityIndex。在這種情況下,我決定從支援 PREAD 讀取的底層持久化 DFSInputStream 中匯出 StoreFile 的 HDFSBlockDistribution。DFSInputStream 公開一個 getAllBlocks 方法,我們可以輕鬆地將其轉換為 HDFSBlockDistribution。以前,StoreFile 的塊分佈是在 StoreFile 開啟時計算的,而且從未改變。現在我們從底層的 DFSInputStream 派生出來,隨著 DFSInputStream 本身對塊移動的反應(如上所述),這個值會隨著時間自動改變。

結 果

案例研究:跨 7000 臺伺服器管理 Locality

首先,我要讓資料來說明問題。我們在 3 月中旬開始向一些問題較多的叢集推出 LocalityHealer,並在 5 月初完成向所有叢集的推出。

該圖表顯示了從 2021 年 3 月 1 日到 2021 年 6 月 1 日,我們所有生產叢集的第 25 百分點的 Locality 值。在 3 月之前,我們看到許多下降到 90% 以下,一些叢集持續下降到幾乎 0%。隨著我們開始推出 LocalityHealer,這些下降變得不那麼頻繁和嚴重。一旦 LocalityHealer 被完全推出,它們就完全被消除了。

我們喜歡把 Locality 保持在 90% 以上,但注意到當 Locality 低於 80% 時,真正的問題就開始顯現出來。另一種看問題的方法是,在一個區間內 ,Locality 低於 80% 的 RegionServer 的數量。

這張圖顯示了與上面相同的時間段,你可以看到我們曾經有數百個伺服器在任何特定時刻都低於 80% 的 Locality。從 5 月初開始,這個數值一直為 0,並保持到今天。

這些圖表最好的一點是它是自動的。遺憾的是,由於 Locality ,我們沒有警報量的指標標準,但 HBase 團隊的任何人都可以告訴你,他們曾經幾乎每天都會因為某個叢集的 Locality 而被呼喚。這一直是一個令人討厭的警報,因為你唯一能做的就是啟動需要數小時才能完成的高度壓實。隨著時間的推移,我們降低了 Locality 警報的敏感性,以避免警報疲勞,但這對叢集的穩定性產生了負面影響。

有了 LocalityHealer,我們就不再考慮 Locality 問題了。我們可以使我們的警報非常敏感,但它們永遠不會發出。Locality 總是接近 100%,我們可以專注於其他的操作問題或價值工作。

案例研究:快速解決由於 Locality 不佳造成的超時

這裡還有一個關於僅承載 15TB 的特定叢集的示例。你可以看到在時間線的開始附近,棕色的線是一個新的伺服器,它以 Locality 為 0 啟動。在時間耗盡之前,壓實花了大約 7 個小時才達到大約 75% 的 Locality。那天晚上晚些時候,增加了更多的伺服器,但對它們開始壓實為時已晚(由於其他任務,如備份,在凌晨執行)。

當我們在第二天達到流量高峰時,HBase 團隊被一個產品團隊呼喚,他們遇到了超時的問題,導致客戶出現 500 毫秒的情況。此時,HBase 團隊有兩個選擇:

啟動壓實,這將進一步增加延遲,並需要 8 個小時以上的時間來解決這個問題。

試試新部署的 LocalityHealer,它還沒有作為一個守護程序執行。

他們選擇了後者,這使得整個叢集的 Locality 在 3 分鐘內達到 100%。放大來看,你可以看到下面的影響。

在本例下,我透過繪製單個平均位置(左軸,藍線)來總結第一個圖表。我把叢集的第 99 個百分點的延遲疊加在一起(右軸,黃線)。在整個上午,我們看到了越來越多的超時(500 毫秒,用灰色虛線表示)。我們知道,當我們達到流量高峰時,這將變得非常關鍵,所以在 11:30 運行了 LocalityHealer。Locality 躍升至 100% 後,立即減少了延遲波動和超時的情況。

結 論

LocalityHealer 改變了 HubSpot 在管理快速增長的叢集的關鍵效能指標方面的遊戲規則。我們目前正在努力將這項工作貢獻給開源社群,在 HBASE-26250 這個總的問題下。

這就是我們的資料基礎設施團隊每天都在處理的工作。

作者介紹:

Bryan Beaudreault,HubSpot 首席工程師。曾在 HubSpot 領導過多個團隊,包括建立資料基礎設施團隊,並帶領 HubSpot 在高度多租戶的雲環境下,在多個數據儲存中實現了 99。99% 的正常執行時間。後來回到產品方面,致力於為 HubSpot 即將推出的一款產品實現對話自動化。

https://product。hubspot。com/blog/healing-hbase-locality-at-scale

TAG: LocalityStoreFile叢集HBase我們