Elasticsearch——利用Parent-Child關係解決大數據場景下的實時查詢

表與表之間的關聯基本上是所有業務系統都存在的,RDBMS通過外鍵實現,MongoDB通過嵌入式子文檔解決,那麼Elasticsearch怎麼解決這個問題呢?答案就是Parent-Child關聯(參考文檔

業務場景

有一個廣告的分發系統,爲了更精準的做廣告的推送,除了自身積累的數據以外,還會從其他合作方通過數據交換(當然這些都是脫敏的數據)的方式獲取更多用戶行爲數據,例如從音樂網站獲取聽的音樂列表、從購物網站獲取最近的購物類別、從書評網站獲取最近瀏覽的圖書等等。這些來自於外部的數據,有以下幾個問題:

  1. 並不是每個用戶都有全部的數據,比如有些用戶只有書評和音樂信息,而有些用戶沒有任何外部信息
  2. 某一類外部的數據源可能包含幾個網站,比如音樂網站有A、B、C三個網站,它們提供的數據格式也並不一致

在進行廣告推送時,需要實時查詢一個用戶的信息完成精準推薦。比如實時查詢滿足下面條件的用戶:

  • 最近一個月,
  • 經常在早上、傍晚或者晚上連續一個小時的音樂;
  • 購買過跑鞋、運動手錶等跑步裝備
  • 且購買過或點評過運動類書籍

再繼續下面的(十分簡化)解決方案之前,可以先思考下

解決方案

這是典型應用大數據進行個性化精準推薦的應用場景,在省卻了數據清洗、評分等各種步驟以後,簡化爲一個查詢問題。分析可以發現數據問題的核心就是:無固定表結構,是典型的Schema-Free的NoSQL應用場景,第一個反應出來的就是MongoDB。

MongoDB

MongoDB用作以上的數據存儲,毫無疑問具有天然的優勢,可以將每個來源的數據都作爲user的一個子文檔存儲,查詢時也只是在這一個Collection上進行(可能有人會說這種方案太蠢了,的確是,不過也要看產品所處的階段)。當然這樣做的問題也顯而易見:
爲了查詢速度,索引是必須要創建的。可是因爲數據源不斷變化,那麼索引的維護就會變成一個災難。一旦忘記創建查詢,可能就會拖死整個系統。

下面當然就是主角上場了。

Elasticsearch

定調:
1. 由於字段是變化,因此必須使用動態Mapping(文檔
2. 由於Parent-Child的關係需要創建索引(Create Index)時就確定,因此必須使用固定的Mapping(文檔

我又檢查了上面兩條,的確是沒有說錯。

其實很簡單,在創建索引時,只需指定父子關係,無需指定其他未知字段。因爲要預先指定type的父子關係,所以就必須先確定type。這是用兩個type:user和user_action,那麼創建索引時的Mapping大致如下:

{
  "mappings": {
    "user": {},
    "user_action": {
      "_parent" : {
        "type": "user"
      }
    }
  }
}

我好像把文檔中的例子抄了一遍,不多實際情況的確是這樣。

那麼在添加文檔到索引中時,對於user就需要指定id,而user_action需要指定parent,例如:

es = Elasticsearch()

_id = 27
_user = {
  'id': 27,
  'name': 'Tigger Fei'
}

# 索引用戶文檔
es.index(index='user_index', doc_type='user', id=str(_id), body=_user)

# 索引用戶行爲文檔, type字段表示列表
# 音樂
_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)
# 圖書,
_book = {
  'type': 'book,'
  'user': 27,
  'name': '我的第一個馬拉松',
  'category': 'running',
  'time': '2017-01-30 12:30:00'
}
es.index(index='user_index', doc_type='user_action', parent=str(_id), body=_book)

如何完成上面的查詢呢,如下:

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"}}
                ]
              }
            }
          }
        }
      ]
    }
  }
}

好了,這個簡單的解決方案就完了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章