目录:

 【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表中。

代码示例:

import java.io.IOException;
002	 
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>
029	    {
031	        private Text wordText=new Text();
032	        private IntWritable one=new IntWritable(1);
033	        @Override
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)
040	            {
041	                wordText.set(word);
042	                context.write(wordText, one);
043	            }
044	             
045	        }
046	    }
048	    public static class WordCountReducer extends Reducer<Text, IntWritable, Text, IntWritable>
049	    {
051	        private IntWritable result=new IntWritable();
052	        protected void reduce(Text key, Iterable<IntWritable> valueList,
053	                Context context)
054	                throws IOException, InterruptedException {
055	            // TODO Auto-generated method stub
056	            int sum=0;
057	            for(IntWritable value:valueList)
058	            {
059	                sum+=value.get();
060	            }
061	            result.set(sum);
062	            context.write(key, result);
063	        }
065	    }
067	    public static class ConvertWordCountOutToHFileMapper extends Mapper<LongWritable, Text, ImmutableBytesWritable, Put>
068	            {
070	        @Override
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]);
079	            //创建HBase中的RowKey
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);
091	        }
093	    }
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]));
109	        //提交第一个Job
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]));
125	        //创建HBase的配置对象
126	        Configuration hbaseConfiguration=HBaseConfiguration.create();
127	        //创建目标表对象
128	        HTable wordCountTable =new HTable(hbaseConfiguration, "word_count");
129	        HFileOutputFormat.configureIncrementalLoad(convertWordCountJobOutputToHFileJob,wordCountTable);
130	        
131	        //提交第二个job
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);
142	    }
144	}

比如原始的输入数据的目录为:/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个数自动配置。