電商搜索的多路召回

當選用 elasticsearch 作爲電商的商品搜索存儲系統時,用戶輸入一個 query 時,這個 query 是如何從es 中查詢出商品數據的?

首先,用戶輸入的 query 詞會通過query 分析服務產出若干個從不同維度表達用戶意圖的tokens。比如輸入“紅富士蘋果”,經 query 分析後會產出以下維度的tokens:
image

比如搜索“紅富士蘋果”,query分析產出的 tokens有:品牌詞:紅富士;本質詞:蘋果、水果;商品的四級類目id;商品的SEO詞(正宗、清甜、現摘)

  1. 本質詞
  2. 品牌詞
  3. 商家名稱
  4. query 所要召回的商品四級類目 id
  5. 商品的 seo詞
    ...

這裏不對 query 分析作詳細討論。總之 query 分析是作用是將用戶輸入的 query 詞變成若干能表達用戶搜索意圖的“維度”,然後基於這些“維度” 去召回商品。正是 query 分析會產出不同“維度”的tokens,根據這些 tokens 查詢 es 獲取商品數據的過程,就稱爲多路召回,每一路召回,我們可以根據它的表徵“維度”,構造不同的 es 查詢語句。

比如:針對四級類目 id,則可以使用:bool-query-filter 查詢;而針對:品牌詞,可以使用 bool-query-must-term 查詢,而針對本質詞,可能會有多個層級:比如蘋果、水果,則可以決定更相關的使用 must 查詢、低相關的使用 should 查詢。此外,如果某一路產出的 token有很多,而且不同的 token 之間權重不一樣,也可以考慮採用 constant-score-query 來封裝某些 token的查詢。

  "query": {
    "bool": {
      "filter": [] // part 1: 過濾條件
      "must": [], // part 1: 召回商品必須滿足的條件
      "should": [] // part 2: 加分,相關性條件
    }
  }

然而,對於召回來說,保證每次查詢結果的穩定性對用戶體驗、線上 case 的排查都非常重要。但是由於es的分片機制,以及線上增量數據的不斷寫入,某些 es 查詢語句並不具有冪等性。比如就描述了使用 rescore 查詢時因es 分片參數不一致導致的查詢結果不一致問題。而對於線上核心電商搜索系統而言,應當要盡力避免這種不一致,所以一般在生產系統中,構造出來的查詢語句要儘量保證其得分的穩定性,比如使用 constant-score-query 來封裝子查詢。另外,一般在 es 中並不存儲 text 類型的字段並進行打分查詢,而是通過 query 分析,將 query 分解成能表達用戶意圖的 token,基於這些 token 做“過濾”查詢。

之所以是這樣設計,其實也是跟電商搜索是:“結構化”召回有關。思考這樣一個問題:es 裏面要存儲哪些字段來支持商品的搜索呢?比如對於一個商品 sku而言:

  1. 商品名稱的分詞結果,使用不同的分詞器產出的分詞結果不一樣,會在 es 中使用多個字段存儲分別存儲不同分詞器的分詞結果。
  2. 商家名稱的分詞結果
  3. 商品的價格
  4. 商品的銷量
  5. 品牌名稱(分詞結果)
  6. 商品的一二三四級類目id
  7. 商品的本質詞(模型特徵)
  8. spu_id、poi_id 等一些正排字段...

這些字段,其實都是結構化了的字段。注意,我們並不是使用 text 類型存儲商品名稱,而是將商品名稱分詞之後,在 es 索引構建時,將它們存儲到 es 中,這樣做的目的是:term 查詢比 text 查詢性能要好;text 查詢的排序結果受es打分影響(分片上 doc 數量的變化),可能會導致從 es 中查出來的排序結果不穩定;在召回之前,肯定是要有query 分析的,query 分析不僅僅是對用戶輸入的查詢語句做分詞,還有其它一些 處理,比如同義詞、相似詞,query 的類目分析等;因此到 es 查詢這一步,主要是 term 或者 filter 查詢,並沒有需要分詞的 text 查詢。

正因爲多路召回的存在,可能導致查詢語句非常的複雜,如果將多路召回表示成一個 es 查詢語句,很有可能在召回階段導致的耗時較高,因此將每一路召回(或者相關的某幾路)放到一個 es 查詢語句中(SearchBuilder),然後以線程池隔離向 es 發起查詢,所以一次用戶查詢,到 es 召回時會被“放大”成多次 es 查詢,存在着“查詢放大”效應。

多路召回面臨的一個問題是:查詢結果的合併。對於商品而言,會有主鍵 spu_id 唯一標識一個商品,因此可以將它作爲多路召回結果的合併主鍵。接下來就是將合併結果送到 rank 服務做排序了。rank 排序這裏不做詳細介紹。

從工程上來說,多路召回需要考慮的一些因素有:

  1. 統一各路召回的查詢語句,當有新的需求或者新的特徵被挖掘出來,需要新增一路召回時,能夠方便地新增一路召回,從而方便業務快速迭代。
  2. 如果在 rank 層排序時,依賴es 的分數,則需要謹慎考慮修改 es 查詢語句對 spu_id 的打分的影響。一般地,es 的打分只是作爲粗排的一個因子,粗排完之後,rank 的模型打分纔是決定商品順序的關鍵因素。
  3. 某一路查詢結果失敗,並不影響其它路的查詢結果,可允許部分失敗。
  4. 能夠較好地分析查詢結果 spu_id 是由哪一路查詢語句召回的,甚至是由哪一個查詢條件召回的,這樣有利於 case 排查分析。參考:es matched_queries。
  5. 每一路召回的查詢耗時監控、查詢結果數量 tp999 監控,支持異步查詢 es(可參考 es 提供的 ListenableActionFuture 類),查詢語句的限流等。這需要在RestHighLevelClient的基礎之上進一步封裝 es 客戶端,並且與公司內部的基礎組件打通(限流組件、監控組件)
  6. ES 索引構建時,避免doc 在分片上分佈不均勻,從而影響 es 查詢語句的穩定性。比如 es 索引實時更新時,es分片的doc 數量可能會動態變化,或者說同一個 spu_id 會不會在不同的索引構建日期,分佈在不同的分片上。這往往是在索引構建時,指定 doc哈希到固定的分片。具體的索引構建本文不詳細介紹。

一般常用的 es query 有如下幾種:

  1. query-bool-must
  2. query-bool-filter
  3. query-bool-should
  4. constant-score-query
  5. rescore query
    視待查的具體字段,以及 query 分析產出結果選擇何種查詢。

參考:

  1. https://mp.weixin.qq.com/s/Nisgorg_Qgr-AdVm3c99LQ
  2. https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章