引子:一场关于“模糊”需求的拉锯战
“咱们这个搜索功能,用户反馈说经常只记得内容中间的几个字,希望支持前后模糊匹配,就像MySQL里LIKE \'%关键词%\'那样。”
“在ES里做前后通配符?这玩意搞不好会把集群搞崩啊!” 我试图挣扎。
“但是竞品都有这个功能了…” 产品经理使出了杀手锏。
经过一番“友好协商”,我们达成共识:工期可以延长,但这个功能必须实现!
送走产品经理,我盯着屏幕陷入沉思:在Elasticsearch里做前后模糊匹配,这确实是个技术挑战。不过话说回来,我们正准备新采购ES集群,和主管评估后决定直接上8.x版本——等等,ES 7.9不是引入了专门的wildcard字段类型吗?
最终方案:基于ES 8.x的wildcard类型字段 + wildcard查询,完美实现前后模糊匹配!
从“分词”这个基础概念说起
要理解ES的模糊搜索,得先搞明白它最核心的概念——分词。
分词的奇妙世界
当你往ES里存入“苹果手机真香”时,背后发生了这样的变化(使用不同分词器,分出来的词可能不一样):
原始文本:\"苹果手机真香\"
↓ 分词处理
[\"苹果\", \"手机\", \"真\", \"香\"]
这就是为什么最简单的match查询能够工作:
GET /products/_search
{
\"query\": {
\"match\": {
\"name\": \"苹果手机\"
}
}
}
但是,这里藏着第一个坑!
默认情况下,match查询使用or操作符,意味着:
// 搜索\"苹果手机\"可能返回:
// - \"苹果电脑\"(只匹配\"苹果\")
// - \"华为手机\"(只匹配\"手机\")
// - \"苹果手机\"(完全匹配)
// - \"好吃苹果\"(只匹配\"苹果\")
用户想要的是“苹果手机”,结果搜出来一堆不相干的东西,这体验能好吗?
更精确的匹配方式
match + operator \”and\” – 必须全部包含
GET /products/_search
{
\"query\": {
\"match\": {
\"name\": {
\"query\": \"苹果手机\",
\"operator\": \"and\"
}
}
}
}
效果:必须同时包含\”苹果\”和\”手机\”两个词。
进步: 排除了只包含一个词的无关结果。
新问题:顺序不固定!“手机苹果”也会被匹配,这显然不符合正常语言习惯。
match_phrase – 真正的词组匹配
GET /products/_search
{
\"query\": {
\"match_phrase\": {
\"name\": \"苹果手机\"
}
}
}
完美!必须完整包含\”苹果手机\”这个词组,且顺序一致。
但是… 当测试用例显示:“用户只记得\’果手\’两个字,怎么搜不到\’苹果手机\’?”
我意识到,传统的分词搜索有其局限性。
ES 7.9之前的解决方案:n-gram分词器
面对前后模糊匹配的需求,在ES 7.9之前,最成熟的方案就是n-gram分词器 + match_phrase实现。
什么是n-gram?
简单说,就是把文本切成固定长度的片段:
原始文本:\"苹果手机\"
2-gram分词:[\"苹果\", \"果手\", \"手机\"]
3-gram分词:[\"苹果手\", \"果手机\"]
配置n-gram分析器
PUT /products
{
\"settings\": {
\"analysis\": {
\"analyzer\": {
\"ngram_analyzer\": {
\"tokenizer\": \"ngram_tokenizer\"
}
},
\"tokenizer\": {
\"ngram_tokenizer\": {
\"type\": \"ngram\",
\"min_gram\": 2, // 最小2个字符
\"max_gram\": 3 // 最大3个字符
}
}
}
},
\"mappings\": {
\"properties\": {
\"name\": {
\"type\": \"text\",
\"analyzer\": \"ngram_analyzer\",
\"search_analyzer\": \"standard\"
}
}
}
}
实现前后模糊匹配
GET /products/_search
{
\"query\": {
\"match_phrase\": {
\"name\": \"果手\"
}
}
}
效果:成功匹配到\”苹果手机\”!
付出的代价:
- 支持任意位置的子串匹配
- 索引体积膨胀3倍以上
- 查询性能受影响
- 需要精细调整n-gram参数
危险的诱惑:7.9之前的wildcard查询
在调研过程中,我发现ES其实一直都有wildcard查询,但文档里满是红色警告。
揭开wildcard查询的真相
常见误解1: \”7.9版本以下只能查keyword字段\”
事实: wildcard可以作用于text字段,但匹配的是分词后的term,结果往往出乎意料,不尽人意。
常见误解2: \”会进行全索引扫描\”
事实: 扫描的是字段倒排索引中的所有term,对每个term进行正则匹配。
wildcard查询实战
// 对keyword字段查询(相对可用)
GET /products/_search
{
\"query\": {
\"wildcard\": {
\"name\": {
\"value\": \"*iPhone*\",
\"case_insensitive\": true
}
}
}
}
// 对text字段查询(强烈不推荐)
GET /products/_search
{
\"query\": {
\"wildcard\": {
\"name\": {
\"value\": \"*iphone*\"
}
}
}
}
性能灾难:前导通配符*会导致遍历所有term,CPU和内存瞬间飙升,妥妥的集群杀手!
新时代的解决方案:ES 7.9+的wildcard字段类型
就在我纠结要不要接受n-gram的索引膨胀时,突然想起:我们不是准备采购ES 8.x吗?
ES 7.9引入的wildcard字段类型简直就是为此场景量身定制!
技术原理揭秘
- 智能n-gram索引:底层使用优化的3字符n-gram
- 二进制doc value:完整保存原始文档,保证匹配精度
- 专用查询引擎:针对通配符场景深度优化
实际配置和使用
PUT /products
{
\"mappings\": {
\"properties\": {
\"name\": {
\"type\": \"wildcard\" // 专门为通配符优化的字段类型
}
}
}
}
GET /products/_search
{
\"query\": {
\"wildcard\": {
\"name\": {
\"value\": \"*果手*\" // 前后模糊匹配
}
}
}
}
性能对比:数字说话
在我们的测试环境中:
| 方案 | 索引大小 | 平均查询延迟 | 集群影响 | 功能完整性 |
|---|---|---|---|---|
| n-gram + match_phrase | 原始大小 × 约3倍 | 50ms左右 | 中等 | |
| 旧版wildcard查询 | 原始大小 | 1000ms+ | 极高风险 | |
| wildcard字段类型 | 原始大小 × 约1.4倍 | 25ms左右 | 很低 |
结果显而易见!
最终技术选型
经过充分的测试和对比,我们最终拍板:
- 采购Elasticsearch 8.x集群
- 对需要模糊匹配的字段使用
wildcard类型 - 传统搜索场景继续使用
match_phrase等成熟方案
// 最终的映射设计
PUT /products
{
\"mappings\": {
\"properties\": {
\"name\": {
\"type\": \"wildcard\" // 用于前后模糊匹配
},
\"description\": {
\"type\": \"text\" // 用于常规全文搜索
},
\"category\": {
\"type\": \"keyword\" // 用于精确分类匹配
}
}
}
}
当演示结果出来时,产品和用户都很满意:“所以现在输入\’果手\’真的能找到\’苹果手机\’了?而且性能还不错?”
“没错,这就是技术演进的力量!”我微笑着回答。
(其实是工期足的力量️,工期足够长,资金足够多,什么都能做)
总结:Elasticsearch模糊搜索方案对比
| 搜索方式 | 适用场景 | 优点 | 缺点 | 推荐指数 |
|---|---|---|---|---|
match |
常规全文搜索 | 简单易用 | 精度较低 | ⭐⭐⭐⭐ |
match + operator: \"and\" |
多词必须匹配 | 提高相关性 | 顺序不固定 | ⭐⭐⭐ |
match_phrase |
精确词组匹配 | 顺序一致 | 不支持模糊 | ⭐⭐⭐⭐ |
n-gram + match_phrase |
前后模糊匹配 | 功能完整 | 索引膨胀严重 | ⭐⭐⭐ |
旧版wildcard查询 |
通配符匹配 | 使用简单 | 性能极差 | ⭐ |
wildcard字段类型 |
前后模糊匹配 | 性能优秀 | 需要ES 7.9+ | ⭐⭐⭐⭐⭐ |
技术心得:
从最初的match查询到最终的wildcard字段类型,这条演进之路告诉我们:
- 了解业务场景:不同的搜索需求需要不同的技术方案
- 理解底层原理:明白分词机制和查询原理才能做出正确选择
- 拥抱技术演进:新版本往往用更优雅的方式解决老问题
技术人的快乐,往往就藏在解决这些“模糊”需求的过程中。毕竟,让模糊的需求变得清晰,让不可能成为可能——这就是我们的职业乐趣所在!
最后,不知道jym在使用ES搜索功能中还遇到过哪些有趣的技术挑战?欢迎大家在评论区分享你的“血泪史”,让我们一起在技术的道路上避坑前行!



还没有评论呢,快来抢沙发~