起因
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,避免影响其它功能
参考:https://blog.csdn.net/t894690230/article/details/52121613