前段时间线上商城的搜索模块被用户疯狂吐槽:“搜个‘苹果手机壳’,结果第一页全是‘苹果水果’和‘华为手机壳’,怎么想的?”由于之前的搜索业务是直接用 MySQL 的 LIKE 模糊查询糊出来的,数据量上去之后不仅慢得像蜗牛,召回率 and 精确度也惨不忍睹。为了彻底解决这个问题,我们痛下决心把搜索独立出来,上了一套“Debezium CDC + Flink + Elasticsearch”的经典架构。本以为配好数据同步就万事大吉,没想到在实际的搜索相关性(Relevancy)调优上,又踩了一堆意想不到的大坑。
第一个大坑就是同步延迟。我们最初用的是传统定时扫表的方案,每 5 分钟扫一次 MySQL 的商品表写入 ES。结果运营同学直接崩溃了:后台改了商品库存和价格,前台搜索页 5 分钟都不能同步,导致大量用户下单时报“商品已下架”或价格变动。干脆一步到位,直接上 Flink-CDC (基于 Debezium) 监听 MySQL binlog。
在同步链路调通之后,真正的重头戏来到了 Elasticsearch 的 BM25 相关性算法调优。ES 默认的 BM25 相似度计算公式中,有两个核心参数:k1(控制词频饱和度)和 b(控制文档长度惩罚)。
默认情况下,k1 = 1.2,b = 0.75。对于一般的网页搜索这个默认值没毛病,但在商品搜索场景下,商品标题通常非常短且堆砌词严重(比如“2026新款 苹果/Apple iPhone 15 Pro Max 手机壳 硅胶防摔保护套”),默认的 b = 0.75 会过度惩罚较长的标题,导致一些长标题的精准匹配商品排名靠后。
经过几轮灰度测试和埋点数据排查,我们发现把商品标题字段的 b 值调小到 0.3,将 k1 调大到 2.0(增强主词频的影响力),能极大提升搜索词的精准度。以下是我们最终在线上落地的 Elasticsearch 索引 Mapping 与 Similarity 配置,大家可以参考:
{
"settings": {
"index": {
"number_of_shards": 3,
"number_of_replicas": 1
},
"analysis": {
"analyzer": {
"ik_max_word_custom": {
"type": "custom",
"tokenizer": "ik_max_word",
"char_filter": [
"html_strip"
],
"filter": [
"lowercase"
]
}
}
},
"similarity": {
"title_bm25_custom": {
"type": "BM25",
"k1": 2.0,
"b": 0.3
},
"desc_bm25_custom": {
"type": "BM25",
"k1": 1.2,
"b": 0.8
}
}
},
"mappings": {
"properties": {
"product_id": {
"type": "long"
},
"title": {
"type": "text",
"analyzer": "ik_max_word_custom",
"search_analyzer": "ik_smart",
"similarity": "title_bm25_custom",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"description": {
"type": "text",
"analyzer": "ik_max_word_custom",
"search_analyzer": "ik_smart",
"similarity": "desc_bm25_custom"
},
"price": {
"type": "double"
},
"status": {
"type": "integer"
},
"created_at": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||strict_date_optional_time||epoch_millis"
}
}
}
}
针对商品标题,由于很多商家会故意堆砌关键词,如果完全不做长度惩罚,长标题很容易因为包含多个无意义词而排在前面。通过把 b 降到 0.3,我们降低了标题长度对得分的影响,使那些精准命中的短标题能够排在更合理的位置。
同时,我们遇到了多字段检索的另一个坑:使用 multi_match 查询时,如果简单使用 best_fields 模式,搜索结果完全被主名字的得分主导,副词(如描述、标签)的贡献直接被抹杀。后来折腾了很久,干脆换成了 cross_fields 模式,并且对 title 进行了 ^3 的权重提升,对 description 做了 ^0.5 的降权,才彻底搞定了商品名和描述之间的相关性心智负担。
这套优化上线后,搜索跳出率直接下降了 12%,GMV 也有了小幅拉升。不过,搜索引擎的调优永远是个黑盒,AB 测试和长尾搜索词的持续分析才是硬道理,最近我们又在盘算着接入点击反馈数据做二次精排,不知道社区里有没有大佬做过这套闭环的?