把「三体」「未来科技」这类专名写进自定义词典,重启容器后
_analyze切出来的结果和之前一模一样——词典没生效。IK 官方 README 的 FAQ 第一条就点破最常见原因:自定义词典文件必须 UTF-8 编码;除此之外还有三个高发点——IKAnalyzer.cfg.xml里ext_dict写的文件名和磁盘上文件名对不上、挂载路径没落进插件config目录、以及已有索引不会自动重新分词(自定义词只对新索引生效,老数据要重建索引才用得上)。逐个核对,把词典先验证成加载成功,再去想分词结果。
Issue & Env(案发现场与环境)
- 触发场景:参照教程给 IK 加自定义词典,目标是把词典里没有的复合专名锁成整词。照着步骤建了
mydict.dic、开了IKAnalyzer.cfg.xml的ext_dict、用-v挂载进容器并重启,结果_analyze返回的还是「未来」「科技」两个词,不是「未来科技」。 - 环境:OpenSearch
2.19.0+ analysis-ik2.19.0,Docker 单节点;词典文件直接挂载进/usr/share/opensearch/plugins/analysis-ik/config/。 - 前置声明:适用对象是「改词典文件后分词结果不变」的读者;如果你在集群(多个数据节点)上,还要确认每个节点都挂了同一份词典,节点间配置不一致也会表现成「时灵时不灵」。
- 关键区分:这个报错没有异常堆栈,服务正常跑、
_analyze正常返回,只是 token 跟你期望的不一样。「不报错 ≠ 生效」是排这个坑要时刻记住的。
StackTrace(堆栈日志)
没有 4xx、没有异常日志。唯一的「证据」是 _analyze 的输出不符合预期。改词典前:
{
"tokens" : [
{ "token" : "未来" },
{ "token" : "科技" }
]
}
改了词典、重启之后,返回还是这俩,一个不少:
{
"tokens" : [
{ "token" : "未来" },
{ "token" : "科技" }
]
}
如果节点启动时加载词典报错,docker logs 可能出现 Dictionary 加载相关的栈(比如 Dictionary.loadMySQLExtDict 一层),多数时候是文件路径或编码问题触发:
[ERROR] ... Dictionary.java:dictionary load ...
Reproduce(复现代码)
常见错法之一——文件没存成 UTF-8,或用编辑器保存成了 ANSI / UTF-8 with BOM:
mkdir -p ik-config
# Windows 记事本默认可能存成 ANSI,这不是 UTF-8
printf '未来科技\r\n平凡的世界\r\n' > ik-config/mydict.dic
cat > ik-config/IKAnalyzer.cfg.xml <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<entry key="ext_dict">mydict.dic</entry>
</properties>
EOF
docker run -d -p 9200:9200 -p 9600:9600 \
-e "discovery.type=single-node" \
-e "OPENSEARCH_INITIAL_ADMIN_PASSWORD=Devfix@2026" \
--name opensearch-ik \
-v "$(pwd)/ik-config/IKAnalyzer.cfg.xml:/usr/share/opensearch/plugins/analysis-ik/config/IKAnalyzer.cfg.xml:ro" \
-v "$(pwd)/ik-config/mydict.dic:/usr/share/opensearch/plugins/analysis-ik/config/mydict.dic:ro" \
opensearch-with-ik:2.19.0
curl -sk -u admin:Devfix@2026 -X POST "https://localhost:9200/_analyze?pretty" \
-H "Content-Type: application/json" \
-d '{ "analyzer": "ik_max_word", "text": "未来科技" }'
# 期望返回整词:{"token":"未来科技"},实际无变化则说明没生效
Root Cause(根因分析)
词典加载链路:cfg 引用 → 文件编码 → 文件路径
IK 在进程启动时读取 IKAnalyzer.cfg.xml,根据 ext_dict 指向的文件名去 config 目录下找文件并解析。链条上任何一环断了,词典就加载不到,但进程不崩溃——默认词典还能用,所以表现成「分词结果不变、也不报错」。这跟前面两篇「直接抛异常」的坑形成对照。
四个高发根因
- 文件不是 UTF-8:官方 README 的 FAQ 原文明确写了——「Please ensure that the text format of your custom dictionary is UTF8 encoded」。Windows 上随手用记事本存的
.dic,很可能是 ANSI/GBK 编码,IK 按 UTF-8 解析会读出乱码或解析失败,词就进不去。 - 文件名对不上:
ext_dict里写的mydict.dic,和磁盘上真实文件名必须完全一致,大小写敏感。写错了就静默找不到文件。 - 挂载路径不对:容器内的词典必须落在插件自己的
config目录(/usr/share/opensearch/plugins/analysis-ik/config/),IKAnalyzer.cfg.xml才能引到;挂到别处(比如只挂在容器根目录)插件读不到。 - 老索引不会重新分词:这是最容易被忽略的一条。IK 分析器在文档索引时定 token;索引早建好了,词典后来才加,已入库文档的 token 还是旧词典切出来的,不会自动更新。所以「改词典后,对存量数据搜不到预期结果」是正常的,得重建索引(reindex / 删了重建)。
多节点集群的额外因素
多数据节点集群里,词典要同步到每个节点并各自重启。节点间配置文件不一致,就会表现为「有时生效有时不生效」,看起来像玄学。
The Fix(解决方案)
第一步:确认文件是 UTF-8(不带 BOM)
file ik-config/mydict.dic
# 期望输出含 UTF-8 Unicode text;若出现 ISO-8859 / ASCII(非 UTF-8)要先转码
# 或在此判断有无 CR/BOM
xxd ik-config/mydict.dic | head
转成 UTF-8(去掉 BOM、统一换行):
iconv -f GBK -t UTF-8 ik-config/mydict.dic > ik-config/mydict.dic.utf8
mv ik-config/mydict.dic.utf8 ik-config/mydict.dic
第二步:核对 cfg 文件名和挂载路径
# cfg 里写的文件名
cat ik-config/IKAnalyzer.cfg.xml
# <entry key="ext_dict">mydict.dic</entry>
# 磁盘文件名必须一字不差、大小写一致
ls -la ik-config/mydict.dic
# 挂载目标是插件 config 目录,别挂到不知名的路径
docker inspect opensearch-ik --format '{{json .Mounts}}'
第三步:用猫命令先验证文件被带进容器
docker exec opensearch-ik cat /usr/share/opensearch/plugins/analysis-ik/config/mydict.dic
# 应原样打出:未来科技 / 平凡的世界 / 冰激凌
第四步:重启并确认词典加载
docker restart opensearch-ik
sleep 30
# 位移词典后,_analyze 应命中整词
curl -sk -u admin:Devfix@2026 -X POST "https://localhost:9200/_analyze?pretty" \
-H "Content-Type: application/json" \
-d '{ "analyzer": "ik_max_word", "text": "未来科技" }'
# 期望:{"tokens":[{"token":"未来科技"}]}
第五步:存量数据要重建索引
自定义词只对新索引的文档生效。要让新词典对已有数据生效,删掉重建或 reindex:
# 彻底重建:删掉旧索引
curl -sk -u admin:Devfix@2026 -X DELETE "https://localhost:9200/books_zh?pretty"
# 重走建索引 + bulk 导入,让文档用新词典重新切词
注意事项
- 改完词典必须重启生效(除非用 HTTP 热加载词典,那要另配
remote_ext_dict)。 - 同一份词典、多节点集群要同步到所有节点。
- 完整搭建流程,见从零跑通 OpenSearch 中文搜索。
