HBase - 数据写入流程解析
本文由??網(wǎng)易云?發(fā)布。
作者:范欣欣
本篇文章僅限內(nèi)部分享,如需轉(zhuǎn)載,請聯(lián)系網(wǎng)易獲取授權(quán)。
?
?
眾所周知,HBase默認適用于寫多讀少的應用,正是依賴于它相當出色的寫入性能:一個100臺RS的集群可以輕松地支撐每天10T?的寫入量。當然,為了支持更高吞吐量的寫入,HBase還在不斷地進行優(yōu)化和修正,這篇文章結(jié)合0.98版本的源碼全面地分析HBase的寫入流程,全文分為三個部分,第一部分介紹客戶端的寫入流程,第二部分介紹服務器端的寫入流程,最后再重點分析WAL的工作原理(注:從服務器端的角度理解,HBase寫入分為兩個階段,第一階段數(shù)據(jù)會被寫入memstore,并且會執(zhí)行WAL的寫入;第二階段會將memstore的中的數(shù)據(jù)集中flush到磁盤,本文主要集中分析第一階段的相關(guān)細節(jié))。
?
客戶端流程解析
(1)用戶提交put請求后,HBase客戶端會將put請求添加到本地buffer中,符合一定條件就會通過AsyncProcess異步批量提交。HBase默認設置autoflush=true,表示put請求直接會提交給服務器進行處理;用戶可以設置autoflush=false,這樣的話put請求會首先放到本地buffer,等到本地buffer大小超過一定閾值(默認為2M,可以通過配置文件配置)之后才會提交。很顯然,后者采用group commit機制提交請求,可以極大地提升寫入性能,但是因為沒有保護機制,如果客戶端崩潰的話會導致提交的請求丟失。
(2)在提交之前,HBase會在元數(shù)據(jù)表.meta.中根據(jù)rowkey找到它們歸屬的region server,這個定位的過程是通過HConnection 的locateRegion方法獲得的。如果是批量請求的話還會把這些rowkey按照HRegionLocation分組,每個分組可以對應一次RPC請求。
(?3 )HBase 會為每個HRegionLocation 構(gòu)造一個遠程RPC 請求MultiServerCallable<Row> , 然后通過rpcCallerFactory.<MultiResponse> newCaller()執(zhí)行調(diào)用,忽略掉失敗重新提交和錯誤處理,客戶端的提交操作到此結(jié)束。服務器端流程解析服務器端RegionServer接收到客戶端的寫入請求后,首先會反序列化為Put對象,然后執(zhí)行各種檢查操作,比如檢查region是否是只讀、memstore大小是否超過blockingMemstoreSize等。檢查完成之后,就會執(zhí)行如下核心操作:
(1)?獲取行鎖、Region更新共享鎖: HBase中使用行鎖保證對同一行數(shù)據(jù)的更新都是互斥操作,用以保證更新的原子性,要么更新成功,要么失敗。
(2)?開始寫事務:獲取write number,用于實現(xiàn)MVCC,實現(xiàn)數(shù)據(jù)的非鎖定讀,在保證讀寫一致性的前提下提高讀取性能。
(3)?寫緩存memstore:HBase中每個列族都會對應一個store,用來存儲該列族數(shù)據(jù)。每個store都會有個寫緩存memstore,用于緩存寫入數(shù)據(jù)。HBase并不會直接將數(shù)據(jù)落盤,而是先寫入緩存,等緩存滿足一定大小之后再一起落盤。
(4)?Append?HLog:HBase使用WAL機制保證數(shù)據(jù)可靠性,即首先寫日志再寫緩存,即使發(fā)生宕機,也可以通過恢復HLog還原出原始數(shù)據(jù)。該步驟就是將數(shù)據(jù)構(gòu)造為WALEdit對象,然后順序?qū)懭際Log中,此時不需要執(zhí)行sync操作。0.98版本采用了新的寫線程模式實現(xiàn)HLog日志的寫入,可以使得整個數(shù)據(jù)更新性能得到極大提升,具體原理見下一個章節(jié)。
(5)釋放行鎖以及共享鎖
(6)Sync HLog真正sync到HDFS,在釋放行鎖之后執(zhí)行sync操作是為了盡量減少持鎖時間,提升寫性能。如果Sync失敗,執(zhí)行回滾操作將memstore中已經(jīng)寫入的數(shù)據(jù)移除。
?(7)?結(jié)束寫事務:此時該線程的更新操作才會對其他讀請求可見,更新才實際生效。具體分析見上一篇文章《HBase?- 并發(fā)控制深度解析》
(8)?flush?memstore:當寫緩存滿64M之后,會啟動flush線程將數(shù)據(jù)刷新到硬盤。刷新操作涉及到HFile相關(guān)結(jié)構(gòu),后面會詳細對此進行介紹。
?
WAL機制解析
?WAL(Write-Ahead Logging)是一種高效的日志算法,幾乎是所有非內(nèi)存數(shù)據(jù)庫提升寫性能的不二法門,基本原理是在數(shù)據(jù)寫入之前首先順序?qū)懭肴罩?#xff0c;然后再寫入緩存,等到緩存寫滿之后統(tǒng)一落盤。之所以能夠提升寫性能,是因為WAL將一次隨機寫轉(zhuǎn)化為了一次順序?qū)懠右淮蝺?nèi)存寫。提升寫性能的同時,WAL可以保證數(shù)據(jù)的可靠性,即在任何情況下數(shù)據(jù)不丟失。假如一次寫入完成之后發(fā)生了宕機,即使所有緩存中的數(shù)據(jù)丟失,也可以通過恢復日志還原出丟失的數(shù)據(jù)。
?
WAL持久化等級 ?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
HBase中可以通過設置WAL的持久化等級決定是否開啟WAL機制、以及HLog的落盤方式。WAL的持久化等級分為如下四個等級:
1.?SKIP_WAL:只寫緩存,不寫HLog日志。這種方式因為只寫內(nèi)存,因此可以極大的提升寫入性能,但是數(shù)據(jù)有丟失的風險。在實際應用過程中并不建議設置此等級,除非確認不要求數(shù)據(jù)的可靠性。
2.?ASYNC_WAL:異步將數(shù)據(jù)寫入HLog日志中。
?3.?SYNC_WAL:同步將數(shù)據(jù)寫入日志文件中,需要注意的是數(shù)據(jù)只是被寫入文件系統(tǒng)中,并沒有真正落盤。
?4.?FSYNC_WAL:同步將數(shù)據(jù)寫入日志文件并強制落盤。最嚴格的日志寫入等級,可以保證數(shù)據(jù)不會丟失,但是性能相對比較差。
?5.?USER_DEFAULT:默認如果用戶沒有指定持久化等級,HBase使用SYNC_WAL等級持久化數(shù)據(jù)。
用戶可以通過客戶端設置WAL持久化等級,代碼:put.setDurability(Durability. SYNC_WAL );?
?
HLog數(shù)據(jù)結(jié)構(gòu)
HBase中,WAL的實現(xiàn)類為HLog,每個Region Server擁有一個HLog日志,所有region的寫入都是寫到同一個HLog。下圖表示同一個Region Server中的3個 region 共享一個HLog。當數(shù)據(jù)寫入時,是將數(shù)據(jù)對<HLogKey,WALEdit>按照順序追加到HLog 中,以獲取最好的寫入性能。
?
上圖中HLogKey主要存儲了log sequence number,更新時間 write time,region name,表名table name以及cluster ids。其中l(wèi)og sequncece number作為HFile中一個重要的元數(shù)據(jù),和HLog的生命周期息息相關(guān),后續(xù)章節(jié)會詳細介紹;region name和table name分別表征該段日志屬于哪個region以及哪張表;cluster ids用于將日志復制到集群中其他機器上。
?
WALEdit用來表示一個事務中的更新集合,在之前的版本,如果一個事務中對一行row R中三列c1,c2,c3分別做了修改,那么hlog中會有3個對應的日志片段如下所示:
<logseq3-for-edit3>:<keyvalue-for-edit-c3>
?
然而,這種日志結(jié)構(gòu)無法保證行級事務的原子性,假如剛好更新到c2之后發(fā)生宕機,那么就會產(chǎn)生只有部分日志寫入成功的現(xiàn)象。為此,hbase將所有對同一行的更新操作都表示為一個記錄,如下:
?<logseq#-for-entire-txn>:<WALEdit-for-entire-txn>
?
其中WALEdit會被序列化為格式<-1, # of edits, <KeyValue>, <KeyValue>, <KeyValue>>,比如<-1, 3, <keyvalue-for-edit- c1>, <keyvalue-for-edit-c2>, <keyvalue-for-edit-c3>>,其中-1作為標示符表征這種新的日志結(jié)構(gòu)。
?
WAL寫入模型
了解了HLog 的結(jié)構(gòu)之后, 我們就開始研究HLog 的寫入模型。 HLog 的寫入可以分為三個階段, 首先將數(shù)據(jù)對<HLogKey,WALEdit>寫入本地緩存,然后再將本地緩存寫入文件系統(tǒng),最后執(zhí)行sync操作同步到磁盤。在以前老的寫入模型中, 上述三步都由工作線程獨自完成,如下圖所示:
上圖中,本地緩存寫入文件系統(tǒng)那個步驟工作線程需要持有updateLock執(zhí)行,不同工作線程之間必然會惡性競爭;不僅如此,在Sync HDFS這步中,工作線程之間需要搶占flushLock,因為Sync操作是一個耗時操作,搶占這個鎖會導致寫入性能大幅降低。
所幸的是,來自中國(準確的來說,是來自小米,鼓掌)的3位工程師意識到了這個問題,進而提出了一種新的寫入模型并被官方 采納。根據(jù)官方測試,新寫入模型的吞吐量比之前提升3倍多,單臺RS寫入吞吐量介于12150~31520,5臺RS組成的集群寫入吞吐量介于22000~70000(見HBASE-8755)。下圖是小米官方給出來的對比測試結(jié)果:
?
在新寫入模型中,本地緩存寫入文件系統(tǒng)以及Sync HDFS都交給了新的獨立線程完成,并引入一個Notify線程通知工作線程是否已經(jīng)Sync成功,采用這種機制消除上述鎖競爭,具體如下圖所示:
?
?
1.?上文中提到工作線程在寫入WALEdit?之后并沒有進行Sync?,?而是等到釋放行鎖阻塞在syncedTillHere?變量上,?等待AsyncNotifier線程喚醒。
2.?工作線程將WALEdit寫入本地Buffer之后,會生成一個自增變量txid,攜帶此txid喚醒AsyncWriter線程
3.?AsyncWriter?線程會取出本地Buffer?中的所有WALEdit?,?寫入HDFS?。注意該線程會比較傳入的txid?和已經(jīng)寫入的最大txid(writtenTxid),如果傳入的txid小于writteTxid,表示該txid對應的WALEdit已經(jīng)寫入,直接跳過
4.?AsyncWriter線程將所有WALEdit寫入HDFS之后攜帶maxTxid喚醒AsyncFlusher線程?
5.?AsyncFlusher線程將所有寫入文件系統(tǒng)的WALEdit統(tǒng)一Sync刷新到磁盤
6.?數(shù)據(jù)全部落盤之后調(diào)用setFlushedTxid方法喚醒AyncNotifier線程
7.?AyncNotifier線程會喚醒所有阻塞在變量syncedTillHere的工作線程,工作線程被喚醒之后表示W(wǎng)AL寫入完成,后面再執(zhí)行MVCC結(jié)束寫事務,推進全局讀取點,本次更新才會對用戶可見
?
通過上述過程的梳理可以知道,新寫入模型采取了多線程模式獨立完成寫文件系統(tǒng)、sync磁盤操作,避免了之前多工作線程惡性搶占鎖的問題。同時,工作線程在將WALEdit寫入本地Buffer之后并沒有馬上阻塞,而是釋放行鎖之后阻塞等待WALEdit落盤,這樣可以盡可能地避免行鎖競爭,提高寫入性能。
?
總結(jié)
文章剛開始就提到HBase寫入分為兩個階段,本文主要集中分析第一階段的相關(guān)細節(jié),首先介紹了HBase的寫入memstore的流程,之后重點分析了WAL的寫入模型以及相關(guān)優(yōu)化。后面一篇文章會接著介紹寫入到memstore的數(shù)據(jù)落盤的相關(guān)知識點,敬請期待!
?
網(wǎng)易有數(shù):企業(yè)級大數(shù)據(jù)可視化分析平臺。面向業(yè)務人員的自助式敏捷分析平臺,采用PPT模式的報告制作,更加易學易用,具備強大的探索分析功能,真正幫助用戶洞察數(shù)據(jù)發(fā)現(xiàn)價值??牲c擊這里免費試用。
?
了解 網(wǎng)易云 :
網(wǎng)易云官網(wǎng):https://www.163yun.com/
新用戶大禮包:https://www.163yun.com/gift
網(wǎng)易云社區(qū):https://sq.163yun.com/
轉(zhuǎn)載于:https://www.cnblogs.com/163yun/p/9020608.html
總結(jié)
以上是生活随笔為你收集整理的HBase - 数据写入流程解析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Eclipse如何从SVN更新和上传修改
- 下一篇: python-包package