MapReduce生成HFile入庫到HBase

一、這種方式有很多的優點:

1. 如果我們一次性入庫hbase巨量數據,處理速度慢不說,還特別佔用Region資源, 一個比較高效便捷的方法就是使用 “Bulk Loading”方法,即HBase提供的HFileOutputFormat類。

2. 它是利用hbase的數據信息按照特定格式存儲在hdfs內這一原理,直接生成這種hdfs內存儲的數據格式文件,然後上傳至合適位置,即完成巨量數據快速入庫的辦法。配合mapreduce完成,高效便捷,而且不佔用region資源,增添負載。

二、這種方式也有很大的限制:

1. 僅適合初次數據導入,即表內數據爲空,或者每次入庫表內都無數據的情況。

2. HBase集羣與Hadoop集羣爲同一集羣,即HBase所基於的HDFS爲生成HFile的MR的集羣(額,咋表述~~~)

三、接下來一個demo,簡單介紹整個過程。

1. 生成HFile部分

package zl.hbase.mr;
 
import java.io.IOException;
 
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.KeyValue;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import org.apache.hadoop.hbase.mapreduce.HFileOutputFormat;
import org.apache.hadoop.hbase.mapreduce.KeyValueSortReducer;
import org.apache.hadoop.hbase.mapreduce.SimpleTotalOrderPartitioner;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;
 
import zl.hbase.util.ConnectionUtil;
 
public class HFileGenerator {
 
    public static class HFileMapper extends
            Mapper<LongWritable, Text, ImmutableBytesWritable, KeyValue> {
        @Override
        protected void map(LongWritable key, Text value, Context context)
                throws IOException, InterruptedException {
            String line = value.toString();
            String[] items = line.split(",", -1);
            ImmutableBytesWritable rowkey = new ImmutableBytesWritable(
                    items[0].getBytes());
 
            KeyValue kv = new KeyValue(Bytes.toBytes(items[0]),
                    Bytes.toBytes(items[1]), Bytes.toBytes(items[2]),
                    System.currentTimeMillis(), Bytes.toBytes(items[3]));
            if (null != kv) {
                context.write(rowkey, kv);
            }
        }
    }
 
    public static void main(String[] args) throws IOException,
            InterruptedException, ClassNotFoundException {
        Configuration conf = new Configuration();
        String[] dfsArgs = new GenericOptionsParser(conf, args)
                .getRemainingArgs();
 
        Job job = new Job(conf, "HFile bulk load test");
        job.setJarByClass(HFileGenerator.class);
 
        job.setMapperClass(HFileMapper.class);
        job.setReducerClass(KeyValueSortReducer.class);
 
        job.setMapOutputKeyClass(ImmutableBytesWritable.class);
        job.setMapOutputValueClass(Text.class);
 
        job.setPartitionerClass(SimpleTotalOrderPartitioner.class);
 
        FileInputFormat.addInputPath(job, new Path(dfsArgs[0]));
        FileOutputFormat.setOutputPath(job, new Path(dfsArgs[1]));
 
        HFileOutputFormat.configureIncrementalLoad(job,
                ConnectionUtil.getTable());
        System.exit(job.waitForCompletion(true) ? 0 : 1);
    }
}

生成HFile程序說明:

①. 最終輸出結果,無論是map還是reduce,輸出部分key和value的類型必須是: < ImmutableBytesWritable, KeyValue>或者< ImmutableBytesWritable, Put>。

②. 最終輸出部分,Value類型是KeyValue 或Put,對應的Sorter分別是KeyValueSortReducer或PutSortReducer。

③. MR例子中job.setOutputFormatClass(HFileOutputFormat.class); HFileOutputFormat只適合一次對單列族組織成HFile文件

④. MR例子中HFileOutputFormat.configureIncrementalLoad(job, table);自動對job進行配置。SimpleTotalOrderPartitioner是需要先對key進行整體排序,然後劃分到每個reduce中,保證每一個reducer中的的key最小最大值區間範圍,是不會有交集的。因爲入庫到HBase的時候,作爲一個整體的Region,key是絕對有序的。

⑤. MR例子中最後生成HFile存儲在HDFS上,輸出路徑下的子目錄是各個列族。如果對HFile進行入庫HBase,相當於move HFile到HBase的Region中,HFile子目錄的列族內容沒有了。

2. HFile入庫到HBase

package zl.hbase.bulkload;
 
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.mapreduce.LoadIncrementalHFiles;
import org.apache.hadoop.util.GenericOptionsParser;
 
import zl.hbase.util.ConnectionUtil;
 
public class HFileLoader {
 
    public static void main(String[] args) throws Exception {
        String[] dfsArgs = new GenericOptionsParser(
                ConnectionUtil.getConfiguration(), args).getRemainingArgs();
        LoadIncrementalHFiles loader = new LoadIncrementalHFiles(
                ConnectionUtil.getConfiguration());
        loader.doBulkLoad(new Path(dfsArgs[0]), ConnectionUtil.getTable());
    }
 
}

通過HBase中 LoadIncrementalHFiles的doBulkLoad方法,對生成的HFile文件入庫。


hbase提供了寫的操作,通常,我們可以採用HBase的Shell 客戶端或者Java API進行操作。

如果數據量大的話,這兩種操作是很費時的。其實如果瞭解了HBase的數據底層存儲的細節的話,HBase的數據存儲格式是HFile定義的格式。

批量導入HBase主要分兩步:

  • 通過mapreduce在輸出目錄OutputDir下生成一系列按Store存儲結構一樣的,存儲HFile文件
  • 通過LoadIncrementalHFiles.doBulkLoad把OutputDir裏面的數據導入HBase表中
優點
  1. HBase提供了一種直接寫hfile文件的類,同時通過類似傳統數據庫的load把這些文件寫進去,不再需要通過客戶端或Java API一條一條插進去,
  2. 這些接口簡單方便,快捷靈活;
  3. 應用不需要一直去連HBase集羣進行RPC multi寫,提高mapreduce效率;
  4. HBase集羣也相應減少不必要的連接,可以讓它去多幹些其它的事,效率更加高效,降低HBase集羣因爲大量併發寫而產生不必要的風險。
1. 從HDFS批量導入

在MapReduce裏面就把想要的輸出成HFileOutputFormat格式的文件,然後通過LoadIncrementalHFiles.doBulkLoad方式就可以load進去即可。例子如下: 

Java代碼  收藏代碼
  1. Configuration conf = getConf();  
  2. conf.set("hbase.table.name", args[2]);  
  3. // Load hbase-site.xml  
  4. HBaseConfiguration.addHbaseResources(conf);  
  5. Job job = new Job(conf, "HBase Bulk Import Example");  
  6. job.setJarByClass(Mapper2.class);  
  7. job.setMapperClass(Mapper2.class);  
  8. job.setMapOutputKeyClass(ImmutableBytesWritable.class);  
  9. job.setMapOutputValueClass(KeyValue.class);  
  10. job.setInputFormatClass(TextInputFormat.class);  
  11.   
  12. // Auto configure partitioner and reducer  
  13. HTable hTable = new HTable(conf, args[2]);  
  14. HFileOutputFormat.configureIncrementalLoad(job, hTable);  
  15.   
  16. FileInputFormat.addInputPath(job, new Path(args[0]));  
  17. FileOutputFormat.setOutputPath(job, new Path(args[1]));  
  18. job.waitForCompletion(true);  
  19.   
  20. // Load generated HFiles into table  
  21. LoadIncrementalHFiles loader = new LoadIncrementalHFiles(conf);  
  22. loader.doBulkLoad(new Path(args[1]), hTable);  
2. 從MySQL批量導入

這個星期把一些MySQL表導到線上HBase表。這個MySQL表散了100份,在HBase集羣未提供向業務使用時,通過Sqoop工具導進HBase表所花費的時間大約32個小時(已串行化),在hbase集羣繁忙時,花了10個小時都還沒有把一張表導到HBase裏面。這是有原因的,Sqoop未實現批量導的功能,它通常是邊讀邊寫。

後來自己寫了一個從MySQL批量導入HBase的應用程序,每個表導入HBase所需時間平均只需要8分鐘。

核心代碼如下:

Java代碼  收藏代碼
  1. HBaseConfiguration.addHbaseResources(conf);  
  2.   
  3. Job job = new Job(conf, "Load_MySQL_" + table + "_to_HBase_" + hbaseTable);  
  4. // 用來讀mysql的Mapper  
  5. job.setJarByClass(MysqlMapper.class);  
  6.   
  7. job.setMapperClass(MysqlMapper.class);  
  8. job.setMapOutputKeyClass(ImmutableBytesWritable.class);  
  9. job.setMapOutputValueClass(KeyValue.class);  
  10. //配置DB參數  
  11. DBConfiguration.configureDB(job.getConfiguration(), driver, connect, username, password);  
  12. DataDrivenDBInputFormat.setInput(job, dbWritableClass, query, boundaryQuery);  
  13. DataDrivenDBInputFormat.setInput(job, dbWritableClass, table, conditions, splitBy, columns);  
  14. //設置輸出路徑  
  15. FileOutputFormat.setOutputPath(job, new Path(tmpTargetDir));  
  16. // 自動設置partitioner和reduce  
  17. HTable hTable = new HTable(conf, hbaseTable);  
  18. HFileOutputFormat.configureIncrementalLoad(job, hTable);  
  19.   
  20. job.waitForCompletion(true);  
  21.   
  22. // 上面JOB運行完後,就把數據批量load到HBASE中  
  23. LoadIncrementalHFiles loader = new LoadIncrementalHFiles(conf);  
  24. loader.doBulkLoad(new Path(tmpTargetDir), hTable);  

 



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章