HBase跨叢集複製Snapshot失敗原因分析及解決
起因
HBase快照在跨叢集複製時,經常會出現由於/hbase/.tmp/data/xxx FileNotFoundException導致任務失敗
現還原出錯場景,並分析錯誤原因,給出一些常用的解決方法
- 主要原因
在建立快照到跨叢集複製過程中,部分StoreFile的位置發生了變動,以至不能正常定址( 使用webhdfs的bug)
場景還原
源叢集:HBase 1.2.0-cdh5.10.0
目標叢集:HBase 1.2.0-cdh5.12.1
1. 建立表mytable,2個region,以03為分割,一個列族info
put 6條資料
put 'mytable','01','info:age','1' put 'mytable','02','info:age','2' put 'mytable','03','info:age','3' put 'mytable','04','info:age','1' put 'mytable','05','info:age','1' put 'mytable','06','info:age','1'
2. 建立快照mysnapshot,生成以下檔案
-
.snapshot 包含了快照資訊,即HBaseProtos.SnapshotDescription物件
name: "mysnapshot"
table: "mytable"
creation_time: 1533774121010
type: FLUSH
version: 2 -
data.manifest
包含了hbase表schema、attributes、column_families,即HBaseProtos.SnapshotDescription物件,重點的是store_files資訊
3. 修改資料
通過Put 修改一個Region的資料
put 'mytable','04','info:age','4'
put 'mytable','05','info:age','5'
put 'mytable','06','info:age','6'
4. 進行flush,major_compat
模擬跨叢集複製過程中出現的大/小合併
還原出錯
控制檯提示,FileNotFoundException,任務失敗
原始碼剖析
1 ExportSnapshot執行復制前會先將.snapshot,data.manifest 複製到目標端 .hbase-snapshot/.tmp/mysnapshot下
2 解析data.manifest,按照storefile進行邏輯切片,map每次會讀入一個SnapshotFileInfo的資訊,只包含了HFileLink資訊,並沒有包括具體路徑
3 map階段
每讀入一個SnapshotFileInfo時,拼接出關於StoreFile可能出現的4個路徑,讀取時按照該順序查詢
/datafs/data/default/mytable/c48642fecae3913e0d09ba236b014667/info/3c5e9ec890f04560a396040fa8b592a3
/datafs/.tmp/data/default/mytable/c48642fecae3913e0d09ba236b014667/info/3c5e9ec890f04560a396040fa8b592a3
/datafs/mobdir/data/default/mytable/c48642fecae3913e0d09ba236b014667/info/3c5e9ec890f04560a396040fa8b592a3
/datafs/archive/data/default/mytable/c48642fecae3913e0d09ba236b014667/info/3c5e9ec890f04560a396040fa8b592a3
當map讀入資料時,呼叫ExportSnapshot.ExportMapper#openSourceFile 初始化InputStream的過程中
通過呼叫FileLink.tryOpen()方法中,來確定StoreFile的真實路徑路徑(遍歷4個路徑,丟擲異常說明不存在,繼續找下一路徑)
在debug中發現,fs為org.apache.hadoop.hdfs.web.WebHdfsFileSystem物件
遺憾的是,WebHdfsFileSystem呼叫getPos()時,不會丟擲異常,因此獲取到如下路徑(檔案實際存在於archive)
/datafs/data/default/mytable/c48642fecae3913e0d09ba236b014667/info/3c5e9ec890f04560a396040fa8b592a3
並將 該路徑設定為currentPath(下一次會用到,避免重複判定)
當InputStream.read(buffer)時,呼叫FileLink.read()
由於初始化時,並沒有使用正確的路徑,因此 in.read()時,丟擲FileNotFoundException(第一次)
繼續呼叫tryOpen().read()方法遍歷4個路徑,此時 currentPath為 data路徑跳過,使用下一個路徑(檔案仍在archive下)
/datafs/.tmp/data/default/mytable/c48642fecae3913e0d09ba236b014667/info/3c5e9ec890f04560a396040fa8b592a3
read錯誤路徑,再次丟擲FileNotFoundException(第二次),此異常向上丟擲,task失敗,觀察map日誌,可看到
紅線之下的FileNotFoundException,即為read()時,丟擲的兩次異常
紅線之上File does not exist 為ExportSnapshot 系呼叫 getSourceFileStatus產生,可以觀察到在遍歷 data/.tmp/mobdir 後尋找到了正確路徑archive(未打印出)
解決思路
綜上:查詢StoreFile時只會查詢data、.tmp目錄,不會查詢archive目錄
因此解決思路上,一是避免StoreFile出現在archive下,二是能正確獲取到archive路徑
避免StoreFile出現在archive
根據生產經驗,在資料大量寫入過程中,Region下不斷生成StoreFile,當StoreFile數量達到閾值時,觸發大/小合併
被合併的StoreFile檔案移動到了archive檔案下,可使用以下幾個方法避免複製時大/小合併
- 對錶進行major_compact後再建快照
- 如果表可以接受一段時間的不可用,幾分鐘到幾十分鐘不等,可對錶進行disable後再操作
- 或者適當調大 hbase.hstore.compaction.Threadhold(表寫入不頻繁下)
- 根據業務情況,儘可能大的錯開資料寫入與複製的間隔(等待大/小合併自動完成)
避免使用webhdfs
使用hdfs時,可以正常的丟擲異常(未具體使用)
修復原始碼bug
使得在定址過程中,可正確讀到archive資料夾
借鑑getSourceFileStatus(),在for中加一行 fs.getFileStatus(),遍歷時正常丟擲FileNotFoundException
將ExportSnapshot抽出,重新組織HFileLink,FileLink,WALLink依賴
打包成一個hadoop jar,避免影響其它功能