基於Solr的HBase多條件查詢測試

來源:互聯網
上載者:User

標籤:status   伺服器組   檔案讀取   charset   block   cti   poi   tde   位置   

轉自:http://www.cnblogs.com/chenz/articles/3229997.html

背景:

某電信項目中採用HBase來儲存使用者終端詳細資料,供前台頁面即時查詢。HBase無可置疑擁有其優勢,但其本身只對rowkey支援毫秒級的快速檢索,對於多欄位的組合查詢卻無能為力。針對HBase的多條件查詢也有多種方案,但是這些方案要麼太複雜,要麼效率太低,本文只對基於Solr的HBase多條件查詢方案進行測試和驗證。

原理:

基於Solr的HBase多條件查詢原理很簡單,將HBase表中涉及條件過濾的欄位和rowkey在Solr中建立索引,通過Solr的多條件查詢快速獲得符合過濾條件的rowkey值,拿到這些rowkey之後在HBASE中通過指定rowkey進行查詢。

測試環境:

solr 4.0.0版本,使用其內建的jetty服務端容器,單節點;

hbase-0.94.2-cdh4.2.1,10台Lunux伺服器組成的HBase叢集。

HBase中2512萬條資料172個欄位;

Solr索引HBase中的100萬條資料;

測試結果:

1、100萬條資料在Solr中對8個欄位建立索引。在Solr中最多8個過濾條件擷取51316條資料的rowkey值,基本在57-80毫秒。根據Solr返回的rowkey值在HBase表中擷取所有51316條資料12個欄位值,耗時基本在15秒;

2、資料量同上,過濾條件同上,採用Solr分頁查詢,每次擷取20條資料,Solr獲得20個rowkey值耗時4-10毫秒,拿到Solr傳入的rowkey值在HBase中擷取對應20條12個欄位的資料,耗時6毫秒。

以下列出測試環境的搭建、以及相關代碼實現過程。一、Solr環境的搭建

因為初衷只是測試Solr的使用,Solr的運行環境也只是用了其內建的jetty,而非大多人用的Tomcat;沒有搭建Solr叢集,只是一個單一的Solr服務端,也沒有任何參數調優。

1)在Apache網站上下載Solr 4:http://lucene.apache.org/solr/downloads.html,我們這裡下載的是“apache-solr-4.0.0.tgz”;

2)在目前的目錄解壓Solr壓縮包:

cd /opt
tar -xvzf apache-solr-4.0.0.tgz

3)修改Solr的設定檔schema.xml,添加我們需要索引的多個欄位(設定檔位於“/opt/apache-solr-4.0.0/example/solr/collection1/conf/”)

   <field name="rowkey" type="string" indexed="true" stored="true" required="true" multiValued="false" />    <field name="time" type="string" indexed="true" stored="true" required="false" multiValued="false" />   <field name="tebid" type="string" indexed="true" stored="true" required="false" multiValued="false" />   <field name="tetid" type="string" indexed="true" stored="true" required="false" multiValued="false" />   <field name="puid" type="string" indexed="true" stored="true" required="false" multiValued="false" />   <field name="mgcvid" type="string" indexed="true" stored="true" required="false" multiValued="false" />   <field name="mtcvid" type="string" indexed="true" stored="true" required="false" multiValued="false" />   <field name="smaid" type="string" indexed="true" stored="true" required="false" multiValued="false" />   <field name="mtlkid" type="string" indexed="true" stored="true" required="false" multiValued="false" />

另外關鍵的一點是修改原有的uniqueKey,本文設定HBase表的rowkey欄位為Solr索引的uniqueKey:

<uniqueKey>rowkey</uniqueKey>

type 參數代表索引資料類型,我這裡將type全部設定為string是為了避免異常類型的資料導致索引建立失敗,正常情況下應該根據實際欄位類型設定,比如整型欄位設定為int,更加有利於索引的建立和檢索;

indexed 參數代表此欄位是否建立索引,根據實際情況設定,建議不參與條件過濾的欄位一律設定為false;

stored 參數代表是否儲存此欄位的值,建議根據實際需求只將需要擷取值的欄位設定為true,以免浪費儲存,比如我們的情境只需要擷取rowkey,那麼只需把rowkey欄位設定為true即可,其他欄位全部設定flase;

required 參數代表此欄位是否必需,如果資料來源某個欄位可能存在空值,那麼此屬性必需設定為false,不然Solr會拋出異常;

multiValued 參數代表此欄位是否允許有多個值,通常都設定為false,根據實際需求可設定為true。

4)我們使用Solr內建的example來作為運行環境,定位到example目錄,啟動服務監聽:

cd /opt/apache-solr-4.0.0/examplejava -jar ./start.jar

如果啟動成功,可以通過瀏覽器開啟此頁面:http://192.168.1.10:8983/solr/

二、讀取HBase源表的資料,在Solr中建立索引

一種方案是通過HBase的普通API擷取資料建立索引,此方案的缺點是效率較低每秒只能處理100多條資料(或許可以通過多線程提高效率):

package com.ultrapower.hbase.solrhbase;import java.io.IOException;import org.apache.hadoop.conf.Configuration;import org.apache.hadoop.hbase.HBaseConfiguration;import org.apache.hadoop.hbase.KeyValue;import org.apache.hadoop.hbase.client.HTable;import org.apache.hadoop.hbase.client.Result;import org.apache.hadoop.hbase.client.ResultScanner;import org.apache.hadoop.hbase.client.Scan;import org.apache.hadoop.hbase.util.Bytes;import org.apache.solr.client.solrj.SolrServerException;import org.apache.solr.client.solrj.impl.HttpSolrServer;import org.apache.solr.common.SolrInputDocument;public class SolrIndexer {    /**     * @param args     * @throws IOException     * @throws SolrServerException     */    public static void main(String[] args) throws IOException,            SolrServerException {        final Configuration conf;        HttpSolrServer solrServer = new HttpSolrServer(                "http://192.168.1.10:8983/solr"); // 因為服務端是用的Solr內建的jetty容器,預設連接埠號碼是8983        conf = HBaseConfiguration.create();        HTable table = new HTable(conf, "hb_app_xxxxxx"); // 這裡指定HBase表名稱        Scan scan = new Scan();        scan.addFamily(Bytes.toBytes("d")); // 這裡指定HBase表的列族        scan.setCaching(500);        scan.setCacheBlocks(false);        ResultScanner ss = table.getScanner(scan);        System.out.println("start ...");        int i = 0;        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("time")                            || fieldName.equalsIgnoreCase("tebid")                            || fieldName.equalsIgnoreCase("tetid")                            || fieldName.equalsIgnoreCase("puid")                            || fieldName.equalsIgnoreCase("mgcvid")                            || fieldName.equalsIgnoreCase("mtcvid")                            || fieldName.equalsIgnoreCase("smaid")                            || fieldName.equalsIgnoreCase("mtlkid")) {                        solrDoc.addField(fieldName, fieldValue);                    }                }                solrServer.add(solrDoc);                solrServer.commit(true, true, true);                i = i + 1;                System.out.println("已經成功處理 " + i + " 條資料");            }            ss.close();            table.close();            System.out.println("done !");        } catch (IOException e) {        } finally {            ss.close();            table.close();            System.out.println("erro !");        }    }}

另外一種方案是用到HBase的Mapreduce架構,分布式並存執行效率特別高,處理1000萬條資料僅需5分鐘,但是這種高並發需要對Solr伺服器進行配置調優,不然會拋出伺服器無法響應的異常:

Error: org.apache.solr.common.SolrException: Server at http://192.168.1.10:8983/solr returned non ok status:503, message:Service Unavailable

MapReduce入口程式:

package com.ultrapower.hbase.solrhbase;import java.io.IOException;import java.net.URISyntaxException;import org.apache.hadoop.conf.Configuration;import org.apache.hadoop.hbase.HBaseConfiguration;import org.apache.hadoop.hbase.client.Scan;import org.apache.hadoop.hbase.mapreduce.TableMapReduceUtil;import org.apache.hadoop.hbase.util.Bytes;import org.apache.hadoop.mapreduce.Job;import org.apache.hadoop.mapreduce.lib.output.NullOutputFormat;public class SolrHBaseIndexer {    private static void usage() {        System.err.println("輸入參數: <設定檔路徑> <起始行> <結束行>");        System.exit(1);    }    private static Configuration conf;    public static void main(String[] args) throws IOException,            InterruptedException, ClassNotFoundException, URISyntaxException {        if (args.length == 0 || args.length > 3) {            usage();        }        createHBaseConfiguration(args[0]);        ConfigProperties tutorialProperties = new ConfigProperties(args[0]);        String tbName = tutorialProperties.getHBTbName();        String tbFamily = tutorialProperties.getHBFamily();        Job job = new Job(conf, "SolrHBaseIndexer");        job.setJarByClass(SolrHBaseIndexer.class);        Scan scan = new Scan();        if (args.length == 3) {            scan.setStartRow(Bytes.toBytes(args[1]));            scan.setStopRow(Bytes.toBytes(args[2]));        }        scan.addFamily(Bytes.toBytes(tbFamily));        scan.setCaching(500); // 設定快取資料量來提高效率        scan.setCacheBlocks(false);        // 建立Map任務        TableMapReduceUtil.initTableMapperJob(tbName, scan,                SolrHBaseIndexerMapper.class, null, null, job);        // 不需要輸出        job.setOutputFormatClass(NullOutputFormat.class);        // job.setNumReduceTasks(0);        System.exit(job.waitForCompletion(true) ? 0 : 1);    }    /**     * 從設定檔讀取並設定HBase配置資訊     *      * @param propsLocation     * @return     */    private static void createHBaseConfiguration(String propsLocation) {        ConfigProperties tutorialProperties = new ConfigProperties(                propsLocation);        conf = HBaseConfiguration.create();        conf.set("hbase.zookeeper.quorum", tutorialProperties.getZKQuorum());        conf.set("hbase.zookeeper.property.clientPort",                tutorialProperties.getZKPort());        conf.set("hbase.master", tutorialProperties.getHBMaster());        conf.set("hbase.rootdir", tutorialProperties.getHBrootDir());        conf.set("solr.server", tutorialProperties.getSolrServer());    }}

對應的Mapper:

package com.ultrapower.hbase.solrhbase;import java.io.IOException;import org.apache.hadoop.conf.Configuration;import org.apache.hadoop.hbase.KeyValue;import org.apache.hadoop.hbase.client.Result;import org.apache.hadoop.hbase.io.ImmutableBytesWritable;import org.apache.hadoop.hbase.mapreduce.TableMapper;import org.apache.hadoop.io.Text;import org.apache.solr.client.solrj.SolrServerException;import org.apache.solr.client.solrj.impl.HttpSolrServer;import org.apache.solr.common.SolrInputDocument;public class SolrHBaseIndexerMapper extends TableMapper<Text, Text> {    public void map(ImmutableBytesWritable key, Result hbaseResult,            Context context) throws InterruptedException, IOException {        Configuration conf = context.getConfiguration();        HttpSolrServer solrServer = new HttpSolrServer(conf.get("solr.server"));        solrServer.setDefaultMaxConnectionsPerHost(100);        solrServer.setMaxTotalConnections(1000);        solrServer.setSoTimeout(20000);        solrServer.setConnectionTimeout(20000);        SolrInputDocument solrDoc = new SolrInputDocument();        try {            solrDoc.addField("rowkey", new String(hbaseResult.getRow()));            for (KeyValue rowQualifierAndValue : hbaseResult.list()) {                String fieldName = new String(                        rowQualifierAndValue.getQualifier());                String fieldValue = new String(rowQualifierAndValue.getValue());                if (fieldName.equalsIgnoreCase("time")                        || fieldName.equalsIgnoreCase("tebid")                        || fieldName.equalsIgnoreCase("tetid")                        || fieldName.equalsIgnoreCase("puid")                        || fieldName.equalsIgnoreCase("mgcvid")                        || fieldName.equalsIgnoreCase("mtcvid")                        || fieldName.equalsIgnoreCase("smaid")                        || fieldName.equalsIgnoreCase("mtlkid")) {                    solrDoc.addField(fieldName, fieldValue);                }            }            solrServer.add(solrDoc);            solrServer.commit(true, true, true);        } catch (SolrServerException e) {            System.err.println("更新Solr索引異常:" + new String(hbaseResult.getRow()));        }    }}

讀取參數設定檔的輔助類:

package com.ultrapower.hbase.solrhbase;import java.io.File;import java.io.FileReader;import java.io.IOException;import java.util.Properties;public class ConfigProperties {    private static Properties props;    private String HBASE_ZOOKEEPER_QUORUM;    private String HBASE_ZOOKEEPER_PROPERTY_CLIENT_PORT;    private String HBASE_MASTER;    private String HBASE_ROOTDIR;    private String DFS_NAME_DIR;    private String DFS_DATA_DIR;    private String FS_DEFAULT_NAME;    private String SOLR_SERVER; // Solr伺服器位址    private String HBASE_TABLE_NAME; // 需要建立Solr索引的HBase表名稱    private String HBASE_TABLE_FAMILY; // HBase表的列族    public ConfigProperties(String propLocation) {        props = new Properties();        try {            File file = new File(propLocation);            System.out.println("從以下位置載入設定檔: " + file.getAbsolutePath());            FileReader is = new FileReader(file);            props.load(is);            HBASE_ZOOKEEPER_QUORUM = props.getProperty("HBASE_ZOOKEEPER_QUORUM");            HBASE_ZOOKEEPER_PROPERTY_CLIENT_PORT = props.getProperty("HBASE_ZOOKEEPER_PROPERTY_CLIENT_PORT");            HBASE_MASTER = props.getProperty("HBASE_MASTER");            HBASE_ROOTDIR = props.getProperty("HBASE_ROOTDIR");            DFS_NAME_DIR = props.getProperty("DFS_NAME_DIR");            DFS_DATA_DIR = props.getProperty("DFS_DATA_DIR");            FS_DEFAULT_NAME = props.getProperty("FS_DEFAULT_NAME");            SOLR_SERVER = props.getProperty("SOLR_SERVER");            HBASE_TABLE_NAME = props.getProperty("HBASE_TABLE_NAME");            HBASE_TABLE_FAMILY = props.getProperty("HBASE_TABLE_FAMILY");        } catch (IOException e) {            throw new RuntimeException("載入設定檔出錯");        } catch (NullPointerException e) {            throw new RuntimeException("檔案不存在");        }    }    public String getZKQuorum() {        return HBASE_ZOOKEEPER_QUORUM;    }    public String getZKPort() {        return HBASE_ZOOKEEPER_PROPERTY_CLIENT_PORT;    }    public String getHBMaster() {        return HBASE_MASTER;    }    public String getHBrootDir() {        return HBASE_ROOTDIR;    }    public String getDFSnameDir() {        return DFS_NAME_DIR;    }    public String getDFSdataDir() {        return DFS_DATA_DIR;    }    public String getFSdefaultName() {        return FS_DEFAULT_NAME;    }    public String getSolrServer() {        return SOLR_SERVER;    }    public String getHBTbName() {        return HBASE_TABLE_NAME;    }    public String getHBFamily() {        return HBASE_TABLE_FAMILY;    }}

參數設定檔“config.properties”:

HBASE_ZOOKEEPER_QUORUM=slave-1,slave-2,slave-3,slave-4,slave-5HBASE_ZOOKEEPER_PROPERTY_CLIENT_PORT=2181HBASE_MASTER=master-1:60000HBASE_ROOTDIR=hdfs:///hbaseDFS_NAME_DIR=/opt/data/dfs/nameDFS_DATA_DIR=/opt/data/d0/dfs2/dataFS_DEFAULT_NAME=hdfs://192.168.1.10:9000SOLR_SERVER=http://192.168.1.10:8983/solrHBASE_TABLE_NAME=hb_app_m_user_teHBASE_TABLE_FAMILY=d
三、結合Solr進行HBase資料的多條件查詢:

可以通過web頁面操作Solr索引,

查詢:

http://192.168.1.10:8983/solr/select?(time:201307 AND tetid:1 AND mgcvid:101 AND smaid:101 AND puid:102)

刪除所有索引:

http://192.168.1.10:8983/solr/update/?stream.body=<delete><query>*:*</query></delete>&stream.contentType=text/xml;charset=utf-8&commit=true

通過java用戶端結合Solr查詢HBase資料:

package com.ultrapower.hbase.solrhbase;import java.io.IOException;import java.nio.ByteBuffer;import java.util.ArrayList;import java.util.List;import org.apache.hadoop.conf.Configuration;import org.apache.hadoop.hbase.HBaseConfiguration;import org.apache.hadoop.hbase.client.Get;import org.apache.hadoop.hbase.client.HTable;import org.apache.hadoop.hbase.client.Result;import org.apache.hadoop.hbase.util.Bytes;import org.apache.solr.client.solrj.SolrQuery;import org.apache.solr.client.solrj.SolrServer;import org.apache.solr.client.solrj.SolrServerException;import org.apache.solr.client.solrj.impl.HttpSolrServer;import org.apache.solr.client.solrj.response.QueryResponse;import org.apache.solr.common.SolrDocument;import org.apache.solr.common.SolrDocumentList;public class QueryData {    /**     * @param args     * @throws SolrServerException      * @throws IOException      */    public static void main(String[] args) throws SolrServerException, IOException {        final Configuration conf;        conf = HBaseConfiguration.create();        HTable table = new HTable(conf, "hb_app_m_user_te");        Get get = null;        List<Get> list = new ArrayList<Get>();                String url = "http://192.168.1.10:8983/solr";        SolrServer server = new HttpSolrServer(url);        SolrQuery query = new SolrQuery("time:201307 AND tetid:1 AND mgcvid:101 AND smaid:101 AND puid:102");        query.setStart(0); //資料起始行,分頁用        query.setRows(10); //返回記錄數,分頁用        QueryResponse response = server.query(query);        SolrDocumentList docs = response.getResults();        System.out.println("文檔個數:" + docs.getNumFound()); //資料總條數也可輕易擷取        System.out.println("查詢時間:" + response.getQTime());         for (SolrDocument doc : docs) {            get = new Get(Bytes.toBytes((String) doc.getFieldValue("rowkey")));            list.add(get);        }                Result[] res = table.get(list);                byte[] bt1 = null;        byte[] bt2 = null;        byte[] bt3 = null;        byte[] bt4 = null;        String str1 = null;        String str2 = null;        String str3 = null;        String str4 = null;        for (Result rs : res) {            bt1 = rs.getValue("d".getBytes(), "3mpon".getBytes());            bt2 = rs.getValue("d".getBytes(), "3mponid".getBytes());            bt3 = rs.getValue("d".getBytes(), "amarpu".getBytes());            bt4 = rs.getValue("d".getBytes(), "amarpuid".getBytes());            if (bt1 != null && bt1.length>0) {str1 = new String(bt1);} else {str1 = "無資料";} //對空值進行new String的話會拋出異常            if (bt2 != null && bt2.length>0) {str2 = new String(bt2);} else {str2 = "無資料";}            if (bt3 != null && bt3.length>0) {str3 = new String(bt3);} else {str3 = "無資料";}            if (bt4 != null && bt4.length>0) {str4 = new String(bt4);} else {str4 = "無資料";}            System.out.print(new String(rs.getRow()) + " ");            System.out.print(str1 + "|");            System.out.print(str2 + "|");            System.out.print(str3 + "|");            System.out.println(str4 + "|");        }        table.close();    }}
小結:

通過測試發現,結合Solr索引可以很好的實現HBase的多條件查詢,同時還能解決其兩個痛點:分頁查詢、資料總量統計。

實際情境中大多都是分頁查詢,分頁查詢返回的資料量很少,採用此種方案完全可以達到前端頁面毫秒級的即時響應;若有大批量的資料互動,比如涉及到資料匯出,實際上效率也是很高,十萬資料僅耗時10秒。

另外,如果真的將Solr納入使用,Solr以及HBase端都可以不斷進行最佳化,比如可以搭建Solr叢集,甚至可以採用SolrCloud基於hadoop的分布式索引服務。

總之,HBase不能多條件過濾查詢的先天性缺陷,在Solr的配合之下可以得到較好的彌補,難怪諸如新蛋科技、國美電商、蘇寧電商等互連網公司以及眾多遊戲公司,都使用Solr來支援快速查詢。

----end

本文串連:http://www.cnblogs.com/chenz/articles/3229997.html

chenzheng

聯絡:[email protected]

基於Solr的HBase多條件查詢測試

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.