HBase 在統一內容平臺業務的最佳化實踐

vivo互联网技术發表於2024-07-11

作者:來自 vivo 網際網路伺服器團隊-Leng Jianyu、Huang Haitao

HBase是一款開源高可靠性、擴充套件性、高效能和靈活性的分散式非關係型資料庫,本文圍繞資料庫選型以及使用HBase的痛點展開,從四個方面對HBase的使用進行最佳化,取得了一些不錯效果。

一、業務簡介

統一內容平臺主要承擔vivo內容生態的內容稽核、內容理解、內容智作和內容分發等核心功能,透過聚合全網內容,建設行業級的業務中臺和內容生態,為上下游提供優質可靠的一站式服務,目前服務的業務方包括影片業務、泛資訊流業務等。

圖片

作為一個內容中臺,每天要新增儲存大量圖文和影片內容來滿足分發的需要。與此同時,對這些內容加工處理的資料也都需要儲存,包括基礎資訊,分類標籤資訊,稽核資訊等等。而且不僅僅是需要儲存的資料量級很大,對資料的讀取和寫入操作也非常頻繁,當前對資料庫的讀寫主要集中在兩個方面:

  • 核心鏈路的內容處理包含了大量對內容特徵資訊的讀取和寫入操作;

  • 對外提供的查詢服務會產生很多回源查詢資料庫的操作。

在經過多年累積後,當前儲存的資料量越變越大,並且可以預見資料量級會不斷膨脹下去,如何選擇一種可靠的儲存選型,來保證服務穩定性和擴充套件性成了當前專案架構的重中之重。

二、存在的問題

在選用HBase之前,內容平臺核心資料的儲存以Mongodb為主要儲存選型,但是在日常的使用中,發現儲存側存在如下一些痛點問題

  • 核心資料量大,大表有20TB 以上,總儲存 60TB 以上,Mongodb 的儲存架構,無法滿足良好擴充套件性要求;

  • 訪問查詢流量大,需要承載智慧push、泛資訊流、影片推薦側的大回源查詢流量,保持查詢介面高效能;

  • 為了維護Mongodb的穩定性,需要定期切換 Mongodb 資料庫主從節點,重做例項,需要運維長期投入,維護成本高。

所以我們迫切需要尋找一個更適合當前場景的資料庫來滿足業務請求量和儲存大小日益增長的需求,並且要求具備高效能、高穩定、可擴充套件、低維護成本的特性。

三、儲存選型

經過一些調研後發現HBase的一些特效能很好地滿足當前場景的要求。

(1)高效能

HBase採用的是Key/Value的列式儲存方式(對比Mongodb是行式資料庫),同一個列族的資料存放在一個檔案中,隨著檔案的增長會進行分裂,分散到其他機器上,所以即使隨著資料量增大,也不會導致讀寫效能的下降。HBase具備毫秒級的讀寫效能,如果寫入資料量大,還可以使用bulkload匯入資料的方式進行高效入庫。

(2)高擴充套件性、高容錯性

HBase的儲存是基於Hadoop的,Hadoop實現了一個分散式檔案系統(HDFS),HDFS的副本機制使得其具有高容錯性的特點,並且HDFS的Federation機制使得其具有高擴充套件性。基於Hadoop意味著HBase與生俱來的超強擴充套件性和高容錯性。

(3)強一致性

HBase的資料是強一致性的,從CAP理論來看,HBase是屬於CP的。CAP 定理表明,在存在網路分割槽的情況下,一致性和可用性必須二選一。HBase在寫入資料時,先把操作的記錄寫入到預寫日誌中(Write-ahead log,WAL),然後再被載入到Memstore的。就算某個節點機器宕掉了,由於WAL的資料是儲存在HDFS上的,所以資料並不會丟失,後續可以透過讀取預寫日誌恢復內容。

(4)列值支援多版本

HBase的多版本特性可以針對某個列族控制列值的版本數,預設是1,即每個key儲存一個版本,同一個rowkey的情況下,後面的列值會覆蓋前面的列值。可以動態修改列族的版本數,每個版本使用時間戳進行標記,預設是寫入時間作為該版本的時間戳,也可以在寫入時指定時間戳。

綜合以上特性,HBase是非常適合當前專案對資料庫選型的要求。

四、HBase 最佳化實踐

隨著HBase在整個專案中逐步擴大使用,也發現了一些使用規範問題以及一些查詢的效能問題。比如查詢毛刺比較多、夜間Compact期間耗時比較高、流量高峰期的時候少量請求會有延遲。針對這些問題,我們從下面四個方面,對HBase的使用進行了最佳化。

4.1 叢集升級

剛開始使用HBase的時候,我們使用的HBase叢集版本是1.2版本的,此版本存在諸多弊端,如:RIT(Region-In-Transition)問題頻發、請求延時突刺、建刪錶速度慢、meta 表穩定性差、節點故障恢復速度慢等問題。我們在使用過程發現的主要問題是響應時間突刺問題,該問題會導致我們實時查詢介面在回源時超時較多,導致介面的響應時間有突刺被下游業務方熔斷,影響業務查詢。與HBase團隊討論與評估後,決定將業務使用的叢集升級到HBase 2.4.8 版本。該版本在公司較多的業務場景中已經得到驗證,可以解決大部分1.2.0版本存在的痛點問題,可以大幅提升讀寫效能,有效降低讀毛刺,單機處理效能的提升可減少20%左右機器成本。

下面是叢集升級後的讀寫平均耗時對比圖。可以看到在升級之前,平均響應時間經常會有一些突刺,最高能達到超過10s,升級後幾乎不存在這麼高的平均響應時間突刺,能保持在10ms以下,偶爾較高也是幾十毫秒級別。

圖片

升級前

圖片

升級後

4.2 連線池使用和連線預熱

HBase Connection 建立物件並不是簡單對應一個socket連線,需要與Zookeeper以及HMaster、RegionServer都建立連線,所以該過程是一個非常耗資源的過程,一般只建立一個 Connection 例項,其它地方共享該例項。在Connection初始化之後,用connection下的getTable方法實現對錶格的連線。為了減少與表格連線帶來的網路開銷,我們建立了對不同表格的連線池來管理客戶端和服務端的連線。大致流程圖如下圖所示。

透過建立連線池,帶來了以下三點優勢:

(1)對錶和表之間進行了連線資源隔離,避免互相影響;

(2)對連線實現了複用,減少了建立連線的網路開銷;

(3)防止突增的流量帶來的影響,實現平滑處理流量。

此外,圖中可以看到,在程式啟動階段,可以實現對HBase表連線的預熱,提前建立對錶格的連線,可以有效避免在程式啟動階段由於大量建立連線導致讀寫的響應時間變長,影響整體效能。

連線池透過使用Apache Commons Pool提供的GenericObjectPool通用物件池來實現,GenericObjectPool包含豐富的配置選項,能夠定期回收空閒物件,並且支援物件驗證,具有強大的執行緒安全性和可擴充套件性。然後將不同表格的連線池物件放到本地快取LoadingCache中,LoadingCache底層透過LRU演算法實現對最久遠且沒有使用的資料的淘汰,保證沒有使用的表格連線能及時釋放。透過使用第三方的物件池和本地快取,建立了對HBase表格的連線池,並且實現了預載入,減少了一些讀寫HBase的開銷,降低了讀寫耗時,對於剛啟動服務時的讀寫突刺帶來了一些改善。

圖片

4.3 按列讀取

HBase建表的時候是不需要確定列的,因為列是可變的,它非常靈活,唯一需要確定的就是列族。一張表的很多屬性比如過期時間、資料塊快取以及是否壓縮等都是定義在列族上,而不是定義在表上或者列上,這一點做法跟以往的資料庫有很大的區別。同一個表裡的不同列族可以有完全不同的屬性配置,但是同一個列族內的所有列都會有相同的屬性。一個沒有列族的表是沒有意義的,因為列必須依賴列族而存在,所以在HBase中一個列的名稱前面總是帶著它所屬的列族。列族存在使得HBase會把相同列族的列儘量放在同一臺機器上,不同列族的列分佈在不同的機器上。

一般情況下,從客戶端發起請求讀取資料,到資料返回大致有如下幾步:

  1. 客戶端從ZooKeeper中獲取meta表所在regionServer節點資訊。

  2. 客戶端訪問meta表所在的regionServer節點,獲取region所在節點資訊。

  3. 客戶端訪問具體region所在regionServer,找到對應的region。

  4. 首先從blockCache中讀取資料,存在則返回,不存在則去memstore中讀取資料,存在則返回,不存在去storeFile(HFile)中讀取資料,存在會先將資料寫入到blockCache中,然後返回資料,不存在則返回空。

簡單的示意圖如下所示:

圖片

整個過程中如果讀取欄位過多,或者欄位長度過大,那麼返回所有列的資料會導致大量無效的資料傳輸,進而導致叢集網路頻寬等系統資源被大量佔用,必然導致讀取效能降低,所以需要減少一些不必要欄位的查詢。

Get類是HBase官方提供的查詢類,在該類中主要有以下幾個方法提供來實現減少欄位讀取:

  • addFamily:新增要取出的列族;

  • addColumn:新增要取出的列;

  • setTimeRange:設定要取出的版本範圍;

  • setMaxVersions:設定取出版本數量。

當前專案中沒有使用到HBase的版本範圍和版本數量的特性,但是主要場景使用的表欄位都比較多(如內容的基本屬效能達到上百個欄位),或者欄位的大小都比較大(如內容解析的一些向量欄位),原本在查詢時,都是直接讀取所有欄位,導致很多欄位其實不需要使用也被一直讀取,浪費效能。透過改用按列讀取的方式來實現不同場景下不同欄位的查詢,避免了超過一半無用欄位的返回,平均響應時間也下降了一些。

4.4 compact最佳化

HBase是基於LSM樹儲存模型的分散式NoSQL資料庫。LSM樹相比於普遍使用在各種資料庫的B+樹來說,能夠獲得較高隨機寫效能的同時,也能保持可靠的隨機讀效能。在進行讀請求的時候,LSM樹要把多個子樹(類似B+樹結構)進行歸併查詢, 因此歸併查詢的子樹數越少,查詢的效能就越高。當MemStore超過閥值的時候,就要flush到HDFS上生成一個HFile。因此隨著不斷寫入,HFile的數量將會越來越多,根據前面所述,HFile數量過多會降低讀效能。為了避免對讀效能的影響,可以對這些HFile進行compact操作,把多個HFile合併成一個HFile。compact操作需要對HBase的資料進行多次的重新讀寫,因此這個過程會產生大量的IO。可以看到compact操作的本質就是以IO操作換取後續的讀效能的提高。

圖片

HBase的compact是針對HRegion的HFile檔案進行操作的。compact操作分為major和minor兩種。major compaction會把所有的HFile都compact為一個HFile,並同時忽略標記為delete的KeyValue(被刪除的KeyValue只有在compact過程中才真正被"刪除"),可以想象major compaction會產生大量的IO操作,對HBase的讀寫效能產生影響。minor則只會選擇數個HFile檔案compact為一個HFile,minor的過程一般較快,而且IO相對較低。在業務高峰期間,都會禁止major操作,只在業務空閒的時段定時執行。

hbase.hstore.compaction.throughput.higher.bound是HBase中控制HFile檔案合併(compaction)速度的引數之一。它指定了一個HFile檔案每秒最大合併資料大小的上限,以位元組為單位。如果一個HFile檔案的大小超過了這個上限,HBase就會嘗試將其分裂成較小的檔案來加快合併速度。透過調整該引數,可以控制HBase在什麼條件下開始嘗試合併HFile檔案。較小的值會導致更頻繁的檔案合併,也會降低HBase的效能。較大的值則可能導致HFile檔案的大小增長過快,從而影響讀取效能。

hbase.hstore.compaction.throughput.lower.bound也是HBase中控制HFile檔案合併速度的引數之一。它指定了一個HFile檔案每秒最小合併資料大小的下限,以位元組為單位。當合並速度達到這個下限時,HBase會停止合併更小的HFile檔案,而等待更多的資料到達之後再進行合併操作。與higher.bound引數相比,lower.bound引數更加影響檔案合併頻率和效能。過高的值會導致較少的檔案合併和較大的HFile檔案,這會影響讀取效能和寫入併發性。反之,過低的值會導致過於頻繁的檔案合併,從而佔用過多的CPU和磁碟I/O資源,影響整個HBase叢集的效能。針對Compact對業務耗時的影響,我們對Compact 操作進行了限流,並且透過多次測試調整Compact上文提到的兩個限流的閾值,取得了非常好的效果。Compact期間的耗時下降了70%y以上。下圖展示了採取限流前後的耗時對比。

圖片

4.5 欄位級版本管理

除了上述提到的最佳化點,我們也探索了一些HBase的其它特性,以備將來用來最佳化其他方面。上文提到,透過對HBase進行按列讀取資料,可以減少get查詢的時間,通常意義來講,列(也就是每個欄位)已經是每條資料的最基本單位了,但是HBase中的資料粒度比傳統資料結構更細一級,同一個位置的資料還細分成多個版本,一個列上可以儲存多個版本的值,多個版本的值被儲存在多個單元格里面,多個版本之間用版本號( version)來區分。所以,唯一確定一條結果的表示式應該是行鍵:列族:列:版本號(rowkey:column family:column:version)。不過,版本號通常是可以省略的,如果寫入時不寫版本號,每個列或者單元格的值都被賦予一個時間戳,這個時間戳預設是由系統制定的,當然寫入時也可以由使用者顯式指定具體的版本號。在查詢時如果不指定版本號,HBase預設獲取最後一個版本的資料返回給你。當然也可以指定版本號返回需要的其他版本的資料。簡單的示意圖如下所示:

圖片

同時HBase為了避免資料存在過多的版本造成不必要的負擔,HBase提供了兩種資料版本的回收方式,一是按照數量維度,儲存最後的n個版本,二是按照時間維度,儲存最近一段時間的版本資料,比如儲存一個月。透過多版本同時儲存,對於一些有時序要求的場景非常友好,透過指定版本的時間戳,可以避免在已經更新了新資料的情況下,被舊資料覆蓋。當前我們建表是都是隻指定了一個版本,使用也都是用的以時間戳為版本號的預設版本,沒有采取版本管理的措施,不同單元格可以記錄多版本的特性可以考慮應用於欄位更新時記錄下多個版本的資料,在不影響讀寫效率的情況下,方便後續在沒有相關日誌的情況下,回溯最近幾次更新的值,並且可以防止誤操作或資料損壞,因為使用者可以恢復到之前的版本資料。此外我們的系統中存在一些透過訊息佇列非同步更新場景,此時可以使用訊息體中的時間戳作為當前版本號,這樣可以在多執行緒消費時,也能保證消費的時序性,因為低版本的版本號無法更新高版本的版本號。

五、總結

本文在對統一內容平臺在資料庫選型分析和最佳化的基礎上,簡要介紹了HBase在實際使用中的一些最佳化方案,經最佳化後,專案整體讀取和寫入效能都有比較明顯的提升,較好的保障了統一內容平臺業務的穩定性,並且大大降低了業務側的運維成本。Hbase本身就具備強大的功能,在大資料領域有獨有的優勢,但是在不同的業務場景,對於HBase的要求也是不一樣的,可以結合具體的實際情況,從使用的資料庫版本、從HBase底層機制的調參、從客戶端呼叫機制的最佳化等多方面挖掘,探索更適合業務的方式,希望本文中提到的一些最佳化方案能給讀者帶來一些啟發。

相關文章