通过BulkLoad快速将海量数据导入到HBase
【前言:笔者之前通过文章《通过Spark生成HFile,并以BulkLoad方式将数据导入到HBase》介绍过如何利用Spark通过BulkLoad方式将数据导入到HBase,本篇文章提供另外一种思路】
Bulk Load的实现原理是通过一个MapReduce Job来实现的,通过Job直接生成一个HBase的内部HFile格式文件,用来形成一个特殊的HBase数据表,然后直接将数据文件加载到运行的集群中。与使用HBase API相比,使用Bulkload导入数据占用更少的CPU和网络资源。
一、实现原理
从数据源(通常是文本文件或其他的数据库)提取数据并上传到HDFS。抽取数据到HDFS和HBase并没有关系,所以大家可以选用自己擅长的方式进行,本文就不介绍了。
利用MapReduce作业处理事先准备的数据 这一步需要一个MapReduce作业,并且大多数情况下还需要我们自己编写Map函数,而Reduce函数不需要我们考虑,由HBase提供。 该作业需要使用rowkey(行键)作为输出Key,KeyValue、Put或者Delete作为输出Value。MapReduce作业需要使用HFileOutputFormat2来生成HBase数据文件。
为了有效的导入数据,需要配置HFileOutputFormat2使得每一个输出文件都在一个合适的区域中。为了达到这个目的,MapReduce作业会使用Hadoop的TotalOrderPartitioner类根据表的key值将输出分割开来。HFileOutputFormat2的方法configureIncrementalLoad()会自动的完成上面的工作。
告诉RegionServers数据的位置并导入数据 这一步是最简单的,通常需要使用LoadIncrementalHFiles(更为人所熟知是completebulkload工具),将文件在HDFS上的位置传递给它,它就会利用RegionServer将数据导入到相应的区域
整个过程图如下:
上面我们已经介绍了HBase的BulkLoad方法的原理,我们需要写个Mapper和驱动程序,实现如下:
使用MapReduce生成HFile文件
public class IteblogBulkLoadMapper extends Mapper<LongWritable, Text, ImmutableBytesWritable, Put>{
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String line = value.toString();
String[] items = line.split("\t");
ImmutableBytesWritable rowKey = new ImmutableBytesWritable(items[0].getBytes());
Put put = new Put(Bytes.toBytes(items[0])); //ROWKEY
put.addColumn("f1".getBytes(), "url".getBytes(), items[1].getBytes());
put.addColumn("f1".getBytes(), "name".getBytes(), items[2].getBytes());
context.write(rowkey, put);
}
}
驱动程序
public class IteblogBulkLoadDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
final String SRC_PATH= "hdfs://iteblog:9000/user/iteblog/input";
final String DESC_PATH= "hdfs://iteblog:9000/user/iteblog/output";
Configuration conf = HBaseConfiguration.create();
Job job=Job.getInstance(conf);
job.setJarByClass(IteblogBulkLoadDriver.class);
job.setMapperClass(IteblogBulkLoadMapper.class);
job.setMapOutputKeyClass(ImmutableBytesWritable.class);
job.setMapOutputValueClass(Put.class);
job.setOutputFormatClass(HFileOutputFormat2.class);
HTable table = new HTable(conf,"blog_info");
HFileOutputFormat2.configureIncrementalLoad(job,table,table.getRegionLocator());
FileInputFormat.addInputPath(job,new Path(SRC_PATH));
FileOutputFormat.setOutputPath(job,new Path(DESC_PATH));
System.exit(job.waitForCompletion(true)?0:1);
}
}
public class LoadIncrementalHFileToHBase {
public static void main(String[] args) throws Exception {
Configuration configuration = HBaseConfiguration.create();
HBaseConfiguration.addHbaseResources(configuration);
LoadIncrementalHFiles loder = new LoadIncrementalHFiles(configuration);
HTable hTable = new HTable(configuration, "blog_info");
loder.doBulkLoad(new Path("hdfs://iteblog:9000/user/iteblog/output"), hTable);
}
}
二、BulkLoad的使用案例
首次将原始数据集载入HBase,您的初始数据集可能很大,绕过HBase写入路径可以显著加速此进程
递增负载 - 要定期加载新数据,请使用BulkLoad并按照自己的理想时间间隔分批次导入数据。
这可以缓解延迟问题,并且有助于您实现服务级别协议(SLA)。但是,压缩触发器就是RegionServer上的HFile数目。因此,频繁导入大量HFile可能会导致更频繁地发生大型压缩,从而对性能产生负面影响。
您可以通过以下方法缓解此问题:调整压缩设置,确保不触发压缩即可存在的最大HFile文件数很高,并依赖于其他因素,如Memstore的大小触发压缩
数据需要源于其他位置 - 如果当前系统捕获了您想在HBase中包含的数据,且因业务原因需要保持活动状态,您可从系统中将数据定期批量加载到HBase中,以便可以在不影响系统的前提下对其执行操作。