91一级特黄大片|婷婷中文字幕在线|av成人无码国产|日韩无码一二三区|久久不射强奸视频|九九九久久久精品|国产免费浮力限制

[轉(zhuǎn)]基于Solr的淘寶商家交易數(shù)據(jù)實(shí)時(shí)查詢方法
來(lái)源: 羅源/
廣州農(nóng)商銀行
3943
3
0
2016-02-02

基于Solr的淘寶商家交易數(shù)據(jù)實(shí)時(shí)查詢方法


摘要前言 DT時(shí)代對(duì)平臺(tái)或商家來(lái)說(shuō)最有價(jià)值的就是數(shù)據(jù)了,在大數(shù)據(jù)時(shí)代數(shù)據(jù)呈現(xiàn)出數(shù)據(jù)量大,數(shù)據(jù)的維度多的特點(diǎn),用戶會(huì)使用多維度隨意組合條件快速召回?cái)?shù)據(jù)。數(shù)據(jù)處理業(yè)務(wù)場(chǎng)景需要實(shí)時(shí)性,需要能夠快速精準(zhǔn)的獲得到需要的數(shù)據(jù)。之前的通過(guò)數(shù)據(jù)庫(kù)的方式來(lái)處理數(shù)據(jù)的方式,由于數(shù)據(jù)庫(kù)的某些固有特性已經(jīng)很難滿足大數(shù)據(jù)時(shí)代對(duì)

前言

DT時(shí)代對(duì)平臺(tái)或商家來(lái)說(shuō)最有價(jià)值的就是數(shù)據(jù)了,在大數(shù)據(jù)時(shí)代數(shù)據(jù)呈現(xiàn)出數(shù)據(jù)量大,數(shù)據(jù)的維度多的特點(diǎn),用戶會(huì)使用多維度隨意組合條件快速召回?cái)?shù)據(jù)。數(shù)據(jù)處理業(yè)務(wù)場(chǎng)景需要實(shí)時(shí)性,需要能夠快速精準(zhǔn)的獲得到需要的數(shù)據(jù)。之前的通過(guò)數(shù)據(jù)庫(kù)的方式來(lái)處理數(shù)據(jù)的方式,由于數(shù)據(jù)庫(kù)的某些固有特性已經(jīng)很難滿足大數(shù)據(jù)時(shí)代對(duì)數(shù)據(jù)處理的需求。

`

所以,在大數(shù)據(jù)時(shí)代使用hadoop,hive,spark,作為處理離線大數(shù)據(jù)的補(bǔ)充手段已經(jīng)大行其道。
以上提到的這些數(shù)據(jù)處理手段,只能離線數(shù)據(jù)處理方式,無(wú)法實(shí)現(xiàn)實(shí)時(shí)性。Solr作為補(bǔ)充,能夠很好地解決大數(shù)據(jù)的多維度查詢和數(shù)據(jù)召回實(shí)時(shí)性要求。

`

本文通過(guò)分析阿里淘寶聚石塔環(huán)境中遇到的一個(gè)具體需求是如何實(shí)現(xiàn)的,通過(guò)這個(gè)例子,拋磚引玉來(lái)體現(xiàn)SORL在數(shù)據(jù)處理上的優(yōu)勢(shì)。

需求說(shuō)明

阿里聚石塔是銜接淘寶大賣家,軟件開(kāi)發(fā)者和平臺(tái)提供者這三者的生態(tài)圈,阿里通過(guò)聚石塔平臺(tái),將阿里云底層的PAAS,IAAS環(huán)境提供給第三方開(kāi)發(fā)者,而第三方開(kāi)發(fā)者可以通過(guò)自己開(kāi)發(fā)的軟件產(chǎn)品,比如ERP,CRM系統(tǒng)販賣給淘寶上的大賣家,提高大賣家的工作效率。

`

賣家的交易數(shù)據(jù)是最有價(jià)值的數(shù)據(jù),通過(guò)交易數(shù)據(jù)可以衍生出很多產(chǎn)品,例如管理交易的ERP軟件,會(huì)員營(yíng)銷工具CRM,在聚石塔環(huán)境中通過(guò)大賣家授權(quán),這部分?jǐn)?shù)據(jù)可以授權(quán)給獨(dú)立軟件開(kāi)發(fā)者ISV。

`

在CRM系統(tǒng)中需要能夠通過(guò)設(shè)置買家的行為屬性快速過(guò)濾出有價(jià)值的買家記錄,進(jìn)行精準(zhǔn)會(huì)員營(yíng)銷。
以下是兩個(gè)具體需求,首先看兩個(gè)線框圖:
image001

以上是賣家需要實(shí)時(shí)篩選一段時(shí)間內(nèi)購(gòu)買數(shù)量在一個(gè)區(qū)間之內(nèi)的買家。

再看一個(gè)線框圖:
image002
賣家需要實(shí)時(shí)搜索一個(gè)時(shí)間段內(nèi),消費(fèi)金額在某個(gè)區(qū)間之內(nèi)的買家會(huì)員。這里的區(qū)間是以天為單位的,時(shí)間跨度可長(zhǎng)可短。

`

了解了線框圖之后,我們還要再看看對(duì)應(yīng)的數(shù)據(jù)庫(kù)ER圖:
image003

`

表結(jié)構(gòu)相當(dāng)簡(jiǎn)單,只有兩張表,稍微有點(diǎn)經(jīng)驗(yàn)的開(kāi)發(fā)工程師就會(huì)寫出以下SQL:

select buyer.buyer_id,count(trade.trade_id) as pay_countFrom buyer  inner join trade on(buyer.buyer_id = trade.buyer_id and buyer.seller_id = trade.seller_id)where trade.trade_time> ? and trade.trade_time < ? and buyer.seller_id=?group by buyer.buyer_idhaving pay_count > =1 AND pay_count <=5

第二個(gè)線框圖會(huì)用以下SQL語(yǔ)句來(lái)實(shí)現(xiàn):

select  buyer.buyer_id , sum(trade.fee) as pay_sumFrom buyer  inner join trade on( buyer.buyer_id = trade.buyer_id and buyer.seller_id = trade.seller_id)where trade.trade_time> ? AND trade.trade_time < ? and buyer.seller_id=?group by buyer.buyer_idhaving pay_sum > =20 and pay_sum <=100

以上,兩個(gè)SQL語(yǔ)句大同小異,having部分稍有不同, SQL語(yǔ)句并不算復(fù)雜,但是在大數(shù)據(jù)情況下,無(wú)法在毫秒級(jí)反饋給用戶。另外,假如where部分有其他查詢條件,比如,買家的性別,買家所屬的地區(qū)等,就需要數(shù)據(jù)庫(kù)上設(shè)置更多的聯(lián)合索引,所以這個(gè)需求使用SQL語(yǔ)句根本無(wú)法實(shí)現(xiàn)的。

查詢加速

問(wèn)題已經(jīng)明確,那么解決的辦法是什么呢?是使用數(shù)據(jù)的存儲(chǔ)過(guò)程?存儲(chǔ)過(guò)程底層還是依賴數(shù)據(jù)庫(kù)表的固有特性,無(wú)非是提供一些以時(shí)間換空間的策略來(lái)實(shí)現(xiàn)罷了,換湯不換藥,而且各個(gè)數(shù)據(jù)庫(kù)產(chǎn)品的存儲(chǔ)過(guò)程實(shí)現(xiàn)很很大差別,一旦選擇了某一個(gè)數(shù)據(jù)的存儲(chǔ)過(guò)程之后以后再要遷移數(shù)據(jù)到其他數(shù)據(jù)平臺(tái)上就非常困難了。

`

這里要向大家隆重介紹搜索引擎Solr。因?yàn)椋阉饕嬖诘讓邮褂玫古潘饕?,這和數(shù)據(jù)庫(kù)有本質(zhì)區(qū)別,倒排索引在數(shù)據(jù)查詢的性能上天生就比數(shù)據(jù)的Btree樹(shù)好上百倍,具體原因不在這里展開(kāi)了。雖然某些數(shù)據(jù)庫(kù)也支持了倒排索引例如PG,但畢竟不是通用的解決辦法。一旦添加了這類型的索引會(huì)影響數(shù)據(jù)的寫入吞吐量,因?yàn)橹亟ㄋ饕浅:臅r(shí)間。

`

開(kāi)源JAVA社區(qū)中使用最廣泛的應(yīng)該屬Solr了,筆者所在的團(tuán)隊(duì)就是長(zhǎng)期研究將Solr應(yīng)用到企業(yè)級(jí)應(yīng)用場(chǎng)景中,在原生Solr之上做了很多優(yōu)化和適配,方便企業(yè)級(jí)用戶使用。

`

言歸正傳,先講講大致思路,實(shí)現(xiàn)的架構(gòu)圖如下:
image006

全量數(shù)據(jù)準(zhǔn)備

這里要說(shuō)明的一點(diǎn),發(fā)送到搜索引擎中的數(shù)據(jù)是一條寬表數(shù)據(jù),所謂寬表數(shù)據(jù)是將ER關(guān)系為1對(duì)N的實(shí)體,聚合成一條記錄。聚合方式有兩種,一種是向1的維度聚合,比如用戶實(shí)體和消費(fèi)記錄實(shí)體,寬表記錄如果是以用戶維度來(lái)聚合和話,就會(huì)將所有的消費(fèi)記錄以某個(gè)特殊字符作為分割符,聚合成一個(gè)字段,作為用戶記錄的一個(gè)冗余字段。也可以以消費(fèi)記錄為維度聚合,將關(guān)聯(lián)的用戶信息作為一個(gè)冗余字段,可想而知這樣的聚合方式用戶數(shù)據(jù)在索引數(shù)據(jù)中會(huì)有很多重復(fù)。

`

打?qū)挶磉@個(gè)環(huán)節(jié)看似和搜索不怎么相關(guān),但是合理的寬表數(shù)據(jù)結(jié)構(gòu)能大幅度地提高用戶數(shù)據(jù)查詢效率。

`

全量流程用Hive來(lái)實(shí)現(xiàn)的,如果是在阿里云公有云環(huán)境中可以用ODPS,因ODPS是PAAS服務(wù)。

`

增量通道,需要寫一個(gè)打?qū)挶聿僮?。因?yàn)樗阉饕嫣赜械慕Y(jié)構(gòu),增量同步更新持續(xù)一段時(shí)間之后會(huì)生成很多索引碎片,所以必須要隔一段時(shí)間從數(shù)據(jù)源重新導(dǎo)出并構(gòu)建一次全量索引數(shù)據(jù)。

`

這里介紹一下上面提交到用戶-消費(fèi)記錄的寬表結(jié)構(gòu)(簡(jiǎn)單起見(jiàn),去掉了表中和問(wèn)題域不相關(guān)的字段):

Buyer表:

買家id賣家id
Buyer_idSeller_id

Trade表:

買家id賣家id交易id交易時(shí)間單筆費(fèi)用
Buyer_idSeller_idtrade_idtrade_timeFee

聚合寬表結(jié)構(gòu):

買家id賣家iddynamic_info(聚合字段)
Buyer_idSeller_idsellerId_date_buyerId_payment_payCount[;sellerId_date_buyerId_payment_payCount]
`

這里需要對(duì)dynamic_info 聚合字段詳細(xì)說(shuō)明一下:

`

sellerId_date_buyerId_payment_payCount 這是一個(gè)聚合單元,從左向右依次的含義是:賣家ID,購(gòu)買的日期(精確到天),買家ID,購(gòu)買天之內(nèi)的費(fèi)用總和,購(gòu)買天之內(nèi)的購(gòu)買次數(shù)總和。

`

Dynamic_info字段可以有多個(gè)聚合單元組成,每個(gè)單元中的date是按天去重的,假如一個(gè)用戶在某一天在一家店中有多條購(gòu)買記錄最終也會(huì)聚合成一個(gè)單元。給一個(gè)聚合字段的實(shí)際示例:

`
Dynamic_info:9999_20151111_222_345.6_3;9999_20151212_222_627.5_1
`

這個(gè)字段的意義就是,一個(gè)id為222的用戶在2015年雙11當(dāng)天購(gòu)買了3筆價(jià)值345元的商品,在雙12當(dāng)天在這個(gè)商家處又購(gòu)買了一筆價(jià)值627.5元的商品。

`

之所以在Solr上進(jìn)行快速數(shù)據(jù)查詢的原因是,Solr的數(shù)據(jù)源是一個(gè)已經(jīng)聚合好的一份數(shù)據(jù),數(shù)據(jù)庫(kù)上執(zhí)行的join操作會(huì)耗費(fèi)大量IO,在Solr查詢省去了這部分時(shí)間。

`

寬表數(shù)據(jù)從多個(gè)分表聚合,數(shù)據(jù)的語(yǔ)義沒(méi)有變化,只是組織形式發(fā)生了變化,如果一個(gè)SAAS的服務(wù)提供上同時(shí)為十幾萬(wàn)個(gè)大賣家提供篩選服務(wù),而每個(gè)大賣家又積累的交易數(shù)據(jù)是非常大的,全部加在一起,要將數(shù)據(jù)進(jìn)行聚合化操作,有非常大的CPU和IO開(kāi)銷,好在在云服務(wù)時(shí)代有強(qiáng)大的離線計(jì)算工具如hadoop,ODPS可以將大數(shù)據(jù)如同肉面粉一般揉(處理)成任何你想要的結(jié)構(gòu),分分鐘不在話下。

Solr引擎端數(shù)據(jù)處理

準(zhǔn)備好全量源數(shù)據(jù),之后就是將其轉(zhuǎn)化為L(zhǎng)ucene的索引文件了,這個(gè)過(guò)程請(qǐng)查閱Solr Wiki便可,這里不進(jìn)行闡述。這里要重點(diǎn)描述的是Solr服務(wù)端如何響應(yīng)用戶的查詢請(qǐng)求,返回給用戶需要的查詢結(jié)果。

`

處理用戶在時(shí)間段內(nèi)購(gòu)買量或購(gòu)買額度進(jìn)行過(guò)濾,需要構(gòu)建一個(gè)QParser的插件,這個(gè)插件的作用是遍歷和查參數(shù)中匹配的條件項(xiàng)生成命中的DocSet命中結(jié)果集。

Qparser代碼實(shí)現(xiàn)

下面是QparserPlugin.java節(jié)選:

for (LeafReaderContext leaf : readerContext.leaves()) {                 docBase = leaf.docBase;                 reader = leaf.reader();                 liveDocs = reader.getLiveDocs();                 terms = reader.terms("dynamic_info");                 termEnum = terms.iterator();                String prefixStart = sellerId + "_" + startTime;                String prefixEnd = sellerId + "_" + endTime;                String termStr = null;                int docid = -1;                if ((termEnum.seekCeil(new BytesRef(prefixStart))) != SeekStatus.END) {                    do {                         Matcher matcher = DYNAMIC_INFO                                 .matcher(termStr = termEnum.term()                                         .utf8ToString());                        if (!matcher.matches()) {                            continue;                         }                         posting = termEnum.postings(posting);                         docid = posting.nextDoc();if (!(docid != PostingsEnum.NO_MORE_DOCS     && (liveDocs == null || (liveDocs != null && liveDocs.get(docid))))) {        continue; }                        if ((matcher.group(1) + "_" + matcher.group(2))                                 .compareTo(prefixEnd) > 0) {                            break;                         }                         addStatis(buyerStatis, docBase, docid, matcher);                     } while (termEnum.next() != null);                 }             }

以上代碼的執(zhí)行邏輯是,截取prefixStart和prefixEnd之間的term序列,進(jìn)行分析如果符合過(guò)濾條件就將對(duì)應(yīng)docid插入buyerStatis收集器中。

`

等第一輪數(shù)據(jù)處理過(guò)程中就在對(duì)聚合結(jié)果進(jìn)行增量累加,代碼如下:

private static StaticReduce addStatis(             Map<Integer, StaticReduce> buyerStatis, int docBase, int docid,             Matcher matcher) {         StaticReduce statis = buyerStatis.get(docBase + docid);        if (statis == null) {             statis = new StaticReduce(docBase + docid, Long.parseLong(matcher                     .group(3))/* buyerid */);             buyerStatis.put(docBase + docid, statis);         }        if (statis.buyerId != Long.parseLong(matcher.group(3))) {            return statis;         }        try {             statis.addPayCount(Integer.parseInt(matcher.group(5)));         } catch (Exception e) {         }        try {             statis.addPayment(Float.parseFloat(matcher.group(4)));         } catch (Exception e) {         }        return statis;     }

同時(shí)對(duì)購(gòu)買數(shù)量,和購(gòu)買金額進(jìn)行累加。

`

最后對(duì)累加結(jié)果進(jìn)行過(guò)濾,符合過(guò)濾條件的,將docid插入到bitset中:

for (StaticReduce statis : buyerStatis.values()) {                 // TODO 這里自己判斷是否要收集這條記錄        if (statis.payCount > Integer.MAX_VALUE                 || statis.paymentSum > 1) {            System.out.println("count:" + statis.payCount + ",sum:"                         + statis.paymentSum);                     bitSet.set(statis.luceneDocId);         } }BitDocIdSet docIdSet = new BitDocIdSet(bitSet);            DocIdSetIterator it = docIdSet.iterator();             final BitQuery bitquery = new BitQuery(it);            return new QParser(qstr, localParams, params, req) {                 @Override                 public Query parse() throws SyntaxError {                    return bitquery;                 }             };

最后將bitSet包裝成BitQuery作為Qparser的parse函數(shù)的返回值,返回有solr進(jìn)一步和其他結(jié)果集進(jìn)行過(guò)濾。

`

Solrconfig配置實(shí)現(xiàn)

需要將以上的QparserPlugin插件注入到solr中,需要在solrconfig中寫以下配置:

  <queryParser name="timesegstats" class ="com.xxx.qp.TimeSegStatsQParserPlugin" >                                         <str name="buyerField">buyer_id</str>                                                                                                                     <str name="compoundField">dynamic_info </str>                                                                                 <str name="countField">emailSendCount</str>                                                                                              <str name="statsFields"></str>                                                                                                                     </queryParser>

Solr查詢語(yǔ)句Q參數(shù)設(shè)置:

q={!multiqp q.op=AND}seller_id:1441097932588 AND {!timesegstats sellerId=1441097932588 statsField=buyActivity startTime=20150901 endTime=20150924 startValue=1 endValue=200} AND {!timesegstats sellerId=1441097932588 statsField=paycount startTime=20140901 endTime=20150924 startValue=2 endValue=100}

總結(jié)

以上是一個(gè)用Solr搜索引擎解決數(shù)據(jù)庫(kù)查詢瓶頸的實(shí)例,其實(shí)搜索引擎的使用場(chǎng)景非常廣泛,不僅可以用在像百度這樣的大規(guī)模非結(jié)構(gòu)化的數(shù)據(jù)查詢,可以定制比較復(fù)雜的排序規(guī)則。Solr更可以解決像本文講到的數(shù)據(jù)庫(kù)加速的場(chǎng)景,使得原本在數(shù)據(jù)庫(kù)上沒(méi)有無(wú)法實(shí)現(xiàn)的SQL查詢,可以通過(guò)Solr搜索引擎上輕松實(shí)現(xiàn)。

`

本文講到的需求,也可以使用像hive這樣的離線處理工具來(lái)實(shí)現(xiàn),每次處理完成后將結(jié)果再導(dǎo)入到mysql中,業(yè)務(wù)端通過(guò)讀取數(shù)據(jù)庫(kù)表中的數(shù)據(jù)來(lái)向用戶展示處理結(jié)果。這樣做雖然可行,但是,沒(méi)有辦法將處理結(jié)果的實(shí)時(shí)性沒(méi)有辦法保證,而且,離線處理結(jié)果的數(shù)據(jù)結(jié)構(gòu)是固化的,沒(méi)有辦法做到將處理結(jié)果靈活調(diào)整。而用Solr做到數(shù)據(jù)的查詢出口,可以很好地解決以上兩個(gè)問(wèn)題。



登錄用戶可以查看和發(fā)表評(píng)論, 請(qǐng)前往  登錄 或  注冊(cè)。
SCHOLAT.com 學(xué)者網(wǎng)
免責(zé)聲明 | 關(guān)于我們 | 聯(lián)系我們
聯(lián)系我們: