
3.2 Elasticsearch-multi_match、boost、function_score 实战
——让“搜得到”进化成“搜得准”
- 本节目标
上一节我们把 2000 万商品导入了 ES,但搜索“苹果手机”却把“苹果味手机壳”顶到第一位。本节用 30 行 DSL 把相关性从“玄学”变成“可控”:
① multi_match 解决“该匹配哪些字段”;② boost 解决“字段谁更重要”;③ function_score 解决“业务规则怎么加权”。最后给出一套可复制的调参模板,直接搬进生产线。
- multi_match:一把梭哈多个字段
1.1 最朴素的 query_string
GET /mall/_search
{“query”:{“query_string”:{“query”:“苹果手机”,“default_field”:“_all”}}}
❌ 结果:把 description 里出现“苹果”的袜子排第一,因为 _all 字段里 description 最长,TF 得分高。
1.2 multi_match 三模式
- best_fields:默认,单字段最高分当最终分
- most_fields:多字段得分加和,适合同义词冗余
- cross_fields:把多个字段当成一个大字段,解决“分词跨字段”问题
1.3 实战:商品标题3、卖点2、类目名^1
GET /mall/_search
{
“query”: {
“multi_match”: {
“query”: “苹果手机”,
“fields”: [
“title^3”,
“sell_point^2”,
“category_name^1”
],
“type”: “best_fields”,
“tie_breaker”: 0.3
}
}
}
✅ 效果:iPhone 排到第一,苹果味手机壳降到第 5 页。
- boost:细粒度“偏心”权重
场景:旗舰店商品想加 10% 分,库存为 0 的减 50%。
ES 的 boost 可在索引期写死,但推荐在查询期用“布尔查询 + boosting”动态调整,避免 re-index。
示例:
GET /mall/_search
{
“query”: {
“boosting”: {
“positive”: {
“multi_match”: {
“query”: “苹果手机”,
“fields”: [“title^3”, “sell_point^2”]
}
},
“negative”: {
“term”: { “stock”: 0 }
},
“negative_boost”: 0.5
}
}
}
解释:先算 positive 得分,若满足 negative 子句,最终得分 *= 0.5。旗舰店加 10% 可用 should + constant_score 再包一层 bool,灵活组合。
- function_score:把“业务规则”翻译成“数学公式”
3.1 四种内置函数
- weight:简单粗暴乘系数
- field_value_factor:拿字段值当系数,如销量、评分
- decay_gauss/linear/exp:地理位置、价格、发布时间衰减
- script_score:Groovy/Painless 任意脚本,天上地下都能算
3.2 需求拆解
“苹果手机”搜索排序公式:
score = 相关性 * (1 + 销量/10000) * 0.8^days_from_now * 是否现货(1 or 0.5)
3.3 DSL 模板
GET /mall/_search
{
“query”: {
“function_score”: {
“query”: {
“multi_match”: {
“query”: “苹果手机”,
“fields”: [“title^3”, “sell_point^2”],
“type”: “best_fields”
}
},
“functions”: [
{
“field_value_factor”: {
“field”: “sold_num”,
“modifier”: “log1p”,
“factor”: 0.0001
}
},
{
“gauss”: {
“on_shelf_date”: {
“origin”: “now”,
“scale”: “10d”,
“decay”: 0.8
}
}
},
{
“filter”: { “term”: { “stock”: 0 } },
“weight”: 0.5
}
],
“score_mode”: “multiply”,
“boost_mode”: “multiply”
}
}
}
关键参数说明:
- log1p 避免销量 0 时乘以 0 导致无得分
- gauss 10 天衰减到 0.8,30 天大约 0.5,可 A/B 测试调 scale
- weight 0.5 相当于 negative_boost,但可叠加多个 filter
- 调参“三板斧”
- 解释 API 看分值:?explain=true,快速定位谁拖了后腿
- 复制真实请求到 Kibana,改一个参数刷一次,用表格记录
- 离线测评:拉 1000 条人工标注 query,计算 NDCG@10,脚本批量跑,一晚出结果
- 踩坑清单
× 滥用 script_score:线上 2000 qps 时,Groovy 编译缓存打满 Young GC,直接 STW 3 s;换 field_value_factor + decay 后 CPU 降 40%
× 在索引期设置 field boost:后期改权重必须 re-index,曾导致凌晨 4 点全集群重写,被老板请喝茶
× cross_fields 对“中文+拼音”混合字段失效:记得统一 analyzer,否则“ping guo”匹配不到“苹果”
- 一键拷贝模板
把下面 JSON 存成 search_template.json,后期只改参数即可:
POST /_scripts/mall_search
{
“script”: {
“lang”: “mustache”,
“source”: “”"
{
“from”: “{{from}}{{^from}}0{{/from}}”,
“size”: “{{size}}{{^size}}20{{/size}}”,
“query”: {
“function_score”: {
“query”: {
“multi_match”: {
“query”: “{{q}}”,
“fields”: [
“title{{title_boost}}{{title_boost}}3{{/title_boost}}”,
“sell_point{{sell_boost}}{{sell_boost}}2{{/sell_boost}}”,
“category_name^1”
],
“type”: “best_fields”,
“tie_breaker”: 0.3
}
},
“functions”: [
{
“field_value_factor”: {
“field”: “sold_num”,
“modifier”: “log1p”,
“factor”: {{sold_factor}}{{^sold_factor}}0.0001{{/sold_factor}}
}
},
{
“gauss”: {
“on_shelf_date”: {
“origin”: “now”,
“scale”: “{{new_scale}}{{^new_scale}}10d{{/new_scale}}”,
“decay”: {{new_decay}}{{^new_decay}}0.8{{/new_decay}}
}
}
}
],
“score_mode”: “multiply”,
“boost_mode”: “multiply”
}
}
}
“”"
}
}
调用:
GET /mall/_search/template
{
“id”: “mall_search”,
“params”: {
“q”: “苹果手机”,
“title_boost”: 4,
“new_scale”: “7d”
}
}
- 小结
multi_match 解决“查什么”,boost 解决“谁重要”,function_score 解决“怎么算”。三件套组合后,搜索 PM 再想“把 iPhone 14 置顶”只需调两个数字,而不用凌晨拉集群。下一节我们把这套脚本固化到 Search Template + Index Alias,配合 AB 实验平台,让运营同学也能 5 分钟上线“新品加权 20%”实验。
更多技术文章见公众号: 大城市小农民

被折叠的 条评论
为什么被折叠?



