亞寵展、全球寵物產業風向標——亞洲寵物展覽會深度解析
1245
2022-05-29
華為云上的NoSQL數據庫服務CloudTable,基于Apache HBase,提供全托管式集群服務,集成了時序數據庫OpenTSDB與時空數據庫GeoMesa,在TB/PB級別的海量數據背景下,可提供ms級查詢以及千萬級TPS,點我了解詳情。
在這個章節,我們繼續探討HBase Compaction,主要是想理清這其中的一些"道"與"術":
“道“:HBase Compaction要解決的本質問題是什么?
“術“:針對HBase Compaction的問題本質,HBase有哪些具體的改進/優秀實踐?
Compaction會導致寫入放大
我們先來看看Facebook在Fast 14提交的論文《Analysis of HDFS Under HBase: A Facebook Messages Case Study》所提供的一些測試結論(在我之前寫的一篇文章《從HBase中移除WAL?3D XPoint技術帶來的變革》已經提到過):
在Facebook Messages系統中,業務讀寫比為99:1,而最終反映到磁盤中,讀寫比卻變為了36:64。WAL,HDFS Replication,Compaction以及Caching,共同導致了磁盤寫IO的顯著放大。
雖然距離論文發表已經過去幾年,但問題本質并未發生明顯變化。尤其是在一個寫多讀少的應用中,因Compaction帶來的寫放大(Write Amplification)尤其明顯,下圖有助于你理解寫放大的原因:
Write Amplification Caused By Compaction
隨著不斷的執行Minor Compaction以及Major Compaction,可以看到,這條數據被反復讀取/寫入了多次,這是導致寫放大的一個關鍵原因,這里的寫放大,涉及到網絡IO與磁盤IO,因為數據在HDFS中默認有三個副本。
Compaction的本質
我們先來思考一下,在集群中執行Compaction,本質是為了什么?容易想到如下兩點原因:
減少HDFS中的HFile文件數量,減少文件句柄數量,降低讀取時延
Major Compaction可以幫助清理集群中不再需要的數據(過期數據,被標記刪除的數據,版本數溢出的數據)
很多HBase用戶在集群中關閉了自動Major Compaction,為了降低Compaction對IO資源的搶占,但出于清理數據的需要,又不得不在一些非繁忙時段手動觸發Major Compaction,這樣既可以有效降低存儲空間,也可以有效降低讀取時延。
而關于如何合理的執行Compaction,我們需要結合業務數據特點,不斷的權衡如下兩點:
避免因文件數不斷增多導致讀取時延出現明顯增大
合理控制寫入放大
HFile文件數量一定會加大讀取時延嗎?也不一定,因為這里與RowKey的分布特點有關。
我們通過列舉幾個典型場景來說明一下不同的RowKey分布,為了方便理解,我們先將一個Region的RowKey Range進一步劃分成多個Sub-Range,來說明不同的RowKey分布是如何填充這些Sub-Ranges的:
Region Sub-Ranges
下面的不同行代表不同的時間點(我們使用不斷遞增的時間點T1,T2,T3,T4,來描述隨著時間演變而產生的RowKey分布變化):
分布A
在這個Region中,某一個時間段只填充與之有關的一個Sub-Range,RowKey隨著時間的演進,整體呈現遞增趨勢。但在填充每一個Sub-Range的時候,又可能有如下兩種情形(以Sub-Range1的填充為例,為了區別于T1~T4,我們使用了另外4個時間點Ta~Td):
分布A-a
這種情形下,RowKey是嚴格遞增的。
分布A-b
這種情形下,RowKey在Sub-Range1的范圍內是完全隨機的。
下面則是一種隨機RowKey的場景,也就是說,每一個時間點產生的數據都是隨機分布在所有的Sub-Range中的:
分布B
對于分布A-a來說,不同的HFile文件在RowKey Range(該HFile文件所涉及到的最小數據RowKey與最大數據RowKey構成的RowKey區間)上并不會產生重疊,如果要基于RowKey讀取一行數據,只需要查看一個文件即可,而不需要查看所有的文件,這里完全可以通過優化讀取邏輯來實現。即使不做Compaction,對于讀取時延的影響并不明顯(當然,從降低文件句柄數量,降低HDFS側的小文件數量的維度來考慮,Compaction還是有意義的)。
對于分布B來說,如果有多個HFiles文件,如果想基于RowKey讀取一行數據,則需要查看多個文件,因為不同的HFile文件的RowKey Range可能是重疊的,此時,Compaction對于降低讀取時延是非常必要的。
調研自己的業務模型
在選擇一個合理的Compaction策略之前,應該要首先調研自己的業務模型,下面是一些參考維度:
1.寫入數據類型/單條記錄大小
是否是KB甚至小于KB級別的小記錄?還是MB甚至更大的圖片/小文件數據?
2.業務讀寫比例
3.隨著時間的不斷推移,RowKey的數據分布呈現什么特點?
4.數據在讀寫上是否有冷熱特點?
是否只讀取/改寫最近產生的數據?
5.是否有頻繁的更新與刪除?
6.數據是否有TTL限制?
7.是否有較長時間段的業務高峰期和業務低谷期?
幾種Compaction策略
HBase中有幾種典型的Compaction策略,來應對幾類典型的業務場景:
Stripe Compaction
它的設計初衷是,Major Compaction占用大量的IO資源,所以很多HBase用戶關閉了自動觸發的Major Compaction,改為手動觸發,因為Major Compaction依然會被用來清理一些不再需要的數據。
隨著時間的推移,Major Compaction要合并的文件總Size越來越大,但事實上,真的有必要每一次都將所有的文件合并成一個大的HFile文件嗎?尤其是,不斷的將一些較老的數據和最新的數據合并在一起,對于一些業務場景而言根本就是不必要的。
因此,它的設計思路為:
將一個Region劃分為多個Stripes(可以理解為Sub-Regions),Compaction可以控制在Stripe(Sub-Region)層面發生,而不是整個Region級別,這樣可以有效降低Compaction對IO資源的占用。
那為何不直接通過設置更多的Region數量來解決這個問題?更多的Region意味著會加大HBase集群的負擔,尤其是加重Region Assignment流程的負擔,另外,Region增多,MemStore占用的總體內存變大,而在實際內存無法變大的情況下,只會使得Flush更早被觸發,Flush的質量變差。
新Flush產生的HFile文件,先放到一個稱之為L0的區域,L0中Key Range是Region的完整Key Range,當對L0中的文件執行Compaction時,再將Compaction的結果輸出到對應的Stripe中:
Stripe Compaction
HBase Document中這么描述Stripe Compaction的適用場景:
Large regions. You can get the positive effects of smaller regions without additional overhead for MemStore and region management overhead.
Non-uniform keys,?such as time dimension in a key. Only the stripes receiving the new keys will need to compact.?Old data will not compact as often, if at all
在我們上面列舉的幾種RowKey分布的場景中,分布A(含分布A-a,分布A-b)就是特別適合Stripe Compaction的場景,因為僅僅新寫入數據的Sub-Range合并即可,而對于老的Sub-Range中所關聯的數據文件,根本沒有必要再執行Compaction。
Stripe Compaction關于Sub-Region的劃分,其實可以加速Region Split操作,因為有的情形下,直接將所有的Stripes分成兩部分即可。
Date Tiered Compaction
我們假設有這樣一種場景:
新數據的產生與時間有關,而且無更新、刪除場景
讀取時通常會指定時間范圍,而且通常讀取最近的數據
在這種情形下,如果將老數據與新數據合并在一起,那么,指定時間范圍讀取時,就需要掃描一些不必要的老數據:因為合并后,數據按RowKey排序,RowKey排序未必與按照數據產生的時間排序一致,這使得新老數據交叉存放,而掃描時老數據也會被讀到。
這是Date Tiered Compaction的設計初衷,Date Tiered Compaction在選擇文件執行合并的時候,會感知Date信息,使得Compaction時,不需要將新老數據合并在一起。這對于基于Time Range的Scan操作是非常有利的,因為與本次Scan不相關的文件可以直接忽略。
什么是Time Range Based Scan?
HBase的Scan查詢,通常都是指定RowKey的Range的,但HBase也支持這樣一類查詢:通過指定一個起始的Timestamp,掃描出所有的落在此Timestamp Range中的所有數據,這就是Time Range Based Scan。
可以參考接口:Scan#setTimeRange(long minStamp, long maxStamp)
Time Range Based Scan可以用來實現針對HBase數據表的增量數據導出/備份能力。
容易想到,時序數據就是最典型的一個適用場景。但需要注意的是,如下場景并不適合使用Date Tiered Compaction:
讀取時通常不指定時間范圍
涉及頻繁的更新與刪除
寫入時主動指定時間戳,而且可能會指定一個未來的時間戳
基于bulk load加載數據,而且加載的數據可能會在時間范圍上重疊
MOB Compaction
能否使用HBase來存儲MB級別的Blob(如圖片之類的小文件)數據?
這是很多應用面臨的一個基礎問題,因為這些數據相比于普通的存儲于HBase中的結構化/半結構化數據顯得過大了,而如果將這些數據直接存儲成HDFS中的獨立文件,會加重HDFS的NameNode的負擔,再者,如何索引這些小文件也是一個極大的痛點。
當然,也有人采用了這樣的方式:將多個小文件合并成HDFS上的一個大文件,這樣子可以減輕HDFS的NameNode的負擔,但需要維護每一個小文件的索引信息(文件名以及每一個小文件的偏移信息)。
如果存這些這些小文件時,像普通的結構化數據/半結構化數據一樣,直接寫到HBase中,會有什么問題?這樣子多條數據可以被合并在較大的HFile文件中,減輕了NameNode的負擔,同時解決了快速索引的問題。但基于前面的內容,我們已經清楚知道了Compaction帶來的寫放大問題。試想一下,數MB級別的Blob數據,被反復多次合并以后,會帶來什么樣的影響?這對IO資源的搶占將會更加嚴重。
因此,HBase的MOB特性的設計思想為:將Blob數據與描述Blob的元數據分離存儲,Blob元數據采用正常的HBase的數據存儲方式,而Blob數據存儲在額外的MOB文件中,但在Blob元數據行中,存儲了這個MOB文件的路徑信息。MOB文件本質還是一個HFile文件,但這種HFile文件不參與HBase正常的Compaction流程。僅僅合并Blob元數據信息,寫IO放大的問題就得到了有效的緩解。
MOB Compaction也主要是針對MOB特性而存在的,這里涉及到數據在MOB文件與普通的HFile文件之間的一些流動,尤其是MOB的閾值大小發生變更的時候(即當一個列超過預設的配置值時,才被認定為MOB),本文暫不展開過多的細節。
在HBase社區中,MOB特性(HBASE-11339)一直在一個獨立的特性分支中開發的,直到2.0版本才最終合入進來(華為的FusionInsight的HBase版本中,以及華為云的CloudTable的HBase版本中,都包含了完整的MOB特性)。
Default Compaction
就是默認的Compaction行為,2.0版本和舊版本中的行為沒有什么明顯變化。
所謂的Default Compaction,具有更廣泛的適用場景,它在選擇待合并的文件時是在整個Region級別進行選擇的,所以往往意味著更高的寫IO放大。
在實際應用中,應該結合自己的應用場景選擇合適的Compaction策略,如果前幾種策略能夠匹配自己的應用場景,那么應該是優選的(這幾個策略的質量狀態如何尚不好判斷,建議結合業務場景進行實測觀察),否則則選擇Default Compaction。
如果上述幾種Compaction策略都無法很好的滿足業務需求的話,用戶還可以自定義Compaction策略,因為HBase已經具備良好的Compaction插件化機制。
如何選擇待合并的文件
無論哪種Compaction策略,都涉及一個至關重要的問題:
“如何選擇待合并的文件列表”
Major Compaction是為了合并所有的文件,所以,不存在如何選擇文件的問題。
選擇文件時,應該考慮如下幾個原則:
選擇合理的文件數量
如果一次選擇了過多的文件: 對于讀取時延的影響時間范圍可能比較長,但Compaction產生的寫IO總量較低。
如果一次選擇了較少的文件: 可能導致過于頻繁的合并,導致寫IO被嚴重放大。
選擇的文件在時間產生順序上應該是連續的,即應該遵循HBase的Sequence ID的順序
HFiles Sorted by Sequence ID Order
這樣子,HBase的讀取時可以做一些針對性的優化,例如,如果在最新的文件中已經讀到了一個RowKey的記錄,那就沒有必要再去看較老的文件。
在HBase中,有一種”歷史悠久”的選擇文件的策略,名為ExploringCompactionPolicy,即使在最新的版本中,該策略依然在協助發揮作用,它選擇文件的原理如下(下面的圖片和例子源自HBASE-6371):
將HFile文件從老到新的順序排序,通常,舊文件較大,因為舊文件更可能是被合并過的文件。
每一次需要從文件隊列中選取一個合適的開始文件位置,通過如下算法:
f[start].size <= ratio * (f[start+1].size + ….. + f[end – 1].size)
找到一個滿足條件的開始位置,以及滿足條件的文件組合。
舉例:假設ratio = 1.0:
1. 如果候選文件[1200, 500, 150, 80, 50, 25, 12, 10],則最終選擇出來的需要合并的文件列表為[150, 80, 50, 25, 12, 10]
Compaction File Selection
2. 如果候選文件[1200, 500, 150, 80, 25, 10],則選不出任何文件。
每一次Minor Compaction所涉及的文件數目都有上下限。如果超過了,會減去一些文件,如果小于下限,則忽略此次Compaction操作。待選擇的文件也有大小限制,如果超出了預設大小,就不會參與Compaction。這里可以理解為:Minor Compaction的初衷只是為了合并較小的文件。另外,BulkLoad產生的文件,在Minor Compaction階段會被忽略。
RatioBasedCompactionPolicy曾一度作為主力文件選擇算法沿用了較長的時間,后來,出現了一種ExploringCompactionPolicy,它的設計初衷為:
RatioBasedCompactionPolicy雖然選擇出來了一種文件組合,但其實這個文件組合并不是最優的,因此它期望在所有的候選組合中,選擇一組性價比更高的組合,性價比更高的定義為:文件數相對較多,而整體大小卻較小。這樣,即可以有效降低HFiles數量,又可能有效控制Compaction所占用的IO總量。
也可以這么理解它們之間的一些不同:
RatioBasedCompactionPolicy僅僅是在自定義的規則之下找到第一個”可行解“即可,而ExploringCompactionPolicy卻在盡可能的去尋求一種自定義評價標準中的”最優解“。
另外,需要說明的一點:ExploringCompactionPolicy在選擇候選組合時,正是采用了RatioBasedCompactionPolicy中的文件選擇算法。
更多的一些思考
Compaction會導致寫入放大,前面的內容中已經反復提及了很多次。在實際應用中,你是否關注過Compaction對于查詢毛刺的影響(查詢時延總是會出現偶發性的陡增)?
關于Compaction的參數調優,我們可能看到過這樣的一些建議:盡可能的減少每一次Compaction的文件數量,目的是為了減短每一次Compaction的執行時間。這就好比,采用Java GC算法中的CMS算法時,每一次回收少量的無引用對象,盡管GC被觸發的頻次會增大,但可以有效降低Full GC的發生次數和發生時間。
但在實踐中,這可能并不是一個合理的建議,例如,HBase默認的觸發Minor Compaction的最小文件數量為3,但事實上,對于大多數場景而言,這可能是一個非常不合理的默認值,在我們的測試中,我們將最小文件數加大到10個,我們發現對于整體的吞吐量以及查詢毛刺,都有極大的改進,所以,我們建議,Minor Compaction的文件數量應該要結合實際業務場景設置合理的值。另外,在實踐中,合理的限制Compaction資源的占用也是非常關鍵的,如Compaction的并發執行度,以及Compaction的吞吐量以及網絡帶寬占用等等。
另外,需要關注到的一點:Compaction會影響Block Cache,因為HFile文件發生合并以后,舊HFile文件所關聯的被Cache的Block將會失效。這也會影響到讀取時延。HBase社區有一個問題單(HBASE-20045),試圖在Compaction時緩存一些最近的Blocks。
在Facebook的那篇論文中,還有一個比較有意思的實踐:
Local Compaction
他們將Compaction下推到存儲層(HDFS)執行,這樣,每一個DateNode在本地合并自己的文件,這樣可以降低一半以上的網絡IO請求,但本地磁盤IO請求會增大,這事實上是用磁盤IO資源來換取昂貴的網絡IO資源。在我們自己的測試中,我們也發現,將Compaction下推到HDFS側執行,能夠明顯的優化讀寫時延毛刺問題。
hbase
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。