官方热更新 IK 分词使用方法

目前该插件支持热更新 IK 分词,通过上文在 IK 配置文件中提到的如下配置

1
2
3
4
	<!--用户可以在这里配置远程扩展字典 -->
<entry key="remote_ext_dict">location</entry>
<!--用户可以在这里配置远程扩展停止词字典-->
<entry key="remote_ext_stopwords">location</entry>

其中 location 是指一个 url,比如 http://yoursite.com/getCustomDict,该请求只需满足以下两点即可完成分词热更新。

  1. 该 http 请求需要返回两个头部(header),一个是 Last-Modified,一个是 ETag,这两者都是字符串类型,只要有一个发生变化,该插件就会去抓取新的分词进而更新词库。
  2. 该 http 请求返回的内容格式是一行一个分词,换行符用 \n 即可。

满足上面两点要求就可以实现热更新分词了,不需要重启 ES 实例。

可以将需自动更新的热词放在一个 UTF-8 编码的 .txt 文件里,放在 nginx 或其他简易 http server 下,当 .txt 文件修改时,http server 会在客户端请求该文件时自动返回相应的 Last-Modified 和 ETag。可以另外做一个工具来从业务系统提取相关词汇,并更新这个 .txt 文件。

ES自定义字典表配置

cat elasticsearch-7.1.1/config/analysis-ik/IKAnalyzer.cfg.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict">xxx.dic</entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords"></entry>
<!--用户可以在这里配置远程扩展字典 -->
<!--<entry key="remote_ext_dict">http://10.26.5.18:8090/search/manager/es/remoteExtDict/select</entry> -->
<entry key="remote_ext_dict">http://10.26.5.18/analize/xxx.dic</entry>
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>

nginx实现热更新

nginx配置

1
2
3
4
5
6
7
8
9
10
server {
listen 80;
charset utf-8;
location /analize/ {
alias /home/deploy/search/log-search/analize_word/;
access_log /data/nginx/log/analize_word.log api;
chunked_transfer_encoding off;
gzip off;
}
}

重启ES
在修改完后等了一会儿可以在es的日志中看到

1
2
3
4
5
6
[2019-08-15T17:57:51,001][INFO ][o.w.a.d.Monitor          ] [ES01] 重新加载词典...
[2019-08-15T17:57:51,002][INFO ][o.w.a.d.Monitor ] [ES01] try load config from /home/deploy/search/log-search/elasticsearch-7.1.1/config/analysis-ik/IKAnalyzer.cfg.xml
[2019-08-15T17:29:51,083][INFO ][o.w.a.d.Monitor ] [ES01] [Dict Loading] http://10.76.7.49:88/analize/test.txt
[2019-08-15T17:29:51,086][INFO ][o.w.a.d.Monitor ] [ES01] 钟丽
[2019-08-15T17:29:51,086][INFO ][o.w.a.d.Monitor ] [ES01] 陈港生
[2019-08-15T17:29:51,086][INFO ][o.w.a.d.Monitor ] [ES01] 重新加载词典完毕...

说明确实成功了。

ES重新加载字典表

修改文件

cat /home/deploy/search/log-search/analize_word/test.txt

1
2
3
4
钟丽
陈港生
陈默涵
流水人间

在ES的日志中等待一会儿,便可以看到

1
2
3
4
5
6
7
8
[2019-08-16T11:37:51,001][INFO ][o.w.a.d.Monitor          ] [ES01] 重新加载词典...
[2019-08-16T11:37:51,020][INFO ][o.w.a.d.Monitor ] [ES01] try load config from /home/deploy/search/log-search/elasticsearch-7.1.1/config/analysis-ik/IKAnalyzer.cfg.xml
[2019-08-16T11:37:51,105][INFO ][o.w.a.d.Monitor ] [ES01] [Dict Loading] http://10.76.7.49:88/analize/test.txt
[2019-08-16T11:37:51,108][INFO ][o.w.a.d.Monitor ] [ES01] 钟丽
[2019-08-16T11:37:51,108][INFO ][o.w.a.d.Monitor ] [ES01] 陈港生
[2019-08-16T11:37:51,108][INFO ][o.w.a.d.Monitor ] [ES01] 陈默涵
[2019-08-16T11:37:51,108][INFO ][o.w.a.d.Monitor ] [ES01] 流水人间
[2019-08-16T11:37:51,108][INFO ][o.w.a.d.Monitor ] [ES01] 重新加载词典完毕...

ok至此,nginx的基于文件的热词更新设置完成。

结合spring代码实现热更新

在tomcat当中,是使用一个respone buffer的缓存来存储即将发回的数据,如果这个buffer没有使用完,默认的情况下,tomcat使用的就是常规的方式,就是一次性返回,这个时候在response header当中是有Content-Length。如果这个buffer写满了而你还有数据要歇的时候,这个时候就先要进行一次会写,这个时候tomcat的响应就变成了chuncked的模式了。还有一种情况,如果显示的进行flush操作,就是response.gerWriter wirter.flush也会导致变成chuncked响应。
因此,在springboot tomcat项目中,需要将buffer size设置的更大(具体看自己的业务需求),同时不能显式的去调用flush操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@RequestMapping(value = "/remoteExtDict/select", method = RequestMethod.GET)
public void select(HttpServletResponse response) {
List<String> resultList = remoteExtDictMapper.selectRemoteExtDict();
StringBuilder result = new StringBuilder();
for (String value : resultList) {
result.append(value + "\n");
}
result.delete(result.length() - 1, result.length());
try {
Long time = remoteExtDictMapper.getMaxTime();
response.setHeader("Last-Modified", time.toString());
response.setHeader("ETag", time.toString());
response.setContentType("text/plain; charset=utf-8");
response.setBufferSize(500000);

String test = "上海堡垒\n陈默涵\n天际漫谈\n大西瓜\n人生如梦\n快手";
PrintWriter writer = response.getWriter();
writer.write(test);
} catch (IOException e) {
log.error("自定义词典更新报错", e);
}
}