HBase RowKey設計的那些事

         在說rowkey設計以前,先回答一下你們配置HBase時可能有的疑問,關於HBase是否須要單獨的ZooKeeper託管?嗯,若是隻是部署HBase,我建議不要用單獨的ZooKeeper進行託管,用HBase自帶的ZooKeeper就能夠,假如要部署其餘應用,好比Spark等能夠單獨部署一個ZooKeeper集羣。好,廢話很少說了,下面說說RowKey設計的事。 java

先談HBase底層架構

    對於新手來講,RowKey的設計是比較陌生的一件事,看上去很簡單的東西,其實很是複雜,RowKey的設計基本上能夠劃分紅兩大影響,分別是分析維度、查詢性能。爲何要這樣分呢?咱們再回頭看看HBase系統架構圖: 架構

這種設計看上去並無什麼問題,可是這種設計隱藏了很是多陷阱,假如CompanyCode字段很是固定,而TimeStamp變化比較大的話,會形成單個Region連續地存儲這些數據,數據量很是大的時候,這個Region會集中了這些數據,當有應用須要訪問這些數據時,形成了RPC timeout,甚至應用程序直接報錯,沒法執行。 app

合理的RowKey設計方法

         基於上面的緣由,咱們須要考慮單點集中以及數據查詢兩方面的因素,所以,在RowKey上咱們要針對這兩個問題進行方案設計。 分佈式

         首先是單點集中問題,咱們出現這樣單點集中的緣由大概有如下幾種: 性能

RowKey前面的字符過於固定 測試

l  集羣結點數量過少 大數據

集羣結點數量是由咱們自身硬件資源限制的,這個咱們不考慮在內,咱們主要考慮RowKey設計。既然是由於前面字符過於集中,那麼咱們能夠經過在RowKey前面添加隨機的一個字符串,下面是引自《HBase Essential》裏面的一個隨機字符計算方法: spa

int saltNumber = new Long(new Long(timestamp).hashCode()) %<number of region servers> 設計

用這種方法,咱們在插入數據的時候能夠人爲地隨機把一斷時間內的數據打散,分佈到各個RegionServer下的Region中,充分利用分佈式的優點,這樣作不緊能夠加快數據的讀寫訪問,也解決了數據集中的問題。 code

改良後的RowKey設計方案

         經過上面的技術研討,能夠制定出如下的RowKey設計方案了:

隨機字符(2) + 時間位(14位)+  CompanyCode4位)

         我在實際測試過程當中,先後兩種方案對比,前者的MR程序跑了1個小時,後者只花了5分鐘。

合理地編寫查詢代碼

         咱們完成數據存儲以後,假如要取出某部分數值,須要設置Scan查詢,如下是我在實戰中用到的部分代碼,僅供參考:

public class HBaseTableDriver extends Configured implements Tool {

 

    public int run(String[] arg0) throws Exception {

       if(arg0.length < 4 || arg0.length > 5)

           throw new IllegalArgumentException("The input argument need:start && stop && farmid && turbineNum && calid");

       if(arg0[0].length() != 8 || arg0[1].length() != 8)

           throw new IllegalArgumentException("The date format should be yyyyMMdd");

      

       Configuration conf = HBaseConfiguration.create();

       conf.set("hbase.zookeeper.quorum", ConstantValues.QUOREM);

       conf.set("hbase.zookeeper.property.clientPort", ConstantValues.CLIENT_PORT);

      

       //extract table && tagid && start time && end time

       conf.set("start", arg0[0]);

       conf.set("stop", arg0[1]);

        conf.set("farmid", arg0[2]);

       conf.set("turbineNum", arg0[3]);

       conf.set("calid", arg0[4]);

       String startRow = "0" + arg0[0] + " 000000" + arg0[2] + "001";

       String stopRow = "2" + arg0[1] + " 235959" + arg0[2] + RowKeyGenerator.addZero(Integer.parseInt(arg0[3]));

      

       String targetKpiTableName = "kpi2";

      

       Job job = Job.getInstance(conf, "KPIExtractor");

        job.setJarByClass(KPIExtractor.class);

        job.setNumReduceTasks(6);

        Scan scan = new Scan();

        scan.addColumn("f".getBytes(), "v".getBytes());

        String regEx = "^\\d{1}(?:" + arg0[0].substring(0, 4) + "|" + arg0[1].substring(0, 4) + ")\\d{17}";

        switch(arg0[4]){

        case "1":

               regEx = regEx + "(?:823|834)$";

               startRow = startRow + "823";

               stopRow = stopRow + "834";

            break;

        case "2":

            regEx = regEx + "211$";

            startRow = startRow + "211";

           stopRow = stopRow + "211";

            break;

        case "3":

            regEx = regEx + "544$";

            startRow = startRow + "544";

           stopRow = stopRow + "544";

            break;

        case "4":

            regEx = regEx + "208$";

            startRow = startRow + "208";

           stopRow = stopRow + "208";

            break;

        case "5":

            regEx = regEx + "(?:739|823)$";

            startRow = startRow + "739";

           stopRow = stopRow + "823";

            break;

        case "6":

            regEx = regEx + "(?:211|823)$";

            startRow = startRow + "211";

           stopRow = stopRow + "823";

            break;

        case "7":

            regEx = regEx + "708$";

            startRow = startRow + "708";

           stopRow = stopRow + "708";

            break;

        case "8":

            regEx = regEx + "822$";

            startRow = startRow + "822";

           stopRow = stopRow + "822";

            break;

        case "9":

            regEx = regEx + "211$";

            startRow = startRow + "211";

           stopRow = stopRow + "211";

            break;

        default:

            throw new IllegalArgumentException("UnKnown Argument calid:"+arg0[4]+",it should be between 1~9");

        }

        scan.setStartRow(startRow.getBytes());

        scan.setStopRow(stopRow.getBytes());

        scan.setFilter(new RowFilter(CompareOp.EQUAL, new RegexStringComparator(regEx)));

        TableMapReduceUtil.initTableMapperJob("hellowrold", scan , KPIMapper.class, ImmutableBytesWritable.class, ImmutableBytesWritable.class, job);

        TableMapReduceUtil.initTableReducerJob(targetKpiTableName, KPIReducer.class, job);

        job.waitForCompletion(true);

       return 0;

    }

   

}



注意點:

l  這裏主要用到了RowFilter對RowKey進行過濾,而且我在查閱相關資料的時候,別人建議不要在大數據量下使用ColumnFilter,性能很是低。

l  能夠經過Configuration爲Map/Reduce傳輸參數值。

相關文章
相關標籤/搜索