HBase原理 – 分布式系統(tǒng)中Snapshot是怎么玩的?
snapshot(快照)基礎(chǔ)原理
snapshot是很多存儲系統(tǒng)和數(shù)據(jù)庫系統(tǒng)都支持的功能。一個snapshot是一個全部文件系統(tǒng)、或者某個目錄在某一時刻的鏡像。實(shí)現(xiàn)數(shù)據(jù)文件鏡像最簡單粗暴的方式是加鎖拷貝(之所以需要加鎖,是因?yàn)殓R像得到的數(shù)據(jù)必須是某一時刻完全一致的數(shù)據(jù)),拷貝的這段時間不允許對原數(shù)據(jù)進(jìn)行任何形式的更新刪除,僅提供只讀操作,拷貝完成之后再釋放鎖。這種方式涉及數(shù)據(jù)的實(shí)際拷貝,數(shù)據(jù)量大的情況下必然會花費(fèi)大量時間,長時間的加鎖拷貝必然導(dǎo)致客戶端長時間不能更新刪除,這是生產(chǎn)線上不能容忍的。
snapshot機(jī)制并不會拷貝數(shù)據(jù),可以理解為它是原數(shù)據(jù)的一份指針。在HBase這種LSM類型系統(tǒng)結(jié)構(gòu)下是比較容易理解的,我們知道HBase數(shù)據(jù)文件一旦落到磁盤之后就不再允許更新刪除等原地修改操作,如果想更新刪除的話可以追加寫入新文件(HBase中根本沒有更新接口,刪除命令也是追加寫入)。這種機(jī)制下實(shí)現(xiàn)某個表的snapshot只需要給當(dāng)前表的所有文件分別新建一個引用(指針),其他新寫入的數(shù)據(jù)重新創(chuàng)建一個新文件寫入即可。如下圖所示:
snapshot流程主要涉及3個步驟:
- 加一把全局鎖,此時不允許任何的數(shù)據(jù)寫入更新以及刪除
- 將Memstore中的緩存數(shù)據(jù)flush到文件中(可選)
- 為所有HFile文件分別新建引用指針,這些指針元數(shù)據(jù)就是snapshot
擴(kuò)展思考:LSM類系統(tǒng)確實(shí)比較容易理解,那其他非LSM系統(tǒng)原地更新的存儲系統(tǒng)如何實(shí)現(xiàn)snapshot呢?
snapshot能實(shí)現(xiàn)什么功能?
snapshot是HBase非常核心的一個功能,使用snapshot的不同用法可以實(shí)現(xiàn)很多功能,比如:
全量/增量備份:任何數(shù)據(jù)庫都需要有備份的功能來實(shí)現(xiàn)數(shù)據(jù)的高可靠性,snapshot可以非常方便的實(shí)現(xiàn)表的在線備份功能,并且對在線業(yè)務(wù)請求影響非常小。使用備份數(shù)據(jù),用戶可以在異常發(fā)生的情況下快速回滾到指定快照點(diǎn)。增量備份會在全量備份的基礎(chǔ)上使用binlog進(jìn)行周期性的增量備份。
使用場景一:通常情況下,對重要的業(yè)務(wù)數(shù)據(jù),建議至少每天執(zhí)行一次snapshot來保存數(shù)據(jù)的快照記錄,并且定期清理過期快照,這樣如果業(yè)務(wù)發(fā)生重要錯誤需要回滾的話是可以回滾到之前的一個快照點(diǎn)的。
使用場景二:如果要對集群做重大的升級的話,建議升級前對重要的表執(zhí)行一次snapshot,一旦升級有任何異??梢钥焖倩貪L到升級前。
2. 數(shù)據(jù)遷移:可以使用ExportSnapshot功能將快照導(dǎo)出到另一個集群,實(shí)現(xiàn)數(shù)據(jù)的遷移
使用場景一:機(jī)房在線遷移,通常情況是數(shù)據(jù)在A機(jī)房,因?yàn)锳機(jī)房機(jī)位不夠或者機(jī)架不夠需要將整個集群遷移到另一個容量更大的B集群,而且在遷移過程中不能停服。基本遷移思路是先使用snapshot在B集群恢復(fù)出一個全量數(shù)據(jù),再使用replication技術(shù)增量復(fù)制A集群的更新數(shù)據(jù),等待兩個集群數(shù)據(jù)一致之后將客戶端請求重定向到B機(jī)房。具體步驟可以參考: https://www.cloudera.com/documentation/enterprise/5-5-x/topics/cdh_bdr_hbase_replication.html#topic_20_11_7
使用場景二:使用snapshot將表數(shù)據(jù)導(dǎo)出到HDFS,再使用Hive\Spark等進(jìn)行離線OLAP分析,比如審計報表、月度報表等
hbase snapshot用法大全
snapshot最常用的命令有snapshot、restore_snapshot、clone_snapshot以及ExportSnapshot這個工具,具體使用方法如下:
為表’sourceTable’打一個快照’snapshotName’,快照并不涉及數(shù)據(jù)移動,可以在線完成。
- hbase> snapshot 'sourceTable', ‘snapshotName'
恢復(fù)指定快照,恢復(fù)過程會替代原有數(shù)據(jù),將表還原到快照點(diǎn),快照點(diǎn)之后的所有更新將會丟失。需要注意的是原表需要先disable掉,才能執(zhí)行restore_snapshot操作。
- hbase> restore_snapshot ‘snapshotName'
根據(jù)快照恢復(fù)出一個新表,恢復(fù)過程不涉及數(shù)據(jù)移動,可以在秒級完成。很好奇是怎么做的吧,且聽下文分解。
- hbase> clone_snapshot 'snapshotName', ‘tableName'
使用ExportSnapshot命令可以將A集群的快照數(shù)據(jù)遷移到B集群,ExportSnapshot是HDFS層面的操作,會使用MR進(jìn)行數(shù)據(jù)的并行遷移,因此需要在開啟MR的機(jī)器上進(jìn)行遷移。HMaster和HRegionServer并不參與這個過程,因此不會帶來額外的內(nèi)存開銷以及GC開銷。唯一的影響是DN在拷貝數(shù)據(jù)的時候需要額外的帶寬以及IO負(fù)載,ExportSnapshot也針對這個問題設(shè)置了參數(shù)-bandwidth來限制帶寬的使用。
- hbase org.apache.hadoop.hbase.snapshot.ExportSnapshot \
- -snapshot MySnapshot -copy-from hdfs://srv2:8082/hbase \
- -copy-to hdfs://srv1:50070/hbase -mappers 16 -bandwidth 1024\
hbase snapshot分布式架構(gòu)-兩階段提交
hbase為指定表執(zhí)行snapshot操作,實(shí)際上真正執(zhí)行snapshot的是對應(yīng)表的所有region。這些region因?yàn)榉植荚诙鄠€RegionServer上,所以需要一種機(jī)制來保證所有參與執(zhí)行snapshot的region要么全部完成,要么都沒有開始做,不能出現(xiàn)中間狀態(tài),比如某些region完成了,某些region未完成。
HBase使用兩階段提交協(xié)議(2PC)來保證snapshot的分布式原子性。2PC一般由一個協(xié)調(diào)者和多個參與者組成,整個事務(wù)提交分為兩個階段:prepare階段和commit階段。其中prepare階段協(xié)調(diào)者會向所有參與者發(fā)送prepare命令,所有參與者開始獲取相應(yīng)資源(比如鎖資源)并執(zhí)行prepare操作確認(rèn)可以執(zhí)行成功,通常核心工作都是在prepare操作中完成的。并返回給協(xié)調(diào)者prepared應(yīng)答。協(xié)調(diào)者接收到所有參與者返回的prepared應(yīng)答之后(表明所有參與者都已經(jīng)準(zhǔn)備好提交),在本地持久化commit狀態(tài),進(jìn)入commit階段,協(xié)調(diào)者會向所有參與者發(fā)送commit命令,參與者接收到commit命令之后會執(zhí)行commit操作并釋放資源,通常commit操作都非常簡單。
接下來就看看hbase是如何使用2PC協(xié)議來構(gòu)建snapshot架構(gòu)的,基本步驟如下:
1. prepare階段:HMaster在zookeeper創(chuàng)建一個’/acquired-snapshotname’節(jié)點(diǎn),并在此節(jié)點(diǎn)上寫入snapshot相關(guān)信息(snapshot表信息)。所有regionserver監(jiān)測到這個節(jié)點(diǎn)之后,根據(jù)/acquired-snapshotname節(jié)點(diǎn)攜帶的snapshot表信息查看當(dāng)前regionserver上是否存在目標(biāo)表,如果不存在,就忽略該命令。如果存在,遍歷目標(biāo)表中的所有region,分別針對每個region執(zhí)行snapshot操作,注意此處snapshot操作的結(jié)果并沒有寫入最終文件夾,而是寫入臨時文件夾。regionserver執(zhí)行完成之后會在/acquired-snapshotname節(jié)點(diǎn)下新建一個子節(jié)點(diǎn)/acquired-snapshotname/nodex,表示nodex節(jié)點(diǎn)完成了該regionserver上所有相關(guān)region的snapshot準(zhǔn)備工作。
2. commit階段:一旦所有regionserver都完成了snapshot的prepared工作,即都在/acquired-snapshotname節(jié)點(diǎn)下新建了對應(yīng)子節(jié)點(diǎn),hmaster就認(rèn)為snapshot的準(zhǔn)備工作完全完成。master會新建一個新的節(jié)點(diǎn)/reached-snapshotname,表示發(fā)送一個commit命令給參與的regionserver。所有regionserver監(jiān)測到/reached-snapshotname節(jié)點(diǎn)之后,執(zhí)行snapshot commit操作,commit操作非常簡單,只需要將prepare階段生成的結(jié)果從臨時文件夾移動到最終文件夾即可。執(zhí)行完成之后在/reached-snapshotname節(jié)點(diǎn)下新建子節(jié)點(diǎn)/reached-snapshotname/nodex,表示節(jié)點(diǎn)nodex完成snapshot工作。
3. abort階段:如果在一定時間內(nèi)/acquired-snapshotname節(jié)點(diǎn)個數(shù)沒有滿足條件(還有regionserver的準(zhǔn)備工作沒有完成),hmaster認(rèn)為snapshot的準(zhǔn)備工作超時。hmaster會新建另一種新的節(jié)點(diǎn)/abort-snapshotname,所有regionserver監(jiān)聽到這個命令之后會清理snapshot在臨時文件夾中生成的結(jié)果。
可以看到,在這個系統(tǒng)中HMaster充當(dāng)了協(xié)調(diào)者的角色,RegionServer充當(dāng)了參與者的角色。HMaster和RegionServer之間的通信通過Zookeeper來完成,同時,事務(wù)狀態(tài)也是記錄在Zookeeper上的節(jié)點(diǎn)上。HMaster高可用情況下主HMaster宕機(jī)了,從HMaster切成主后根據(jù)Zookeeper上的狀態(tài)可以決定事務(wù)十分繼續(xù)提交或者abort。
snapshot核心實(shí)現(xiàn)
上節(jié)從架構(gòu)層面介紹了snapshot如何在分布式體系中完成原子性操作。那每個region是如何真正實(shí)現(xiàn)snapshot呢?hmaster又是如何匯總所有region snapshot結(jié)果?
region如何實(shí)現(xiàn)snapshot?
在基本原理一節(jié)我們提到過snapshot不會真正拷貝數(shù)據(jù),而是使用指針引用的方式創(chuàng)建一系列元數(shù)據(jù)。那元數(shù)據(jù)具體是什么樣的元數(shù)據(jù)呢?實(shí)際上snapshot的整個流程基本如下:
分別對應(yīng)debug日志中如下片段:
- snapshot.FlushSnapshotSubprocedure: Flush Snapshotting region yixin:yunxin,user1359,1502949275629.77f4ac61c4db0be9075669726f3b72e6. started...
- snapshot.SnapshotManifest: Storing 'yixin:yunxin,user1359,1502949275629.77f4ac61c4db0be9075669726f3b72e6.' region-info for snapshot.
- snapshot.SnapshotManifest: Creating references for hfiles
- snapshot.SnapshotManifest: Adding snapshot references for [] hfiles
注意:region生成的snapshot文件是臨時文件,生成目錄在/hbase/.hbase-snapshot/.tmp下,一般因?yàn)閟napshot過程特別快,所以很難看到單個region生成的snapshot文件。
hmaster如何匯總所有region snapshot的結(jié)果?
hmaster會在所有region完成snapshot之后執(zhí)行一個匯總操作(consolidate),將所有region snapshot manifest匯總成一個單獨(dú)manifest,匯總后的snapshot文件是可以在HDFS目錄下看到的,路徑為:/hbase/.hbase-snapshot/snapshotname/data.manifest。注意,snapshot目錄下有3個文件,如下圖所示:
其中.snapshotinfo為snapshot基本信息,包含待snapshot的表名稱以及snapshot名;data.manifest為snapshot執(zhí)行后生成的元數(shù)據(jù)信息,即snapshot結(jié)果信息??梢允褂胔adoop dfs -cat /hbase/.hbase-snapshot/snapshotname/data.manifest 查看:
clone_snapshot如何實(shí)現(xiàn)呢?
前文提到snapshot可以用來搞很多大事情,比如restore_snapshot、clone_snapshot以及export snapshot等等,這節(jié)就來看看clone_snapshot這個功能具體是如何實(shí)現(xiàn)的。直接進(jìn)入正題,整個步驟可以概括為如下:
- 預(yù)檢查:確認(rèn)目標(biāo)表沒有進(jìn)行snapshot操作以及restore操作,否則直接返回錯誤
- 在tmp文件夾下新建表目錄并在表目錄下新建.tabledesc文件,在該文件中寫入表schema信息
- 新建region目錄:這個步驟是clone_snapshot和create table最大的不同,新建的region目錄是依據(jù)snapshot manifest中信息確定的,region中有哪些列族?列族中有哪些HFile文件?都來源于此。
此處有一個很有意思的事情是clone_snapshot克隆表的過程中并不涉及數(shù)據(jù)的移動,那不禁要問克隆出的表中文件是什么文件?與原表中數(shù)據(jù)文件之間的對應(yīng)關(guān)系如何建立?這個問題的解決和split過程中reference文件的解決思路基本一致,不過在clone_snapshot中并不稱作reference文件,而叫做linkfile,和reference文件不一樣的是linkfile文件沒有任何內(nèi)容,只是在文件名上做了文章,比如原文件名是abc,生成的linkfile就為:table=region-abc,通過這種方式就可以很容易定位到原表中原始文件的具體路徑:xxx/table/region/hfile,因此就可以不需要移動數(shù)據(jù)了。
上圖中LinkFile文件名為music=5e54d8620eae123761e5290e618d556b-f928e045bb1e41ecbef6fc28ec2d5712,根據(jù)定義我們知道m(xù)usic為原始文件的表名,5e54d8620eae123761e5290e618d556b為引用文件所在的region,f928e045bb1e41ecbef6fc28ec2d5712為引用文件,如下圖所示:
我們可以依據(jù)規(guī)則可以直接根據(jù)LinkFile的文件名定位到引用文件所在位置:***/music/5e54d8620eae123761e5290e618d556b/cf/f928e045bb1e41ecbef6fc28ec2d5712,如下圖所示:
4. 將表目錄從tmp文件夾下移動到hbase root location
5. 修改meta表,將克隆表的region信息添加到meta表中,注意克隆表的region名稱和原數(shù)據(jù)表的region名稱并不相同(region名稱與table名稱相關(guān),table名不同,region名稱就肯定不會相同)
6. 將這些region通過round-robin方式立刻均勻分配到整個集群中,并在zk上將克隆表的狀態(tài)設(shè)置為enabled,正式對外提供服務(wù)
其他需要注意的
不知道大家有沒有關(guān)注另一個問題,按照上文的說法我們知道snapshot實(shí)際上是一系列原始表的元數(shù)據(jù),主要包括表schema信息、原始表所有region的region info信息,region包含的列族信息以及region下所有的hfile文件名以及文件大小等。那如果原始表發(fā)生了compaction導(dǎo)致hfile文件名發(fā)生了變化或者region發(fā)生了分裂,甚至刪除了原始表,之前所做的snapshot是否就失效了?
從功能實(shí)現(xiàn)的角度來講肯定不會讓用戶任何時間點(diǎn)所作的snapshot失效,那如何避免上述所列的各種情況下snapshot失效呢?HBase的實(shí)現(xiàn)也比較簡單,在原始表發(fā)生compact的操作前會將原始表復(fù)制到archive目錄下再執(zhí)行compact(對于表刪除操作,正常情況也會將刪除表數(shù)據(jù)移動到archive目錄下),這樣snapshot對應(yīng)的元數(shù)據(jù)就不會失去意義,只不過原始數(shù)據(jù)不再存在于數(shù)據(jù)目錄下,而是移動到了archive目錄下。
大家可以做一下這樣一個實(shí)驗(yàn)看看:
- 使用snapshot給一張表做快照,比如snapshot ’test’,’test_snapshot’
- 查看archive目錄,確認(rèn)不存在目錄:/hbase-root-dir/archive/data/default/test
- 對表test執(zhí)行major_compact操作:major_compact ’test’
- 再次查看archive目錄,就會發(fā)現(xiàn)test原始表移動到了該目錄,/hbase-root-dir/archive/data/default/test就會存在
同理,如果對原始表執(zhí)行delete操作,比如delete ’test’,也會在archive目錄下找到該目錄。和普通表刪除的情況不同的是,普通表一旦刪除,剛開始是可以在archive中看到刪除表的數(shù)據(jù)文件,但是等待一段時間后archive中的數(shù)據(jù)就會被徹底刪除,再也無法找回。這是因?yàn)閙aster上會啟動一個定期清理archive中垃圾文件的線程(HFileCleaner),定期會對這些被刪除的垃圾文件進(jìn)行清理。但是snapshot原始表被刪除之后進(jìn)入archive,并不可以被定期清理掉,上文說過clone出來的新表并沒有clone真正的文件,而是生成的指向原始文件的連接,這類文件稱之為LinkFile,很顯然,只要LinkFile還指向這些原始文件,它們就不可以被刪除。好了,這里有兩個問題:
1. 什么時候LinkFile會變成真實(shí)的數(shù)據(jù)文件?
如果看過筆者上篇文章《HBase原理 – 所有Region切分的細(xì)節(jié)都在這里了》的同學(xué),肯定看著這個問題有種似曾相識的趕腳。不錯,HBase中一個region分裂成兩個子region后,子region的文件也是引用文件,這些引用文件是在執(zhí)行compact的時候才真正將父region中的文件遷移到自己的文件目錄下。LinkFile也一樣,在clone出的新表執(zhí)行compact的時候才將合并后的文件寫到新目錄并將相關(guān)的LinkFile刪除,理論上也是借著compact順便做了這件事。
2. 系統(tǒng)在刪除archive中原始表文件的時候怎么知道這些文件還被一些LinkFile引用著?
HBase Split后系統(tǒng)要刪除父region的數(shù)據(jù)文件,是首先要確認(rèn)兩個子region已經(jīng)沒有引用文件指向它了,系統(tǒng)怎么確認(rèn)這點(diǎn)的呢?上節(jié)我們分析過,meta表中會存儲父region對應(yīng)的兩個子region,再掃描兩個子region的所有文件確認(rèn)是否還有引用文件,如果已經(jīng)沒有引用文件了,就可以放心地將父region的數(shù)據(jù)文件刪掉了,當(dāng)然,如果還有引用文件存在就只能作罷。
那刪除clone后的原始表文件,是不是也是一樣的套路?答案并不是,HBase用了另一種方式來根據(jù)原始表文件找到引用文件,這就是back-reference機(jī)制。HBase系統(tǒng)在archive目錄下新建了一種新的back-reference文件,來幫助原始表文件找到引用文件。來看看back-reference文件是一種什么樣的文件,它是如何根據(jù)原始文件定位到LinkFile的:
- (1)原始文件:/hbase/data/table-x/region-x/cf/file-x
- (2)clone生成的LinkFile:/hbase/data/table-cloned/region-y/cf/{table-x}-{region-x}-{file-x},因此可以很容易根據(jù)LinkFile定位到原始文件
- (3)back-reference文件:/hbase/.archive/data/table-x/region-x/cf/.links-file-x/{region-y}.{table-cloned},可以看到,back-reference文件路徑中包含所有原始文件和LinkFile的信息,因此可以有效的根據(jù)原始文件/table-x/region-x/cf/file-x定位到LinkFile:/table-cloned/region-y/cf/{table-x}-{region-x}-{file-x}
到這里,有興趣的童鞋可以將這塊知識點(diǎn)串起來做個簡單的小實(shí)驗(yàn):
(1)使用snapshot給一張表做快照,比如snapshot ’table-x’,’table-x-snapshot’
(2)使用clone_snapshot克隆出一張新表,比如clone_snapshot ’table-x-snapshot’,’table-x-cloned’。并查看新表test_clone的HDFS文件目錄,確認(rèn)會存在LinkFile
(3)刪除原表table-x(刪表之前先確認(rèn)archive下沒有原表文件),查看確認(rèn)原表文件進(jìn)入archive,并在archive中存在back-reference文件。注意瞅瞅back-reference文件格式哈。
(4)對表’table-x-clone’執(zhí)行major_compact,命令為major_compact ’test_clone’。執(zhí)行命令前確認(rèn)table-x-clone文件目錄下LinkFile存在。
(5)major_compact執(zhí)行完成之后查看table-x-clone的HDFS文件目錄,確認(rèn)所有LinkFile已經(jīng)不再存在,全部變成了真實(shí)數(shù)據(jù)文件。