億級數據多條件組合查詢——秒級響應解決方案

1 概述
組合查詢爲多條件組合查詢,在很多場景下都有使用。購物網站中通過勾選類別、價格、銷售量範圍等屬性來對所有的商品進行篩選,篩選出滿足客戶需要的商品,這是一種典型的組合查詢。在小數據量的情況下,後臺通過簡單的sql語句便能夠快速過濾出需要的數據,但隨着數據量的增加,繼續使用sql語句,查詢效率會直線下降。當數據量達到一定的量級,服務器將會不堪重負甚至面臨掛掉的危險,並且大數據量的存儲也成爲了一個問題。本文將討論在億級數據的情況下,多條件組合查詢秒級響應的解決方案。

2 方案思考
2.1 數據存儲
假定每條數據有10個字段,每個字段的大小爲4Byte,共有1億條數據。通過傳統的關係型數據庫mysql,使用JDBC批處理和事務混合的方式對數據進行插入,插入一億數據大約需要半小時,字段可能會出現爲空的情況,導致冗餘。針對海量數據的存儲,現如今使用較多的是HBase。使用HBase的好處有三:其一,它是非關係型數據庫,字段爲空的值只在邏輯上存在,在空間上不存在,因此解決了冗餘的問題;其二,它是面向列的數據庫,能夠通過簡單的API調用對字段進行橫向擴展;其三,它是分部式數據庫,表的RowKey 按照字典排序,Region按照RowKey設置split point進行shard,通過這種方式實現全局、分佈式索引,通過RowKey索引數據能夠在毫秒級返回。Hbase插入數據可以調用批量插入或者通過MR程序插入,實測在批量提交數據條數設置爲1000,開10個線程的情況下,插入一億數據大約需要10分鐘。若需要加速插入速度,可以通過增加批量提交數、調整線程數或者使用MR程序進行Hbase的寫入。Hbase本身是分佈式數據庫,數據存儲可以存儲在多個節點上,使用Zookeeper統一管理,提供數據備份和故障恢復的功能。因此使用Hbase作爲數據倉庫,對結構化數據進行存儲。

2.2 數據查詢
Hbase中的數據查詢只有兩種方式:一是使用get 'tablename', 'rowkey‘’直接通過rowkey進行查詢,億級數據的查詢結果可以在毫秒內返回;二是設置過濾器對全表進行Scan掃描,該查詢方式在海量數據的情況下耗時十分長,當然也和服務器的性能有關。我們的需求是秒級響應,如果使用全表掃描方式,數據量達到萬級或者十萬級就無法實現實時響應了,要進行這樣的查詢,往往是要通過類似Hive、Pig等系統進行全表的MapReduce計算,這種方式既浪費了機器的計算資源,又因高延遲使得應用黯然失色。因此我們考慮使用rowKey對數據進行查詢,如果我們使用rowKey對全表進行多條件組合查詢,這將對rowKey的設置要求十分高,面向業務而言這對程序員十分不友好,因此我們需要通過建立二級索引的方式,按索引的種類掃描各自獨立的單索引表,最後將掃描結果merge,得到目標rowKey。HBase有原生的建立二級索引的方式,即使用HBase的coprocessor協處理器,可以根據業務進行靈活的設置,但較爲複雜,本文討論使用一種業務模式較爲固定,但更加簡單直接的方式創建索引——Solr。Solr是一個獨立的企業級搜索應用服務器,是Apache Lucene項目的開源企業搜索平臺。其主要功能包括全文檢索、命中標示、分面搜索、動態聚類、數據庫集成,以及富文本(如Word、PDF)的處理。Solr是高度可擴展的,並提供了分佈式搜索和索引複製。我們可以直接使用Solr這一組件,通過修改配置文件以實現相關的業務需求。通過批量建立索引的方式對HBase中的一億條數據的10個字段構建索引,耗時爲3383s,約爲1小時。具體代碼如下:

public class ThreadsCreateIndexWork {
    private static Logger logger = LoggerFactory.getLogger(ThreadsCreateIndexWork.class);
    public static void main(String[] args) throws IOException, SolrServerException {
        if(args.length < 3) {
            logger.info("[tableName  |  queueSize  |  threadCount]");
            logger.info("e.g.| test1 20000 20");
        }
        String tableName = args[0];
        String queueSize = args[1];
        String threadCount = args[2];

        long start = System.currentTimeMillis();

        final Configuration conf;
        Properties prop = PropertiesReaderUtils.getProperties("conf/path.properties");
        String server = prop.getProperty("solr.server");
        SolrServer solrServer = new ConcurrentUpdateSolrServer(server, Integer.parseInt(queueSize), Integer.parseInt(threadCount));

        conf = HBaseConfiguration.create();
        HTable table = new HTable(conf, tableName); // 這裏指定HBase表名稱
        Scan scan = new Scan();
        scan.addFamily(Bytes.toBytes("people")); // 這裏指定HBase表的列族
        scan.setCaching(500);
        scan.setCacheBlocks(false);
        ResultScanner ss = table.getScanner(scan);

        try {
            for (Result r : ss) {
                SolrInputDocument solrDoc = new SolrInputDocument();
                solrDoc.addField("rowkey", new String(r.getRow()));
                for (KeyValue kv : r.raw()) {
                    String fieldName = new String(kv.getQualifier());
                    String fieldValue = new String(kv.getValue());
                    if (fieldName.equalsIgnoreCase("upperClothing")
                            || fieldName.equalsIgnoreCase("lowerClothing")
                            || fieldName.equalsIgnoreCase("coatStyle")
                            || fieldName.equalsIgnoreCase("trousersStyle")
                            || fieldName.equalsIgnoreCase("sex")
                            || fieldName.equalsIgnoreCase("age")
                            || fieldName.equalsIgnoreCase("angle")
                            || fieldName.equalsIgnoreCase("bag")
                            || fieldName.equalsIgnoreCase("umbrella")
                            || fieldName.equalsIgnoreCase("featureType")){
                        solrDoc.addField(fieldName, fieldValue);
                    }
                }
                solrServer.add(solrDoc);
            }
            ss.close();
            table.close();
        } catch (IOException e) {
        } finally {
            ss.close();
            table.close();
        }

        long time = System.currentTimeMillis() - start;
        logger.info("---------- create index with thread use time " + String.valueOf(time));
    }
}
 

3 解決方案

綜上,針對億級數據多條件組合查詢,給出的解決方案是使用HBase+Solr的方式,CDH將HBase和Solr都以組件的方式提供出來,可以使用CDH平臺對HBase和Solr進行統一的管理。Hbase用於存儲海量數據,Solr使用SolrCloud模式進行部署,提供索引構建和查詢。索引的創建可以通過接口離線批量創建,也可以使用HBase Indexer連接HBase和Solr,提供自動化索引構建,CDH平臺也集成了Hbase Indexer(Lily HBase Indexer)這一組件。
 

 

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