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