作者:來自 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會把相同列族的列盡量放在同一臺機器上,不同列族的列分布在不同的機器上。
一般情況下,從客戶端發起請求讀取數據,到數據返回大致有如下幾步:
-
客戶端從ZooKeeper中獲取meta表所在regionServer節點信息。
-
客戶端訪問meta表所在的regionServer節點,獲取region所在節點信息。
-
客戶端訪問具體region所在regionServer,找到對應的region。
-
首先從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底層機制的調參、從客戶端調用機制的優化等多方面挖掘,探索更適合業務的方式,希望本文中提到的一些優化方案能給讀者帶來一些啟發。