普通全文检索只能命中词面一致的文档,真实的搜索体验还要求:用户搜「手机」能带出「智能手机」、打错一个字也能找到结果、输入前缀立即出现补全建议。本文讲解同义词、n-gram、completion suggester 与模糊匹配四类能力,并讨论索引时与查询时处理的取舍。
1. 同义词与检索扩展
一句话总结: 同义词把语义等价的词折叠成统一词项,扩大召回而不增加查询复杂度。
1.1 同义词过滤器
{
"settings": {
"analysis": {
"filter": {
"synonym_filter": {
"type": "synonym",
"synonyms": [
"电脑,计算机,PC => 电脑",
"手机,智能手机,手机终端"
]
}
},
"analyzer": {
"synonym_analyzer": {
"type": "custom",
"tokenizer": "ik_max_word",
"filter": ["lowercase", "synonym_filter"]
}
}
}
},
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "synonym_analyzer",
"search_analyzer": "synonym_analyzer"
}
}
}
}
synonym filter 有两种写法:箭头形式「=>」只把左侧展开成右侧(收敛),逗号形式则组内互相等价(双向展开)。双向展开在查询时膨胀词项、影响打分,量大的同义词表建议收敛方向。
1.2 索引时与查询时
同义词可以在索引时展开,也可以只在查询时展开。索引时展开会让每个同义词都进倒排索引,段变大、词项变多,后续改同义词表必须重建索引;查询时展开保持索引干净,同义词表可即时生效,代价是每次查询多一步分析。生产上默认查询时展开,除非查询 QPS 极高到无法接受分析开销。
1.3 synonym_graph 处理多词同义词
{
"settings": {
"analysis": {
"filter": {
"synonym_graph_filter": {
"type": "synonym_graph",
"synonyms": ["搜索引擎,检索引擎"]
}
}
}
}
}
多词同义词(如「搜索引擎」对应「检索引擎」)需要 synonym_graph 而非 synonym:graph 会把同义词展开成多词短语图,配合 match_phrase 才能正确匹配位置关系;普通 synonym 对多词短语生成的词元顺序可能错乱。
2. n-gram 与边缘匹配
一句话总结: n-gram 把词切成连续子串,让部分输入也能命中,尤其适合无空格文本与容错补全。
2.1 edge_ngram 前缀补全
{
"settings": {
"analysis": {
"filter": {
"autocomplete_filter": {
"type": "edge_ngram",
"min_gram": 2,
"max_gram": 20
}
},
"analyzer": {
"autocomplete": {
"type": "custom",
"tokenizer": "keyword",
"filter": ["lowercase", "autocomplete_filter"]
}
}
}
},
"mappings": {
"properties": {
"suggest_name": {
"type": "text",
"analyzer": "autocomplete",
"search_analyzer": "keyword"
}
}
}
}
edge_ngram 从词头切出逐字前缀(如「Elastic」切成 E、El、Ela…),索引时用前缀全量,查询时用原词匹配,实现「输入即补全」。min_gram 设 2 可少存单字噪声,max_gram 决定最长的补全长度。字段必须区分 index 与 search analyzer,否则查询也走 ngram 会逐字匹配造成误召回。
2.2 ngram 子串匹配
{
"settings": {
"analysis": {
"filter": {
"substring_filter": { "type": "ngram", "min_gram": 2, "max_gram": 4 }
}
}
}
}
普通 ngram 切所有连续子串,能命中词中任意片段,代价是索引体积翻几倍。适合商品编码、邮箱、零件号这类需要子串检索的字段,不适合长正文。索引体积膨胀是 ngram 的固有成本,设计时先估字段长度分布再定 gram 范围。
2.3 前缀查询的替代
{
"query": {
"prefix": { "brand": "Nike" }
}
}
prefix 查询不做分析直接按词项前缀匹配,适合 keyword 字段的轻量前缀过滤。它对索引不做 ngram 预处理,性能随匹配面增大而退化,十万级词项以下可用,大规模补全场景让位给 edge_ngram 与 completion suggester。
3. completion suggester
一句话总结: completion suggester 用内存中的 FST 前缀树做亚毫秒级补全,是搜索框自动补全的标准方案。
3.1 字段与映射
{
"mappings": {
"properties": {
"suggest": {
"type": "completion",
"analyzer": "standard",
"contexts": [
{ "name": "category", "type": "category", "path": "category" }
]
}
}
}
}
completion 字段在内存中构建 FST(有限状态转换器)前缀树,查询只走内存结构,速度远快于倒排索引。contexts 给补全加过滤维度(如类目、地区),补全结果只返回匹配上下文的建议。
3.2 写入与补全查询
{
"index": { "_id": "p1" },
"suggest": {
"input": ["Nike Air Max", "Air Max 270"],
"weight": 80,
"contexts": { "category": ["shoes"] }
}
}
{
"suggest": {
"product-suggest": {
"prefix": "nike ai",
"completion": {
"field": "suggest",
"size": 5,
"contexts": { "category": ["shoes"] }
}
}
}
}
input 是补全源文本,weight 控制排序优先级(越热门的建议权重越高)。prefix 查询只需用户输入前缀,FST 树直接定位候选,补全响应通常在 10ms 内。weight 可由业务热度(点击量、销量)映射而来。
3.3 性能与内存
completion 字段全部驻留堆内存,索引越大内存越高,1GB 规模的补全词典约占数十到数百 MB 堆。写入后段内 FST 才可见,新数据有一定延迟;补全字段更新频繁时考虑独立补全索引或定期重建,避免写放大。FST 不支持子串补全,用户输入词中片段时补全失效,这种情况需要 edge_ngram 方案配合。
4. 模糊匹配与拼写纠错
一句话总结: 模糊匹配按编辑距离容忍错别字,让轻微拼写错误也能召回正确文档。
4.1 fuzzy 查询
{
"query": {
"fuzzy": {
"title": {
"value": "Elasticseach",
"fuzziness": "AUTO",
"prefix_length": 2,
"max_expansions": 50
}
}
}
}
fuzziness 定义允许的编辑距离:AUTO 按词长自动取值(短词容错低、长词容错高),也可直接写 1 或 2。prefix_length 要求前 N 个字符必须精确,显著缩小候选词项范围。max_expansions 限制展开词项数,防模糊查询扫太多倒排词项拖慢查询。
4.2 编辑距离与中文场景
Levenshtein 编辑距离:
fuzziness=1 允许 1 次插入/删除/替换
fuzziness=2 允许 2 次操作
AUTO 词长 0-2:0, 3-5:1, 6+:2
模糊匹配适合字母语言(英文、拼音)的拼写错误。中文错别字是「字形相近但字不同」,编辑距离模型并不匹配,中文纠错要靠分词后的拼音转换或专门的纠错词典,不能直接依赖 fuzzy。
4.3 fuzzy 与 match 的取舍
{
"query": {
"match": {
"title": {
"query": "Elasticseach 指南",
"fuzziness": "AUTO"
}
}
}
}
match 查询直接内嵌 fuzziness,比单独 fuzzy 更常用:既保留 BM25 打分又容忍拼写错误。模糊匹配本质是召回工具,会引入噪声,线上常把 fuzzy 结果降权处理或放在 should 分支作为兜底。
5. phrase suggester 与 did-you-mean
一句话总结: phrase suggester 基于候选短语的编辑距离与打分给出纠错建议,实现「你是不是想找」。
5.1 phrase 补全建议
{
"suggest": {
"did-you-mean": {
"text": "Elasticseach 教程",
"phrase": {
"field": "title",
"size": 3,
"gram_size": 1,
"direct_generator": {
"field": "title",
"suggest_mode": "missing"
},
"confidence": 0.5
}
}
}
}
phrase suggester 逐词生成候选修正并打分,text 是用户原输入,输出替换建议(如 Elasticseach → Elasticsearch)。confidence 是采纳阈值,低于该阈值的结果不返回。前端拿到建议后展示「您是不是要找 Elasticsearch 教程」,用户点击后重发正确查询。
5.2 term suggester 与 completion 的配合
term suggester 给出单词的候选替换词,适合只纠一个词的场景;completion suggester 侧重补全前缀而非纠错。三者用途分明:输入前缀 → completion,轻度错字 → fuzzy 兜底召回,整体纠错建议 → phrase suggester 展示 did-you-mean。
5.3 建议质量
建议质量取决于候选源:词典大、字段文本丰富的建议更准确。shingle filter 把相邻词组合成短语,phrase suggester 配合 shingle 才能生成整短语纠错。冷门词的候选少,建议常落空,用 highlight 或置信度门槛兜底,没把握时不展示建议比展示错误建议更好。
6. 索引时与查询时的取舍
一句话总结: 处理发生在索引时还是查询时,决定索引体积、查询延迟与变更成本。
6.1 取舍对比
| 能力 | 索引时 | 查询时 | 建议 |
|---|---|---|---|
| 同义词 | 词项展开、需重建 | 分析期展开、即时生效 | 默认查询时 |
| ngram/edge_ngram | 必须索引时 | 查询时不生效 | 索引时 |
| completion | 必须索引时(FST) | 不可 | 索引时 |
| fuzzy | 不可 | 查询时展开候选 | 查询时 |
edge_ngram 与 completion 天然是索引时能力,索引结构即检索结构;同义词与 fuzzy 可以放查询时,代价是查询分析变重。设计时把「结构性强、字典稳定」的部分放索引时,「语义规则变化快」的部分放查询时。
6.2 查询时膨胀控制
查询时展开(同义词、fuzzy、通配)都会让单条查询处理更多词项,QPS 高时被放大成写放大级别的负担。控制手段:同义词图收敛方向、fuzzy 加 prefix_length 与 max_expansions、通配查询限制前缀、必要时对热点查询做结果缓存。
6.3 多字段分工
{
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "ik_max_word"
},
"title.completion": {
"type": "completion",
"analyzer": "standard"
},
"title.keyword": {
"type": "keyword",
"ignore_above": 128
}
}
}
}
同一标题拆成三个角色:text 走全文检索,completion 子字段走补全,keyword 子字段走精确排序。索引体积增加但各角色各司其职,是工程上的常见取舍。
7. 综合搜索体验设计
一句话总结: 把补全、纠错、模糊召回组合成「输入即联想、错了也能找、给用户纠错建议」的完整体验。
7.1 三阶段交互
输入前缀 → completion 补全建议(亚毫秒)
回车检索 → match + fuzzy 兜底召回
零结果 → phrase suggester 给出 did-you-mean
搜索框输入时走 completion 返回联想;点击联想或回车走正式检索,match 为主、fuzzy 兜底;检索零结果时调用 phrase suggester 展示纠错建议。三个阶段互不干扰,各自独立接口,前端按状态切换。
7.2 建议与结果的联动
补全建议与检索结果可能不一致:用户点击补全项后,应把补全项的规范文本作为新查询条件,而不是用用户打到一半的输入。建议项排序用 weight(热度)而非字母序,才能让热门商品浮上来。纠错建议只在置信度足够时展示,避免「错误地纠正正确的输入」。
7.3 性能与可观测
补全接口要控制在毫秒级,建议做独立接口独立限流,防热点词刷爆查询。did-you-mean 只在零结果路径触发,QPS 天然低。建议在 Kibana 观察各阶段转化率:补全点击率、模糊召回率、纠错采纳率,用数据迭代权重与词典。
8. 总结
一句话总结: 高级文本检索用同义词扩展召回、edge_ngram 与 completion 做补全、fuzzy 兜底错字、phrase suggester 收口纠错,构成完整的搜索体验。
| 环节 | 要点 |
|---|---|
| 同义词 | 查询时展开、graph 处理多词、表收敛防膨胀 |
| ngram | edge_ngram 前缀补全,索引时切、查询时原词 |
| completion | FST 内存补全、weight 排序、context 过滤 |
| fuzzy | 编辑距离兜底召回,AUTO 与 prefix_length 控量 |
| did-you-mean | phrase suggester 生成纠错建议,置信度门槛 |
| 取舍 | 结构稳定放索引时,语义多变放查询时 |
| 体验 | 补全/检索/纠错三阶段联动 |
| 红线 | 中文错字不依赖 fuzzy,fuzzy 膨胀控 max_expansions |
高级文本检索的价值在把「能搜」升级成「好搜」:用户输入到一半就有联想,输错了也能找到,找不到还有纠错指引。设计顺序是先用 completion 与 edge_ngram 解决补全,再用 fuzzy 兜底召回,最后用 phrase suggester 收口纠错。分词与索引原理可阅读《倒排索引与分词原理》,查询打分参考《Query DSL 与相关性打分》,字段设计参考《数据建模与 Mapping 设计》。
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。