表與表之間的關(guān)聯(lián)基本上是所有業(yè)務(wù)系統(tǒng)都存在的,RDBMS通過(guò)外鍵實(shí)現(xiàn),MongoDB通過(guò)嵌入式子文檔解決,那么Elasticsearch怎么解決這個(gè)問(wèn)題呢?答案就是Parent-Child關(guān)聯(lián)(參考文檔)
有一個(gè)廣告的分發(fā)系統(tǒng),為了更精準(zhǔn)的做廣告的推送,除了自身積累的數(shù)據(jù)以外,還會(huì)從其他合作方通過(guò)數(shù)據(jù)交換(當(dāng)然這些都是脫敏的數(shù)據(jù))的方式獲取更多用戶(hù)行為數(shù)據(jù),例如從音樂(lè)網(wǎng)站獲取聽(tīng)的音樂(lè)列表、從購(gòu)物網(wǎng)站獲取最近的購(gòu)物類(lèi)別、從書(shū)評(píng)網(wǎng)站獲取最近瀏覽的圖書(shū)等等。這些來(lái)自于外部的數(shù)據(jù),有以下幾個(gè)問(wèn)題:
并不是每個(gè)用戶(hù)都有全部的數(shù)據(jù),比如有些用戶(hù)只有書(shū)評(píng)和音樂(lè)信息,而有些用戶(hù)沒(méi)有任何外部信息某一類(lèi)外部的數(shù)據(jù)源可能包含幾個(gè)網(wǎng)站,比如音樂(lè)網(wǎng)站有A、B、C三個(gè)網(wǎng)站,它們提供的數(shù)據(jù)格式也并不一致在進(jìn)行廣告推送時(shí),需要實(shí)時(shí)查詢(xún)一個(gè)用戶(hù)的信息完成精準(zhǔn)推薦。比如實(shí)時(shí)查詢(xún)滿(mǎn)足下面條件的用戶(hù):
最近一個(gè)月,經(jīng)常在早上、傍晚或者晚上連續(xù)一個(gè)小時(shí)的音樂(lè);購(gòu)買(mǎi)過(guò)跑鞋、運(yùn)動(dòng)手表等跑步裝備且購(gòu)買(mǎi)過(guò)或點(diǎn)評(píng)過(guò)運(yùn)動(dòng)類(lèi)書(shū)籍再繼續(xù)下面的(十分簡(jiǎn)化)解決方案之前,可以先思考下
這是典型應(yīng)用大數(shù)據(jù)進(jìn)行個(gè)性化精準(zhǔn)推薦的應(yīng)用場(chǎng)景,在省卻了數(shù)據(jù)清洗、評(píng)分等各種步驟以后,簡(jiǎn)化為一個(gè)查詢(xún)問(wèn)題。分析可以發(fā)現(xiàn)數(shù)據(jù)問(wèn)題的核心就是:無(wú)固定表結(jié)構(gòu),是典型的Schema-Free的NoSQL應(yīng)用場(chǎng)景,第一個(gè)反應(yīng)出來(lái)的就是MongoDB。
MongoDB用作以上的數(shù)據(jù)存儲(chǔ),毫無(wú)疑問(wèn)具有天然的優(yōu)勢(shì),可以將每個(gè)來(lái)源的數(shù)據(jù)都作為user的一個(gè)子文檔存儲(chǔ),查詢(xún)時(shí)也只是在這一個(gè)Collection上進(jìn)行(可能有人會(huì)說(shuō)這種方案太蠢了,的確是,不過(guò)也要看產(chǎn)品所處的階段)。當(dāng)然這樣做的問(wèn)題也顯而易見(jiàn): 為了查詢(xún)速度,索引是必須要?jiǎng)?chuàng)建的。可是因?yàn)閿?shù)據(jù)源不斷變化,那么索引的維護(hù)就會(huì)變成一個(gè)災(zāi)難。一旦忘記創(chuàng)建查詢(xún),可能就會(huì)拖死整個(gè)系統(tǒng)。
下面當(dāng)然就是主角上場(chǎng)了。
定調(diào): 1. 由于字段是變化,因此必須使用動(dòng)態(tài)Mapping(文檔) 2. 由于Parent-Child的關(guān)系需要?jiǎng)?chuàng)建索引(Create Index)時(shí)就確定,因此必須使用固定的Mapping(文檔)
我又檢查了上面兩條,的確是沒(méi)有說(shuō)錯(cuò)。
其實(shí)很簡(jiǎn)單,在創(chuàng)建索引時(shí),只需指定父子關(guān)系,無(wú)需指定其他未知字段。因?yàn)橐A(yù)先指定type的父子關(guān)系,所以就必須先確定type。這是用兩個(gè)type:user和user_action,那么創(chuàng)建索引時(shí)的Mapping大致如下:
{ "mappings": { "user": {}, "user_action": { "_parent" : { "type": "user" } } }}我好像把文檔中的例子抄了一遍,不多實(shí)際情況的確是這樣。
那么在添加文檔到索引中時(shí),對(duì)于user就需要指定id,而user_action需要指定parent,例如:
es = Elasticsearch()_id = 27_user = { 'id': 27, 'name': 'Tigger Fei'}# 索引用戶(hù)文檔es.index(index='user_index', doc_type='user', id=str(_id), body=_user)# 索引用戶(hù)行為文檔, type字段表示列表# 音樂(lè)_music = { 'type': 'music', 'user': 27, 'period': 'morning', 'duration': 78, 'category': 'running', 'time': '2017-01-29 12:30:00'}es.index(index='user_index', doc_type='user_action', parent=str(_id), body=_music)# 圖書(shū),_book = { 'type': 'book,' 'user': 27, 'name': '我的第一個(gè)馬拉松', 'category': 'running', 'time': '2017-01-30 12:30:00'}es.index(index='user_index', doc_type='user_action', parent=str(_id), body=_book)如何完成上面的查詢(xún)呢,如下:
POST user_index/user/_search{ "query": { "bool": { "filter": [ { "has_child": { "type": "user_action", "query": { "bool": { "filter": [ {"term": {"type": "music"}}, {"range": {"duration": {"gte": 60}}}, {"range": { "time": { "gte": "2017-01-07 00:00:00", "format": "yyyy-MM-dd HH:mm:ss" } }}, {"term": {"category": "running"}}, {"terms": {"period": ["morning", "night"]}} ] } } } }, { "has_child": { "type": "user_action", "query": { "bool": { "filter": [ {"range": { "time": { "gte": "2017-01-07 00:00:00", "format": "yyyy-MM-dd HH:mm:ss" } }}, {"term": {"type": "book"}}, {"term": {"category": "running"}} ] } } } } ] } }}好了,這個(gè)簡(jiǎn)單的解決方案就完了。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注