Appearance
中文分词和 IK 分词器
ES 做全文检索时,分词器非常重要。英文天然有空格,分词相对容易;中文没有天然空格,如果分词器不合适,经常会出现“明明有数据却搜不到”或者“搜出来一堆不相关结果”。
Analyzer 做了什么?
Analyzer 可以理解成文本进入倒排索引前的一条处理流水线。它通常包含:
- character filter:先处理原始字符,比如去 HTML 标签。
- tokenizer:把文本切成 token。
- token filter:对 token 做小写、停用词、同义词等处理。
写入文档时,text 字段会通过 analyzer 被切成 term,写进倒排索引。
查询 match 时,查询词也会通过 analyzer 被切成 term,再去匹配倒排索引。
所以,写入分词和查询分词要匹配,否则就会影响召回。
standard 分词器为什么不适合中文?
ES 默认 standard analyzer 对英文效果不错,但对中文经常不能满足业务预期。
比如:
http
GET /_analyze
{
"analyzer": "standard",
"text": "中华人民共和国国歌"
}不同版本下具体结果可能有差异,但它通常不能像中文搜索那样理解词语边界。
中文搜索更常用 IK、jieba、HanLP、smartcn 等分词方案。在国内业务里,IK 是很常见的选择。
IK 的两个常用分词器
IK 插件常用两个 analyzer:
ik_max_word:细粒度拆分,尽可能多地切出词,召回更高。ik_smart:粗粒度拆分,切出更少更长的词,结果更精确。
例如“中华人民共和国国歌”:
- ik_max_word 可能切出:中华人民共和国、中华人民、中华、人民、共和国、国歌等。
- ik_smart 可能切出:中华人民共和国、国歌。
常见设计是:
json
{
"title": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
}
}写入时用细粒度,提高召回;查询时用粗粒度,减少噪音。
安装 IK 要注意版本匹配
IK 插件版本要和 ES 版本匹配。比如 ES 是 7.17.x,就要选择对应的 IK 7.17.x 插件。
常见安装方式:
bash
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.17.0/elasticsearch-analysis-ik-7.17.0.zip安装完成后需要重启 ES 节点。集群环境中,每个需要处理该索引分片的节点都要安装同版本插件,否则分片可能无法正常分配或查询失败。
验证插件:
http
GET /_cat/plugins?v测试分词:
http
GET /_analyze
{
"analyzer": "ik_max_word",
"text": "金十期货"
}在 Mapping 里使用 IK
创建索引时指定 analyzer:
http
PUT /articles
{
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart",
"fields": {
"keyword": { "type": "keyword", "ignore_above": 256 }
}
},
"content": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
}
}
}
}如果索引已经创建,已有字段的 analyzer 通常不能直接改。需要新建索引并 reindex。
自定义词典
业务词对中文搜索特别重要。
比如“德玛西亚”“AIGC”“金十期货”这类词,如果默认词典不认识,就可能被拆得很碎,搜索效果变差。
IK 支持本地扩展词典。常见步骤:
- 在
plugins/ik/config下创建自定义词典文件,比如custom.dic。 - 每行写一个词。
- 在
IKAnalyzer.cfg.xml里配置扩展词典。 - 重启 ES。
词典内容示例:
text
德玛西亚
金十期货
Elasticsearch重启后用 _analyze 验证:
http
GET /_analyze
{
"analyzer": "ik_max_word",
"text": "金十期货发布消息"
}远程词典热更新
生产环境频繁重启 ES 不现实,所以 IK 也支持远程词典。
远程词典接口需要满足两个条件:
- HTTP 响应带
Last-Modified或ETag头。 - 响应内容一行一个词,使用换行分隔。
可以用 Nginx 托管一个 txt 文件,也可以由内部词库服务提供接口。
配置示意:
xml
<entry key="remote_ext_dict">http://dict.example.com/ik_ext.dic</entry>当 Last-Modified 或 ETag 变化时,IK 会重新拉取词典。这样新增词可以不重启 ES 生效。
词典更新后,历史数据会自动变化吗?
不会。
词典更新只影响之后的分词行为。已经写入索引的历史文档,它们的倒排索引还是旧分词结果。
如果希望历史数据也按新词典重新分词,有两种常见方式:
使用 _update_by_query 触发重新索引当前索引里的文档:
http
POST /articles/_update_by_query?conflicts=proceed或者创建新索引后 _reindex:
http
POST /_reindex
{
"source": { "index": "articles" },
"dest": { "index": "articles_v2" }
}如果 Mapping、分词器、字段结构都要调整,更推荐新建索引再 reindex,最后用别名切换。
搜索效果怎么调?
分词只是搜索效果的一部分。调搜索时可以按这个顺序看:
- 用
_analyze看标题、正文、查询词分别被切成什么。 - 确认字段是 text 还是 keyword。
- title、content 等字段是否需要不同 boost。
- 业务词有没有进入扩展词典。
- 是否需要同义词,比如“ES”和“Elasticsearch”。
- 是否需要 match_phrase 提高短语匹配权重。
- 是否需要 function_score 叠加时间、热度、标签权重。
例如让标题权重更高:
http
GET /articles/_search
{
"query": {
"multi_match": {
"query": "Elasticsearch 写入",
"fields": ["title^3", "content"]
}
}
}停用词和同义词
一些高频无意义词,比如“的”“了”“啊”,可以考虑停用词。但中文搜索里停用词要谨慎,过度过滤可能影响短句查询。
同义词也很常见,比如:
text
ES, Elasticsearch
数据库, DB
全文检索, 搜索同义词会影响召回和相关性,最好有明确的业务维护流程,不要随手堆词。词越多,误召回也可能越多。
小结
中文搜索的关键不是“装了 IK 就万事大吉”,而是要形成完整链路:
- Mapping 里正确声明 text 字段和 analyzer。
- 写入分词和查询分词要匹配。
- 业务词要进入扩展词典。
- 词典更新后,历史索引需要重新索引才会改变。
- 搜索效果要结合字段权重、短语匹配、同义词和业务排序一起调。
只要把分词结果看清楚,很多 ES 搜索问题就会从玄学变成工程问题。