對於曆史資料的查詢,在資料規模不大的情況下,可以用傳統的關係型資料庫,如oracle,mysql等,可以利用他們提供的索引功能,實現高效的查詢。
但是當資料上升到一定規模後,用傳統的關係型資料庫就不太合適了,當然可以把資料存到分散式資料庫HBase中。
HBase目前只支援對rowkey的一級索引,對於二級索引還不支援,當然可以把所有要索引的欄位都拼接到rowkey中,根據hbase的filter功能進行查詢,
如按照尋找一個使用者的一段時間的訂單,rowkey可以這樣設計,rowkey="100022333||2012-12-23:10:20||orderNum",由於hbase在儲存時,預設按照rowkey進行排序,這樣一個使用者的曆史資料會集中在一個region中,這樣便於順序的尋找。用這種方式的一個弊端是對分頁的功能支援的不好,分頁所用的總count數量和rowNum,可以在corprocessor中實現記錄數量的匯總,但是對於從哪條條記錄開始rowNum,則不太好支援,並且總記錄數量的匯總需要單獨用coprocessor的endpoint來實現,這樣就增加了計算量;如果放在用戶端做分頁,對海量資料來說,是不可行的。
當然可以在Hbase中對該表產生對應的索引表,有幾個二級索引就有幾張表,如主表的rowkey設計為rowkey="orderNum",那麼索引表就是rowkey="usetNum||orderdate",cf="orderNum",同樣也會有分頁的問題。
下面提出一種sorl+hbase的方式,solr建立索引(solr支援分頁的操作),hbase儲存資料。
在往HBase寫資料的過程中,建立solr索引,可以在HBase的coprocessor的observer功能中實現
public class SorlIndexCoprocessorObserver extends BaseRegionObserver { /** * 建立solr索引 */ public void postPut(final ObserverContext<RegionCoprocessorEnvironment> e, final Put put, final WALEdit edit, final boolean writeToWAL) throws IOException { byte[] rowKey = put.getRow(); String rowKeyStr = new String(rowKey, "UTF-8"); List<KeyValue> kv = put .get("cf".getBytes(), Bytes.toBytes("orderTime")); String orderTime = new String(kv.get(0).getValue(), "UTF-8"); List<KeyValue> kv2 = put.get("cf".getBytes(), Bytes.toBytes("userNum")); String userNum = new String(kv2.get(0).getValue(), "UTF-8"); String solrUrl = "http://10.1.1.57:8082/solr"; SolrServer solr = null; try { solr = new CommonsHttpSolrServer(solrUrl); } catch (MalformedURLException err) { // TODO Auto-generated catch block err.printStackTrace(); } SolrInputDocument siDoc = new SolrInputDocument(); siDoc.addField("id", rowKeyStr); siDoc.addField("rowkey", rowKeyStr); siDoc.addField("orderTime", orderTime); siDoc.addField("userNum", userNum); try { solr.add(siDoc); } catch (SolrServerException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } catch (IOException e2) { // TODO Auto-generated catch block e2.printStackTrace(); } try { solr.commit(); } catch (SolrServerException e3) { e3.printStackTrace(); } catch (IOException e1) { e1.printStackTrace(); } } |
配置coprocessor
alter ‘t1′, METHOD => ‘table_att', ‘coprocessor'=> ‘hdfs:///xxx.jar|com.newcosoft.hadoop.hbase.SorlIndexCoprocessorObserver|1001
一個表上可以配置多個協同處理器,一個序列會自動成長進行標識。載入協同處理器(可以說是過濾程式)需要符合以下規則:
[coprocessor jar file location] | class name | [priority] | [arguments]
對於solr中的commit操作,commit提交後,索引flush到硬碟上,並觸發listener,創造新的insexSearcher(新的insexReader,從硬碟中載入索引),這樣後續的查詢就用新的insexsearcher了對查詢效能影響比較大。在大量匯入的情況下,可以匯入完成後,單獨調用sorl的commit操作。
此處採用solr的master,slave方式,master提供索引構建,多個slave提供索引查詢;對於master的commit操作,會產生一個新的snapshot,
slave上的Snappuller程式一般是在crontab上面執行的,它會去master詢問,有沒有新版的snapshot;一旦發現新的版本,slave就會把它下載下來,然後snapinstall。
當一個新的searcher被open的時候,會有一個緩衝預熱的過程,預熱之後,新的索引才會交付使用;這裡會控制Snappuller程式的執行頻率,solr的最佳化這裡不做深入。
從solr中根據索引欄位以及startrow、pagesize尋找出對應的曆史訂單orderNum list後,遍曆該list,去hbase中進行查詢
String solrUrlSlave = "http://10.1.1.59:8082/solr"; SolrServer solr2 = null; try { solr2 = new CommonsHttpSolrServer(solrUrlSlave); } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } String queryString = "q=userNum:1111002&orderDate=2012-10-12&startrow=1&pagesize=10&sort=orderDate+desc"; SolrParams solrParams = SolrRequestParsers .parseQueryString(queryString); try { QueryResponse rsp = solr2.query(solrParams); SolrDocumentList docList=rsp.getResults(); //遍曆該docList,到HBASE中尋找 //TODO } catch (SolrServerException e) { e.printStackTrace(); } |
當然關於solr的索引,需要考慮到sorl的HA,有很多策略,這裡不做介紹了,後面出單獨的章節做闡述