一. 系統(tǒng)設(shè)計目的
<small>根據(jù)實時binlog數(shù)據(jù)和日志流數(shù)據(jù)實時更新學(xué)習(xí)模型,做到在線預(yù)測</small>
二. 業(yè)務(wù)場景介紹
<small> 目前線上每天有200w左右的訂單,而且某些用戶可能一天有多次下單,因此用戶的實時下單數(shù)據(jù)對用戶做個性化推薦有著重要的意義。而且目前都是T+1的方式進行預(yù)測,對于某些高頻用戶推薦的時效性并不高,需要設(shè)計一套實時推薦引擎。</small>
三. 系統(tǒng)設(shè)計目標(biāo)
- <small>實時性:時延能在秒級別</small>
- <small>擴展性強:通用性強,不受限于接入數(shù)據(jù)格式和算法種類</small>
- <small>可靠性高:服務(wù)穩(wěn)定可靠,即使在流量突增也能應(yīng)對</small>
- <small>框架簡單化,算法模型復(fù)雜化:算法模型可任意配置</small>
- <small>特征動態(tài)擴展:可根據(jù)數(shù)據(jù)源隨意添加新特征并實時反饋到在線預(yù)測中</small>
- <small>支持模型ABTest:可對兩個不同的模型做在線ABTest,評估效果</small>
- <small>監(jiān)控:時延,在線預(yù)測時間,實時數(shù)據(jù)流量,模型效果評估</small>
- <small>可維護性:排查問題方便,代碼修改簡單</small>
- <small>穩(wěn)定性:即使系統(tǒng)的某一個模塊掛掉也不造成服務(wù)的停止</small>
四. 系統(tǒng)設(shè)計方案
<small>系統(tǒng)架構(gòu)圖如下:</small>

<small>分為四個模塊:離線ETL模塊,數(shù)據(jù)聚合模塊,學(xué)習(xí)模塊,實時預(yù)測模塊,配置模塊</small>
離線ETL模塊:
<small>負(fù)責(zé)實時清洗數(shù)據(jù)流并存入NoSQL中,NoSQL中的數(shù)據(jù)是按照主題表去存入的,比喻訂單維度表,用戶維度表等,每個表按照階段寫入,每個階段是一個數(shù)據(jù)塊,以供聚合模塊增量計算(具體請看第五節(jié))??紤]到數(shù)據(jù)流的多樣性,可能并不能包含維度表的各個特征字段,所以只需要提取主鍵和相關(guān)的特征字段。ETL過程只是負(fù)責(zé)處理各個不同的數(shù)據(jù)流并持久化到NoSQL中。</small>數(shù)據(jù)聚合模塊:
<small>定期讀取ES模塊的數(shù)據(jù)根據(jù)配置模塊配置的特征進行提取和計算。該模塊能夠增量和全量的計算特征并存儲到cache中。需要定義新特征時讀取存儲模塊數(shù)據(jù)全量計算存儲到cache;在實時處理過程中只需要增量計算即可。</small>學(xué)習(xí)模塊:
<small>目前有兩種方案,如下:
1.定期讀取配置模塊獲取最新特征,接著讀取cache中數(shù)據(jù)做學(xué)習(xí)訓(xùn)練(需要從NoSQL中讀取uid,根據(jù)uid獲取訓(xùn)練集);
2.實時訓(xùn)練,并定期讀取配置模塊,定期保存訓(xùn)練模型,如果配置發(fā)生變化,從頭開始訓(xùn)練模型,重新學(xué)習(xí),但是需要獲取所有用戶數(shù)據(jù)(需要從NoSQL中讀取uid,根據(jù)uid獲取訓(xùn)練集);
評價:方案1模型訓(xùn)練實時性不高,設(shè)計簡單,便于維護;方案2實時性好,但需要實時獲取變更數(shù)據(jù)(考慮如何保存uid在從cache中取),要求算法支持增量訓(xùn)練;
</small>實時預(yù)測模塊:
<small>定期更新緩存的訓(xùn)練模型,再根據(jù)uid讀取用戶特征數(shù)據(jù),在線預(yù)測(可以考慮通過消息驅(qū)動,而不是定期讀取)</small>-
全局配置模塊:
<small>負(fù)責(zé)配置各個算法的特征,預(yù)測類別,特征順序,為聚合模塊、學(xué)習(xí)模塊、在線預(yù)測模塊提供配置服務(wù)</small>
<small>各個模塊的交互如下所示:</small>
實時推薦引擎設(shè)計.png
五. 各模塊具體考慮問題和解決辦法
離線ETL模塊:
<small>問題:</small>
<small>a:針對不同數(shù)據(jù)流如何做解析?非標(biāo)準(zhǔn)化數(shù)據(jù)如日志流如何做解析?</small>
<small>b:解析后的數(shù)據(jù)格式是怎樣的?統(tǒng)一的數(shù)據(jù)格式如何定義?</small>
<small>c:對于新數(shù)據(jù)流是否可以簡單快速解析?</small>
<small>d:對于大量數(shù)據(jù)如何做到實時解析?是否考慮jstorm?</small>
<small>解決辦法:</small>
<small>a:binlog數(shù)據(jù)結(jié)構(gòu)化,直接通過消費kafka的方式讀取指定數(shù)據(jù)表的數(shù)據(jù);日志數(shù)據(jù)可以指定topic,對每一個topic數(shù)據(jù)通過定義關(guān)鍵階段和正則匹配的方式,選取指定數(shù)據(jù)做解析;考慮使用處理器鏈的方式來處理每個topic下的數(shù)據(jù),如果需要消費其他格式數(shù)據(jù)只需要定義一個處理器即可。</small>
<small>b:解析數(shù)據(jù)格式應(yīng)該是表名+主鍵+字段方式,比喻訂單格式:(payinfo,orderNo,amount,orderTime);統(tǒng)一的數(shù)據(jù)格式為:表名、主鍵<k,v> 、字段名列表List(<k,v>)</small>
<small>c:接入新數(shù)據(jù)流,只需要實現(xiàn)相應(yīng)的處理器即可,保證能解析成統(tǒng)一的數(shù)據(jù)格式</small>
<small>d:可以考慮使用jstrom做到并行處理,每個topology消費一個topic的數(shù)據(jù),增加處理器只需要增加一個bolt即可</small>ES存儲模塊:
<small>問題:</small>
<small>a:是否支持按照主鍵頻繁更新并且對查詢效率影響不大?</small>
<small>b:是否支持動態(tài)添加字段?</small>
<small>c:ETL過后的數(shù)據(jù)如何存儲(一個寬表 or 多個維度表)?</small>
<small>d:根據(jù)定義的存儲結(jié)構(gòu)是否可以方便的對數(shù)據(jù)做聚合?提取特征?(如果多個維度表涉及到j(luò)oin)</small>
<small>e:需要存儲大量的用戶歷史數(shù)據(jù),根據(jù)以上考慮ES是否真的合適?</small>
<small>解決辦法:</small>
<small>a:在QPS較大時ES更新數(shù)據(jù)會影響查詢性能,HBase未調(diào)研</small>
<small>b:ES支持動態(tài)添加字段,但沒有HBase性能好</small>
<small>c:如果是ES,多個維度表性能肯定比寬表好,因為每次更新數(shù)據(jù)就是delete-index操作;如果是HBase,兩種設(shè)計影響不大</small>
<small>d:ES和HBase都不支持join,如果跨表做聚合,必須去做關(guān)聯(lián)查詢</small>
?。澹篐Base在動態(tài)添加字段和頻繁更新的情況下或許更好
- 數(shù)據(jù)聚合模塊:
<small>問題:</small>
<small>a:如何做到數(shù)據(jù)實時增量計算?</small>
<small>b:是否考慮jstorm去做實時計算?</small>
<small>c:特征計算:根據(jù)數(shù)據(jù)流計算所有的定義特征然后組裝算法特征? or 直接根據(jù)算法配置計算特征?</small>
<small>d:在數(shù)據(jù)量較大,特征維度較多的時候如何做到低延遲?</small>
<small>e:如何解決數(shù)據(jù)更新導(dǎo)致統(tǒng)計錯誤的問題?如何解決用戶下單鏈路中數(shù)據(jù)流延遲(因為數(shù)據(jù)流分散到不同的topic中)問題?</small>
<small>解決辦法:</small>
<small>a:數(shù)據(jù)階梯寫入原則,統(tǒng)計時候按照階梯增量統(tǒng)計,兩種可行方案:
1.使用時間段將數(shù)據(jù)分離,比喻19:0020:00是一個數(shù)據(jù)塊,20:0021:00是另一個數(shù)據(jù)塊
2.按照數(shù)據(jù)量將數(shù)據(jù)分離,比喻第10001500是一個數(shù)據(jù)塊,15002000是另一個數(shù)據(jù)塊</small>
<small>b:可以考慮使用jstorm做實時處理,按照字段的維度或者特征維度并行執(zhí)行,比喻提取特征平均訂單金額和最大訂單金額,可以針對amount字段設(shè)置一個bolt,或者直接設(shè)置兩個bolt(需要考慮算法的特征計算不能保證同時執(zhí)行完畢的情況)</small>
<small>c:考慮到特征有復(fù)用的情況,根據(jù)定義的特征計算然后組裝各個算法特征,能大大減少計算量</small>
<small>d:時延取決于實時增量計算的策略和并行計算的能力。如果數(shù)據(jù)階梯劃分越細(xì),jstorm并行計算所有特征吞吐越大,則延遲越低。但是需要考慮數(shù)據(jù)延遲可能導(dǎo)致特征計算錯誤的問題</small>
e:目前沒有太好的辦法,如果不能解決數(shù)據(jù)流問題,只能在一致性和實時性之間做折衷。比喻延遲計算前幾個階梯的數(shù)據(jù),但是會降低實時性,或者直接計算當(dāng)前階梯數(shù)據(jù),但是不能保證一致性
cache模塊:
<small>問題:</small>
<small>a:如何存儲各個算法的特征數(shù)據(jù)?</small>
<small>b:是否支持算法特征數(shù)據(jù)的變化?</small>
<small>c:是否可以快速的根據(jù)算法查詢到訓(xùn)練集和根據(jù)用戶查詢某個算法的特征數(shù)據(jù)以便預(yù)測?</small>
<small>解決辦法:</small>
<small>a:以uid為key建立map,map中存放用戶的特征數(shù)據(jù)(<k,v>形式),需要使用的時候根據(jù)配置的特征順序構(gòu)造特征向量</small>
<small>b:如果按照a的思路,可以支持特征數(shù)據(jù)的動態(tài)變化</small>
<small>c:如果按照a的思路,可以支持</small>學(xué)習(xí)模塊:
<small>問題:</small>
<small>a:如何解決算法語言和框架語言的不一致性?</small>
<small>b:如何隔離各個算法做定期調(diào)用?</small>
<small>c:如何保證數(shù)據(jù)做算法的增量調(diào)用</small>
<small>d:如何存儲訓(xùn)練得到的模型?</small>
<small>解決辦法:</small>
* ?。幔盒枰{(diào)研 *
<small>b:可以考慮使用調(diào)度器,配置調(diào)度時間</small>
?。悖合人伎紝W(xué)習(xí)系統(tǒng)如何能獲取cache中所有數(shù)據(jù)?(cache不允許做全量掃描),當(dāng)前架構(gòu)是否合理?另外,突然提取新特征,所有用戶的新特征如何更新到緩存中?
?。洌嚎梢源鎯υ诒镜?,每個訓(xùn)練的模型都帶有版本號,有沒有更好的方式,調(diào)研訓(xùn)練后的模型如何存儲?
- 全局配置模塊:
<small>問題:</small>
<small>a:配置項有哪些?</small>
<small>b:配置文件存儲到哪里?qconfig?mysql(如何做配置的推送)?</small>
<small>c:如何嚴(yán)格保證特征配置的順序?</small>
<small>d:是否需要定義特征的提取規(guī)則?如果需要如何定義?</small>
<small>解決辦法:</small>
<small>a兩種配置:
1.特征配置項:特征名
2.算法配置項包括:算法名、特征名(和cache中一致)、特征順序、支付方式類別、當(dāng)前配置版本</small>
<small>b:配置文件存放到mysql中或者緩存,學(xué)習(xí)模塊定期讀取訓(xùn)練好的模型,根據(jù)讀取的模型版本讀取對應(yīng)版本的特征數(shù)據(jù)(模型和特征數(shù)據(jù)配置都緩存起來),不做消息的推送</small>
<small>c:通過特征的配置順序?qū)崿F(xiàn)</small>
<small>d:不需要,特征的提取在聚合階段完成,聚合階段實現(xiàn)所有的特征計算。</small> - 實時預(yù)測模塊:
<small>問題:</small>
<small>a:</small>
<small>解決辦法:</small>
<small>a:</small>
