近期有一個需求,須要對優惠券可用商品列表加個排序,只針對面值類的券不包括折扣券。html
需求是這樣的,假設有一張面值券 50 塊錢,可用商品列表 A 100、B 40、C 10,當用戶查詢當前券可用商品列表的時候優先將卡券能夠直接抵扣且不須要用戶在額外支付的商品排在前面。架構
C 10
B 40
A 100elasticsearch
其實排序有不少側重,好比:ide
1.根據用戶利益最大化原則,排序列表應該是 B、C、A
2.根據用戶購買習慣,有多是 A、B、C
3.根據運營策略、第三方利益等有多是C、B、Aui
這裏暫且先不擴展如何對商品列表進行智能排序,若是須要完整的個性化商品推薦,涉及不少東西,後面有經驗在拿來分享。插件
咱們就這個簡單的 case,一開始最直接的想法就是加個排序列,建索引的時候將排序值計算好直接寫入。後來分析了下原來索引(index) 結構不是這種笛卡爾積的排列,因此在短期內很難立立刻線,須要新建 index 結構。code
後來經過討論用影響評分的方法來解決,能夠節省時間快速上線。htm
ES query DSL 支持不少種類型的查詢,結果的排序若是沒有特殊聲明 sort field 則是根據es打分(score)來排序的,score 分值越高排序越靠前。對象
ES score 計算比較複雜,涉及到 TF(詞頻)/IDF(逆向文檔頻率)、罕見詞、匹配文檔長度、權重 boost 向量空間模型 等,不過 ES 提供了幾種封裝好的評分插件供使用。排序
function_score 查詢來讓咱們根據業務場景改變文檔評分方法,根據業務場景咱們須要徹底控制 score 生成的邏輯,因此咱們選擇 script_score 方式。
script_score
若是需求超出以上範圍時,用自定義腳本能夠徹底控制評分計算,實現所需邏輯。
(參考:https://www.elastic.co/guide/cn/elasticsearch/guide/current/function-score-query.html)
腳本默認是 groovy,固然也能夠根據須要使用其餘腳本語言,咱們來看下實現。
script.inline: on script.enfine.groovy.inline.aggs: on script.indexed: on script.file: on
首先在 es.yml 配置中打開腳本支持相關選項。
{ "query": { "function_score": { "query": { "bool": { "should": [ { "match": { "productName": "英語" } } ] } }, "score_mode": "first", "script_score": { "lang": "groovy", "params": { "couponPrice": 100 }, "script": "def deduct = couponPrice - doc['unitCost'].value.toFloat(); if (deduct > 0) {return 10000 + deduct;}else if(deduct==0 || (deduct<1 && deduct>0)){return 20000;}else{return doc['unitCost'].value.toFloat()-couponPrice;}" }, "boost_mode": "replace" } }, "from": 0, "size": 100 }
查詢條件能夠任意,關鍵是 script_score 對象,script 是須要 ES 腳本引擎執行的腳本代碼。
一個比較重要的選項 boost_mode ,boost_mode 是控制整個 document 的評分方式,這裏咱們選擇替代(replace)默認計算好的評分。
這裏面的排序有一個小技巧,如何將負數排序在前面,正數排序在後面,還有抵扣後是0的處理。
def deduct = couponPrice - doc['unitCost'].value.toFloat(); if (deduct > 0) { return 10000 + deduct; }else if(deduct==0 || (deduct<1 && deduct>0)){ return 20000; }else{ return doc['unitCost'].value.toFloat()-couponPrice; }
經過 couponPrice 變量表示優惠券面值金額,若是當前商品抵扣完是負數說明須要排序在前面,那麼如何和抵扣完正數分開尼,這裏能夠取一個稍微大點的值加上抵扣後的負值,這樣把負值轉換成正數天然就排序在前面。
抵扣後等於0的或者小於1大於0的值也是能夠優先安排在前面,固然這裏仍是不夠靈活的,最好的方式是根據當前面值、商品價格動態計算才準確。
最後就是抵扣完須要用戶在額外支付的排在最後面,直接取須要額外支付的金額數值做爲排序。
經過 ES 評分咱們能作不少事情,這個case只是一個簡單的場景。
做者:王清培 (滬江集團資深架構師)