近期有一个需求,须要对优惠券可用商品列表加个排序,只针对面值类的券不包括折扣券。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只是一个简单的场景。
做者:王清培 (沪江集团资深架构师)