目录:
【1】传统的HTableOutputFormat写HBase有什么问题?
【2】BulkLoad的流程与实现?
【3】说明
【1】传统的HTableOutputFormat写HBase有什么问题?
通常的Mapreduce在写入HBase时使用的是TableOutputFormat方式,在Reduce中直接生成Put对象写入到HBase。
该方式的不足:在大数据量些入时效率低下,因为HBase会block写入,频繁进行flush,split和compact等大量IO操作),并对HBase节点的稳定性造成一定的影响(GC时间过长,响应变慢,导致节点超时退出,并引起一系列连锁反应)。
BulkLoad入库方式。
原理:利用HBase的数据信息按照特定的格式存储在 hdfs内,直接在HDFS中生成持久化的HFile数据格式文件,然后上传至合适的位置。
优点:配合mapreduce高效便捷,不占用region资源,增添负载,大数据量写入时效率高,降低了对HBase节点的写入压力。
相比较:
(1)消除了对Hbase集群的插入压力
(2)提高了Job的运行速度,降低了Job的执行时间
不足:目前BulkLoad只适用于只有一个列族的情况,在新版本的HBase中,但列族的限制会消除。
【2】BulkLoad的流程与实现?
需要两个Job配合完成:
(1)第一个Job运行原来业务处理逻辑,处理的结果不直接调用HTableOutputFormat写入到HBase,而是写入到HDFS上面。
(2)第二个Job以第一个Job写入到HDFS上的数据作为输入,然后将其格式化为Hbase的底层存储文件HFile。
(3)调用BulkLoad将第二个Job生成的HFile导入到相应的HBase表中。
代码示例:
001 | import java.io.IOException; |
003 | import org.apache.hadoop.conf.Configuration; |
004 | import org.apache.hadoop.fs.Path; |
005 | import org.apache.hadoop.hbase.HBaseConfiguration; |
006 | import org.apache.hadoop.hbase.KeyValue; |
007 | import org.apache.hadoop.hbase.client.HTable; |
008 | import org.apache.hadoop.hbase.client.Put; |
009 | import org.apache.hadoop.hbase.io.ImmutableBytesWritable; |
010 | import org.apache.hadoop.hbase.mapreduce.HFileOutputFormat; |
011 | import org.apache.hadoop.hbase.mapreduce.KeyValueSortReducer; |
012 | import org.apache.hadoop.hbase.mapreduce.LoadIncrementalHFiles; |
013 | import org.apache.hadoop.hbase.util.Bytes; |
014 | import org.apache.hadoop.io.IntWritable; |
015 | import org.apache.hadoop.io.LongWritable; |
016 | import org.apache.hadoop.io.Text; |
017 | import org.apache.hadoop.mapreduce.Job; |
018 | import org.apache.hadoop.mapreduce.Mapper; |
019 | import org.apache.hadoop.mapreduce.Reducer; |
020 | import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; |
021 | import org.apache.hadoop.mapreduce.lib.input.TextInputFormat; |
022 | import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; |
023 | import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat; |
024 | import org.apache.hadoop.util.GenericOptionsParser; |
026 | public class GeneratePutHFileAndBulkLoadToHBase { |
028 | public static class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> |
031 | private Text wordText= new Text(); |
032 | private IntWritable one= new IntWritable( 1 ); |
034 | protected void map(LongWritable key, Text value, Context context) |
035 | throws IOException, InterruptedException { |
036 | // TODO Auto-generated method stub |
037 | String line=value.toString(); |
038 | String[] wordArray=line.split( " " ); |
039 | for (String word:wordArray) |
042 | context.write(wordText, one); |
048 | public static class WordCountReducer extends Reducer<Text, IntWritable, Text, IntWritable> |
051 | private IntWritable result= new IntWritable(); |
052 | protected void reduce(Text key, Iterable<IntWritable> valueList, |
054 | throws IOException, InterruptedException { |
055 | // TODO Auto-generated method stub |
057 | for (IntWritable value:valueList) |
062 | context.write(key, result); |
067 | public static class ConvertWordCountOutToHFileMapper extends Mapper<LongWritable, Text, ImmutableBytesWritable, Put> |
071 | protected void map(LongWritable key, Text value, Context context) |
072 | throws IOException, InterruptedException { |
073 | // TODO Auto-generated method stub |
074 | String wordCountStr=value.toString(); |
075 | String[] wordCountArray=wordCountStr.split( "\t" ); |
076 | String word=wordCountArray[ 0 ]; |
077 | int count=Integer.valueOf(wordCountArray[ 1 ]); |
080 | byte [] rowKey=Bytes.toBytes(word); |
081 | ImmutableBytesWritable rowKeyWritable= new ImmutableBytesWritable(rowKey); |
082 | byte [] family=Bytes.toBytes( "cf" ); |
083 | byte [] qualifier=Bytes.toBytes( "count" ); |
084 | byte [] hbaseValue=Bytes.toBytes(count); |
085 | // Put 用于列簇下的多列提交,若只有一个列,则可以使用 KeyValue 格式 |
086 | // KeyValue keyValue = new KeyValue(rowKey, family, qualifier, hbaseValue); |
087 | Put put= new Put(rowKey); |
088 | put.add(family, qualifier, hbaseValue); |
089 | context.write(rowKeyWritable, put); |
095 | public static void main(String[] args) throws Exception { |
096 | // TODO Auto-generated method stub |
097 | Configuration hadoopConfiguration= new Configuration(); |
098 | String[] dfsArgs = new GenericOptionsParser(hadoopConfiguration, args).getRemainingArgs(); |
100 | //第一个Job就是普通MR,输出到指定的目录 |
101 | Job job= new Job(hadoopConfiguration, "wordCountJob" ); |
102 | job.setJarByClass(GeneratePutHFileAndBulkLoadToHBase. class ); |
103 | job.setMapperClass(WordCountMapper. class ); |
104 | job.setReducerClass(WordCountReducer. class ); |
105 | job.setOutputKeyClass(Text. class ); |
106 | job.setOutputValueClass(IntWritable. class ); |
107 | FileInputFormat.setInputPaths(job, new Path(dfsArgs[ 0 ])); |
108 | FileOutputFormat.setOutputPath(job, new Path(dfsArgs[ 1 ])); |
110 | int wordCountJobResult=job.waitForCompletion( true )? 0 : 1 ; |
112 | //第二个Job以第一个Job的输出做为输入,只需要编写Mapper类,在Mapper类中对一个job的输出进行分析,并转换为HBase需要的KeyValue的方式。 |
113 | Job convertWordCountJobOutputToHFileJob= new Job(hadoopConfiguration, "wordCount_bulkload" ); |
115 | convertWordCountJobOutputToHFileJob.setJarByClass(GeneratePutHFileAndBulkLoadToHBase. class ); |
116 | convertWordCountJobOutputToHFileJob.setMapperClass(ConvertWordCountOutToHFileMapper. class ); |
117 | //ReducerClass 无需指定,框架会自行根据 MapOutputValueClass 来决定是使用 KeyValueSortReducer 还是 PutSortReducer |
118 | //convertWordCountJobOutputToHFileJob.setReducerClass(KeyValueSortReducer.class); |
119 | convertWordCountJobOutputToHFileJob.setMapOutputKeyClass(ImmutableBytesWritable. class ); |
120 | convertWordCountJobOutputToHFileJob.setMapOutputValueClass(Put. class ); |
122 | //以第一个Job的输出做为第二个Job的输入 |
123 | FileInputFormat.addInputPath(convertWordCountJobOutputToHFileJob, new Path(dfsArgs[ 1 ])); |
124 | FileOutputFormat.setOutputPath(convertWordCountJobOutputToHFileJob, new Path(dfsArgs[ 2 ])); |
126 | Configuration hbaseConfiguration=HBaseConfiguration.create(); |
128 | HTable wordCountTable = new HTable(hbaseConfiguration, "word_count" ); |
129 | HFileOutputFormat.configureIncrementalLoad(convertWordCountJobOutputToHFileJob,wordCountTable); |
132 | int convertWordCountJobOutputToHFileJobResult=convertWordCountJobOutputToHFileJob.waitForCompletion( true )? 0 : 1 ; |
134 | //当第二个job结束之后,调用BulkLoad方式来将MR结果批量入库 |
135 | LoadIncrementalHFiles loader = new LoadIncrementalHFiles(hbaseConfiguration); |
136 | //第一个参数为第二个Job的输出目录即保存HFile的目录,第二个参数为目标表 |
137 | loader.doBulkLoad( new Path(dfsArgs[ 2 ]), wordCountTable); |
139 | //最后调用System.exit进行退出 |
140 | System.exit(convertWordCountJobOutputToHFileJobResult); |
比如原始的输入数据的目录为:/rawdata/test/wordcount/20131212
中间结果数据保存的目录为:/middata/test/wordcount/20131212
最终生成的HFile保存的目录为:/resultdata/test/wordcount/20131212
运行上面的Job的方式如下:
hadoop jar test.jar /rawdata/test/wordcount/20131212 /middata/test/wordcount/20131212 /resultdata/test/wordcount/20131212
【3】说明
(1)HFile方式在所有的加载方案里面是最快的,前提是:表是空的。如果表中已经有了数据,HFile再导入到HBase的表中会触发Split操作。
(2)最终的输出结果。
无论是map还是reduce,输出部分key和value的类型是:<ImmutableBytesWritable,keyvalue>或者<ImmutableBytesWritable,Put>.
(3)最终输出部分,Value类型是KeyValue 或Put,对应的Sorter分别是KeyValueSortReducer或PutSortReducer,这个 SorterReducer 可以不指定,因为源码中已经做了判断
(4)MR例子中Job.setOutputFormatClass(HFileOutputFormat.class);HFileOutputFormat只适合一次对但列族组织成HFile文件,多列簇需要多个Job,不过新版本的HBase已经解决了这个限制。
(5)MR例子中最后生成HFile存储在HDFS上,输出路径下的子目录是各个列族。如果对HFile进行入库HBase,相当于move HFile到HBase的Region中,HFile子目录的列族内容就没有了。
(6)最后一个Reduce没有setNumReduceTasks是因为,该设置由框架根据region个数自动配置。