利用BulkLoad導入Hbase表

1、插入HBase表傳統方法具有什麼問題?

我們先看下 HBase 的寫流程:

    通常 MapReduce 在寫HBase時使用的是TableOutputFormat方式,在map/reduce中直接生成put對象寫入HBase,該方式在大量數據寫入時效率低下,因爲HBase會block寫入,頻繁進行flush、split、compact等大量IO操作,這樣對HBase節點的穩定性也會造成一定的影響,例如GC時間過長,響應變慢,導致節點超時退出,並引起一系列連鎖反應,而HBase支持BulkLoad的寫入方式,它是利用HBase數據按照特定格式存儲在HDFS內這一原理,直接利用MapReduce生成持久化的HFile數據格式文件,然後上傳至合適位置,即完成巨量數據快速入庫的辦法。配合mapreduce完成,高效便捷,而且不佔用region資源,增添負載,在大數據量寫入時能極大的提高寫入效率,並降低對HBase節點的寫入壓力。
    通過使用先生成HFile,然後再BulkLoad到HBase的方式來替代之前直接調用HTableOutputFormat的方法有如下的好處:
(1)消除了對HBase集羣的插入壓力
(2)提高了Job的運行速度,降低了Job的執行時間

2、BulkLoad實踐

    BulkLoad原理已在上面介紹,其具體實現流程是先用MapReduce生成HFile文件輸出並且存儲在HDFS上,然後利用loader.doBulkLoad(HFIle,HBaseTable);寫入HBase中。具體代碼如下:

public class BulkLoad {
    
    private static final String JOBNAME = "BulkLoad";
    private static final String TABLENAME = "bulkLoad";
    private static final String PATH_IN = "/xx/xx";    //輸入路徑
    private static final String PATH_OUT = "/xx/xx";    //輸入路徑    
    
    private static final String SEPARATOR = "\\|";
    
    private static final byte[] ColumnFamily =  "f".getBytes();  // 列簇
    private static final byte[] QUALIFIER_TAG1 =  "tag1".getBytes();    // 列名  
    private static final byte[] QUALIFIER_TAG2 =  "tag2".getBytes(); 
    private static final byte[] QUALIFIER_TAG3 =  "tag3".getBytes(); 
    private static final byte[] QUALIFIER_TAG4 =  "tag4".getBytes(); 
    private static final byte[] QUALIFIER_TAG5 =  "tag5".getBytes(); 
    private static final byte[] QUALIFIER_TAG6 =  "tag6".getBytes(); 
    private static final byte[] QUALIFIER_TAG7 =  "tag7".getBytes(); 
    private static final byte[] QUALIFIER_TAG8 =  "tag8".getBytes(); 
    private static final byte[] QUALIFIER_TAG9 =  "tag9".getBytes(); 
    private static final byte[] QUALIFIER_TAG10 =  "tag10".getBytes(); 
    
    public static class Map extends Mapper<LongWritable, Text, ImmutableBytesWritable, Put> {
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            String[] strArr = value.toString().split(SEPARATOR);
            String row = strArr[0];
            Put put = new Put(Bytes.toBytes(row.toString()));     // rowkey
            put.add(ColumnFamily, QUALIFIER_TAG1, Bytes.toBytes(strArr[2]));
            put.add(ColumnFamily, QUALIFIER_TAG2, Bytes.toBytes(strArr[3]));
            put.add(ColumnFamily, QUALIFIER_TAG3, Bytes.toBytes(strArr[4]));
            put.add(ColumnFamily, QUALIFIER_TAG4, Bytes.toBytes(strArr[5]));
            put.add(ColumnFamily, QUALIFIER_TAG5, Bytes.toBytes(strArr[6]));
            put.add(ColumnFamily, QUALIFIER_TAG6, Bytes.toBytes(strArr[7]));
            put.add(ColumnFamily, QUALIFIER_TAG7, Bytes.toBytes(strArr[8]));
            put.add(ColumnFamily, QUALIFIER_TAG8, Bytes.toBytes(strArr[9]));
            put.add(ColumnFamily, QUALIFIER_TAG9, Bytes.toBytes(strArr[10]));
            put.add(ColumnFamily, QUALIFIER_TAG10, Bytes.toBytes(strArr[11]));
            
            context.write(new ImmutableBytesWritable(value.getBytes()), put);
        }
    }
    
    public static void main(String[] args) throws Exception {

        Configuration conf = HBaseConfiguration.create();
        conf.set("hbase.zookeeper.quorum", "xx,xx,xx");

        Job job = new Job(conf, JOBNAME);
        job.setJarByClass(CreditScoreBulkLoad.class);
        job.setMapOutputKeyClass(ImmutableBytesWritable.class);
        job.setMapOutputValueClass(Put.class);
        
        job.setMapperClass(Map.class);
        //這個 SorterReducer(KeyValueSortReducer或PutSortReducer) 可以不指定,
        //因爲源碼中已經做了判斷
        job.setReducerClass(PutSortReducer.class);
        job.setOutputFormatClass(HFileOutputFormat.class);

        FileSystem fs = FileSystem.get(URI.create("/"),conf);
        
        Path outPath = new Path(PATH_OUT);
        if (fs.exists(outPath))fs.delete(outPath, true);

        FileInputFormat.setInputPaths(job, new Path(PATH_IN));
        FileOutputFormat.setOutputPath(job, outPath);

        HTable table = new HTable(conf, TABLENAME);
        HFileOutputFormat.configureIncrementalLoad(job, table);

        if(job.waitForCompletion(true)){
            LoadIncrementalHFiles loader = new LoadIncrementalHFiles(conf);
            loader.doBulkLoad(outPath, table);
        }

        System.exit(0);
    }
}

3、說明與注意事項:

(0)上文提到會將生成的HFile文件插入HBase中,在此過程中由MapReduce生成的存儲在HDFS上的文件會消失,其實插入HBase就是將HFile文件移到HBase中,但是HFile文件在HDFS上的存儲路徑還在,只是裏面的文件消失了。

(1)利用BulkLoad導入HBase時,定記得在建表時做region的預切分(隨後會對HBase的Region進行總結),HFileOutputFormat.configureIncrementalLoad方法會根據region的數量來覺得reduce的數量以及每個reduce覆蓋的rowkey範圍。否則當個reduce過大,任務處理不均衡,導致任務運行時間過長。

(2)單個rowkey下的子列不要過多,否則在reduce階段排序的時候會造成oom,有一種辦法是通過二次排序來避免reduce階段的排序,看應用而定。

(3)該代碼執行完後需要將hdfs中生成好的hfile寫入到hbase表中。採用hadoop jar hbase-version.jar completebulkload /hfilepath tablename 命令實現。

(4)HFile方式在所有的加載方案裏面是最快的,不過有個前提——數據是第一次導入,表是空的。如果表中已經有了數據。HFile再導入到hbase的表中會觸發split操作。

(5)最終輸出結果,無論是map還是reduce,輸出部分key和value的類型必須是: < ImmutableBytesWritable, KeyValue>或者< ImmutableBytesWritable, Put>。
否則報這樣的錯誤:

java.lang.IllegalArgumentException: Can't read partitions file
...
Caused by: java.io.IOException: wrong key class: org.apache.hadoop.io.*** is not class org.apache.hadoop.hbase.io.ImmutableBytesWritable

(6)最終輸出部分,Value類型是KeyValue 或Put,對應的Sorter分別是KeyValueSortReducer或PutSortReducer,這個 SorterReducer 可以不指定,因爲源碼中已經做了判斷:

if (KeyValue.class.equals(job.getMapOutputValueClass())) {
    job.setReducerClass(KeyValueSortReducer.class);
} else if (Put.class.equals(job.getMapOutputValueClass())) {
    job.setReducerClass(PutSortReducer.class);
} else {
    LOG.warn("Unknown map output value type:" + job.getMapOutputValueClass());
}

(7) MR例子中job.setOutputFormatClass(HFileOutputFormat.class); HFileOutputFormat只適合一次對單列族組織成HFile文件,多列簇需要起多個job,不過新版本的 Hbase 已經解決了這個限制。

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

(9)最後一個 Reduce 沒有 setNumReduceTasks 是因爲,該設置由框架根據region個數自動配置的。

(10)下邊配置部分,註釋掉的其實寫不寫都無所謂,因爲看源碼就知道configureIncrementalLoad方法已經把固定的配置全配置完了,不固定的部分才需要手動配置。

4、Refer:

1、http://blog.csdn.net/kirayuan/article/details/6371635

2、http://blog.pureisle.net/archives/1950.html

3、http://shitouer.cn/2013/02/hbase-hfile-bulk-load

4、http://my.oschina.net/leejun2005/blog/187309


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