看看HBase Bulkload 源码
什么都别说先看代码
val regionLocator = new HRegionLocator(hbTableName, classOf[ClusterConnection].cast(conn))
val loader = new LoadIncrementalHFiles(hbaseConf)
val admin = conn.getAdmin()
loader.doBulkLoad(new Path(path),admin,realTable,regionLocator)
-- PS 我看的源码版本是 hbase-client-1.2.1
从上面可以直观的看到2个关键的信息 HRegionLocator 、**LoadIncrementalHFiles **,接下来先看看这2个类有没有说明信息。
HRegionLocator(org.apache.hadoop.hbase.client.HRegionLocator)
翻译下上面的英文起作用:用于查看单个HBase表的区域位置信息,感觉是一些辅助性信息。再看看LoadIncrementalHFiles
LoadIncrementalHFiles (org.apache.hadoop.hbase.mapreduce.LoadIncrementalHFiles)
“Tool to load the output of HFileOutputFormat into an existing table” 是一个加载HFile文件到HBase的工具,
这个工具类在构造函数中,有几个信息比较有意思:
HFILE_BLOCK_CACHE_SIZE_KEY= "hfile.block.cache.size" -- 设置块缓存大小
-- 此处禁用了块缓存
-- 这个块缓存与 bucket cache 有关
public static final String MAX_FILES_PER_REGION_PER_FAMILY
= "hbase.mapreduce.bulkload.max.hfiles.perRegion.perFamily"; -- 设置最大的列族个数
doBulkLoad (org.apache.hadoop.hbase.mapreduce.LoadIncrementalHFiles#doBulkLoad)
然后就到了关键的步骤doBulkLoad,上源码:
- 上来就是检查要导入的表,在HBase中是否存在,不存在就抛异常结束
- 然后根据配置hbase.loadincremental.threads.max参数,构造一个线程池,这里这个参数既然可以手动配置那么后续就可以考虑适当的增加以提高效率,可以当成后续的优化参数
- 这里涉及另一个参数 hbase.loadincremental.validate.hfile,是否需要校验HFile,当文件个数很多时,会比较耗时,如果可以保证HFile文件时正确的时候可以考虑改为false,会提高下效率
- 将要加载的HFile封装成LQT,并放入到queue中,然后检查HFile中是否含有目标表中不存在列族,有则抛出异常
- 参数hbase.bulkload.retries.number控制了hbase对一个hfile最多plit多少次 ,如果Hfile文件 跨越多个region,bulkload会自动地将Hfile文件split,但是对于每次retry只会将指定的Hfile文件split一次
- LQI队列转为Multimap,通过groupOrSplitPhase
- 在 groupOrSplit 方法中尝试将HFile 逻辑上分配给对应的Region ,如果HFile跨越了Region,那么就将其分割,产生2个LQI
- 在bulkLoadPhase方法中,利用线程池尝试原子的加载数据tryAtomicRegionLoad(conn, table.getName(), first, lqis)
- 一系列的操作之后,终于到了加载HFile文件的地方HStore#bulkLoadHFile
bulkLoadHFile(org.apache.hadoop.hbase.regionserver.HStore#bulkLoadHFile(org.apache.hadoop.hbase.regionserver.StoreFile))
先上源码图:
StoreFileManager有两个实现类:DefaultStoreFileManager(标准了Not thread-safe)、StripeStoreFileManager,我们每个都看看到底都做了什么。
- DefaultStoreFileManager
- 维护最新的storeFiles的ImmutableList列表
- sortAndSetStoreFiles 从名字就能看出是排序storeFile,根据SeqId、FileSize、BulkTime、PathName,然后将排序后的storeFiles放到ImmutableList列表中
private void sortAndSetStoreFiles(List<StoreFile> storeFiles) {
Collections.sort(storeFiles, StoreFile.Comparators.SEQ_ID);
storefiles = ImmutableList.copyOf(storeFiles);
}
public static final Comparator<StoreFile> SEQ_ID =
Ordering.compound(ImmutableList.of(
Ordering.natural().onResultOf(new GetSeqId()),
Ordering.natural().onResultOf(new GetFileSize()).reverse(),
Ordering.natural().onResultOf(new GetBulkTime()),
Ordering.natural().onResultOf(new GetPathName())
));
- 看完之后发现,总结下HStore 把要加载的HFile的一些重要信息,好像没做什么其他的操作,有点怀疑是不是自己看漏了什么…
- StripeStoreFileManager
- 说这Manager之前要说一下,关于StripeCompactionPolicy,它的机制是:
- 将Region划分为多个Stripes(相当于sub region)
- 新Flush产生的文件先放入一个L0的区域,这个区域会有多个HFile文件
- 然后L0区域进行compaction操作时,会将HFile对应的写入Stripes中
- 这样可以加速Region Split操作(由全局转换为局部)
- 这个Manager需要与StripeCompactionPolicy and StripeCompactor 一起使用
大致看了下流程,具体的实现没细看。不过从目前所看到的源码来说,在使用Bulkload批量加载数据的时候:
- 没有经过MemCache
- 没有涉及BlockCache
- 是Region直接加载HFile文件到对应的Store的Stripes中
过程中涉及的可优化的参数
- hbase.loadincremental.threads.max构造一个工作线程池
- hbase.loadincremental.validate.hfile,是否需要校验HFile,如果可以保证HFile文件时正确的时候可以考虑改为false,会提高下效率
- **hbase.bulkload.retries.number控制了hbase对一个hfile最多plit多少次 **,可以尝试增加次数,但是如果报错了代表HFile文件跨过了多个Region,是不是本身也有问题?