HBase 高性能獲取數據(多線程批量式解決辦法) + MySQL和HBase性能測試比較

摘要:   在前篇博客裏已經講述了經過一個自定義 HBase Filter來獲取數據的辦法,在末尾指出此辦法的性能是不能知足應用要求的,很顯然對於如此成熟的HBase來講,高性能獲取數據應該不是問題。下面首先簡單介紹了搜索引擎的性能,而後詳細說明了HBase與MySQL的性能對比,這裏的數據都是通過實際的測試得到的。最後,給出了採用多線程批量從HBase中取數據的方案,此方案通過測試要比經過自定義Filter的方式性能高出不少。html

關鍵詞: HBase, 高性能, 獲取數據, 性能對比, 多線程java

需求:    HBase高性能獲取數據 

Solr和HBase專輯數據庫

一、「關於Solr的使用總結的心得體會」(http://www.cnblogs.com/wgp13x/p/3742653.html)緩存

二、「中文分詞器性能比較」(http://www.cnblogs.com/wgp13x/p/3748764.html)安全

三、「Solr與HBase架構設計」(http://www.cnblogs.com/wgp13x/p/a8bb8ccd469c96917652201007ad3c50.html)服務器

四、 「大數據架構: 使用HBase和Solr將存儲與索引放在不一樣的機器上」(http://www.cnblogs.com/wgp13x/p/3927979.html)多線程

五、「一個自定義 HBase Filter -經過RowKeys來高性能獲取數據」(http://www.cnblogs.com/wgp13x/p/4196466.html)架構

六、「HBase 高性能獲取數據 - 多線程批量式解決辦法」(http://www.cnblogs.com/wgp13x/p/4245182.html併發


一、  如何存儲十億、百億數據?    答:使用數據存儲集羣,增長水平拓展能力,以容納上百億數據量異步

二、  如何保證在十億、百億數據上面的查詢效率?    答:使用分佈式搜索引擎

數據量過億,不管是存儲在關係型數據庫仍是非關係型數據庫,使用非索引字段進行條件查詢、模糊查詢等複雜查詢都是一件極其緩慢甚至是不可能完成的任務,數據庫索引創建的是二級索引,大數據查詢主要依靠搜索引擎。

根據Solr中國資料顯示,在2400億每條數據大概200字節的數據創建索引,搭建分佈式搜索引擎,在50臺機器進行搜索測試,其中有條件查詢、模糊查詢等,其中80%的搜索可以在毫秒內返回結果,剩下一部分可以在20秒內返回,還有5%左右的查詢須要在50秒左右的時間完成查詢請求,客戶端查詢請求的併發量爲100個客戶端。

如下結論均是在同一臺服務器上的測試結果。 

MySQL單機隨機讀寫能力測試

MySQL(InnoDB)

運行環境

Window Server 2008 x64

存儲引擎

InnoDB

最大存儲容量

64T

列數

39

每條數據的大小

Avg=507Byte

總數據量

302,418,176

佔用的磁盤空間

210G

插入效率

總共耗時13個小時,每秒約6500條,隨着數據量的增大,插入的效率影響不大

單條數據全表隨機讀取時間

30ms

百條數據全表隨機讀取時間

1,783ms;1,672ms

千條數據全表隨機讀取時間

18,579ms;15,473ms

其餘

條件查詢、Order By、模糊查詢基本上是沒法響應的


HBase基本說明與性能測試
 

HBase

數據庫類型

NoSql—列式數據庫

運行所須要的環境

Linux

是否能夠搭建集羣

自然的分佈式數據庫,具備自動分片功能

可擴展性

強,無縫支持水平拓展

插入

與設置的參數關係很大,批量插入和單條插入差異大,單臺機器可以實現1w~3w之間的插入速度

更新

 

刪除

 

查詢

只支持按照rowkey來查詢或者全表掃描

範圍查詢

不支持

模糊匹配

不支持

時間範圍查詢

不支持

分頁查詢

能夠作到

數據庫安全性

大數據量下的查詢響應時間

各個數據級別下的響應時間: (均爲隨機讀取,不命中緩存)

13-------------------5ms(單行)

23-------------------124ms(30)

大數據量下佔用的磁盤空間

各個數據級別下的磁盤佔用空間(以出租車表爲例,17個字段,一行200個字節):

11-------------------18G(使用GZ壓縮) 

是否有良好的技術支持

社區活躍,可是配置複雜,參數繁多,學習代價比較大

數據導入和導出

有從RDBMS導入數據的工具Sqoop

熱備份

 

異步複製

 

是否須要商業付費

是否開源

優勢

一、  支持高效穩定的大數據存儲,上億行、上百萬列、上萬個版本,對數據自動分片

二、  列式存儲保證了高效的隨機讀寫能力

三、  列數能夠動態增加

四、  水平拓展十分容易

五、  擁有良好的生態系統,Sqoop用戶數據的導入、Pig能夠做爲ETL工具,Hadoop做爲分佈式計算平臺

缺點

一、  學習複雜

二、  不支持範圍查詢、條件查詢等查詢


        從上面的測試結果表中能夠看出,MySQL單表插入速度爲每秒6500條,HBase單臺機器可以實現1w~3w之間的插入速度,這充分說明HBase插入數據的速度比MySQL高不少。在MySQL單機隨機讀寫能力測試中單條數據全表隨機讀取時間是指依據主鍵去MySQL單表取數據花費的時間;在HBase基本說明與性能測試中,大數據量下查詢響應時間是指依照Rowkey到HBase取數據所花費的時間。30ms對5ms,這說明HBase取數據的速度之快也是MySQL可望不可即的。



 

在進行上面的性能測試中,不管是從MySQL經過主鍵讀取,仍是從HBase經過Rowkey讀取,讀取的數據量都不大,不超過1000條。當須要一次性讀取萬級數據時,須要經過設計優化的代碼來保證讀取速度。

在實現過程當中,發現當批量Get的數據量達到必定程度時(如10W),向HBase請求數據會從innerGet發生EOFExeption異常。這裏附加上一段從HBase依照多Rowkey獲取數據的代碼,它採用了性能高的批量Get。在這裏,我將這種大批量請求化爲每1000個Get的請求,而且採用多線程方式,通過驗證,這種方法的效率仍是蠻高的。

 

private ExecutorService pool = Executors.newFixedThreadPool(10);    // 這裏建立了10個 Active RPC Calls
    public Datas getDatasFromHbase(final List<String> rowKeys,
        final List<String> filterColumn, boolean isContiansRowkeys,
        boolean isContainsList)
    {
        if (rowKeys == null || rowKeys.size() <= 0)
        {
            return Datas.getEmptyDatas();
        }
        final int maxRowKeySize = 1000;
        int loopSize = rowKeys.size() % maxRowKeySize == 0 ? rowKeys.size()
            / maxRowKeySize : rowKeys.size() / maxRowKeySize + 1;
        ArrayList<Future<List<Data>>> results = new ArrayList<Future<List<Data>>>();
        for (int loop = 0; loop < loopSize; loop++)
        {
            int end = (loop + 1) * maxRowKeySize > rowKeys.size() ? rowKeys
                .size() : (loop + 1) * maxRowKeySize;
            List<String> partRowKeys = rowKeys.subList(loop * maxRowKeySize,
                end);
            HbaseDataGetter hbaseDataGetter = new HbaseDataGetter(partRowKeys,
                filterColumn, isContiansRowkeys, isContainsList);
            synchronized (pool)
            {
                Future<List<Data>> result = pool.submit(hbaseDataGetter);
                results.add(result);
            }
        }
        Datas datas = new Datas();
        List<Data> dataQueue = new ArrayList<Data>();
        try
        {
            for (Future<List<Data>> result : results)
            {
                List<Data> rd = result.get();
                dataQueue.addAll(rd);
            }
            datas.setDatas(dataQueue);
        }
        catch (InterruptedException | ExecutionException e)
        {
            e.printStackTrace();
        }
        return datas;
    }
class HbaseDataGetter implements Callable<List<Data>>
    {
        private List<String> rowKeys;
        private List<String> filterColumn;
        private boolean isContiansRowkeys;
        private boolean isContainsList;
 
        public HbaseDataGetter(List<String> rowKeys, List<String> filterColumn,
            boolean isContiansRowkeys, boolean isContainsList)
        {
            this.rowKeys = rowKeys;
            this.filterColumn = filterColumn;
            this.isContiansRowkeys = isContiansRowkeys;
            this.isContainsList = isContainsList;
        }
 
        @Override
        public List<Data> call() throws Exception
        {
            Object[] objects = getDatasFromHbase(rowKeysfilterColumn);
            List<Data> listData = new ArrayList<Data>();
            for (Object object : objects)
            {
                Result r = (Result) object;
                Data data = assembleData(r, filterColumnisContiansRowkeys,
                    isContainsList);
                listData.add(data);
            }
            return listData;
        }
    }
private Object[] getDatasFromHbase(List<String> rowKeys,
        List<String> filterColumn)
    {
        createTable(tableName);
        Object[] objects = null;
        HTableInterface hTableInterface = createTable(tableName);
        List<Get> listGets = new ArrayList<Get>();
        for (String rk : rowKeys)
        {
            Get get = new Get(Bytes.toBytes(rk));
            if (filterColumn != null)
            {
                for (String column : filterColumn)
                {
                    get.addColumn(columnFamilyName.getBytes(),
                        column.getBytes());
                }
            }
            listGets.add(get);
        }
        try
        {
            objects = hTableInterface.get(listGets);
        }
        catch (IOException e1)
        {
            e1.printStackTrace();
        }
        finally
        {
            try
            {
                listGets.clear();
                hTableInterface.close();
            }
            catch (IOException e)
            {
                e.printStackTrace();
            }
        }
        return objects;
    }
private HTableInterface createTable(String tableName)
    {
        HTable table = null;
        try
        {
            table = new HTable(initHbaseConfiguration(), tableName);
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        return table;       
    }

 

能夠確定的是,此種批量取數據的方法達成的速度,與取一次性數據的數量基本成線性關係,與總數據量相關不大,須要取出的數據越多耗時也就越多,通過測試一次性取1000條數據花費大約在2至3s之內,總數據量爲400W。而經過自定義Filter方式取數據的方法的速度,與取一次性數據的數量相關不大,與總數據量成線性關係,總數據量越大取出越慢,即便只需取一條 ,由於此方式對HBase每條數據都過濾一遍。這樣,若是在總數不大,須要取數據量較大的狀況下,經過自定義Filter取數據的方式可能還佔有些優點,但在正常狀況下,此種批量取數據的方法仍是優點更大。

 

不得不提的是:在實現過程當中,我曾將這種大批量請求化爲每4000個Get的多線程請求方式,咱們的HBase版本爲0.94,這樣在一次性請求200000條數據時,HBase直接掛機,client拋出EOFException異常,【processBatchCallback(HConnectionManager.java:1708),processBatch(HConnectionManager.java:1560),(HTable.java:779)】,查看併發鏈接數與每1000個Get請求同樣保持爲10個左右,沒有異常。查閱相關資料後,咱們懷疑,這是因爲HTable的非線程安全特性致使的,但通過多時糾纏,最終也沒獲得可靠結論。後來肯定這是因爲HBase0.94版自身的問題,在使用0.96版後,此問題便再也不出現了。並且咱們發現0.94版HBase並不穩定,常常有掛掉狀況出現。0.96版HBase要好得多。

 

這裏補充很是重要的一點,在上面的代碼中,我經過 private ExecutorService pool = Executors.newFixedThreadPool(10);  建立了一個最多容納10個線程的線程池,從而建立了10個 Active RPC Calls,有效提升了獲取速度。然而,我將此線程池容量擴大至20個後,的確建立了20個 Active RPC Calls,以下圖所示,可是會直接引發事故:HBase掛掉。不得不吐cao,HBase實在不穩定,維護極其花費成本。在種種實踐驗證後,才獲得了這個穩定高效的方式,每1000個Get一次批量請求,至多10個線程同時取。 

 

 

平均效率以下圖所示:

 

 

 

 

更多的HBase及其它的數據存儲方案測試狀況,HBase高性能插入數據解決方案,正在整理中,敬請批評指正。

 

 

 



相關文章
相關標籤/搜索