文章目录
问题背景
1.需求
最近有一些需求导致我们需要扩充IK的词库,按照IK的官方走了一遍,这里是官方的一些介绍
IK热更新配置方式
我们的ES版本为7.1.1
2. 使用tomcat提供一个接口
按照文档在tomcat服务中提供一个接口,来将自定义的热词数据写出去,结果在es中不能成功加载这些数据。
项目是一个springboot项目,接口的代码如下
@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");
String test = "上海堡垒\n陈默涵\n天际漫谈\n大西瓜\n人生如梦\n快手";
PrintWriter writer = response.getWriter();
writer.write(test);
writer.flush();
} catch (IOException e) {
log.error("自定义词典更新报错" , e);
}
}
在浏览器端和crul请求中返回的结果都比较正常,编码等都是正确设置了的。所以很是疑惑,如果使用es本地文件的方式添加热词是可以的。所以感觉应该是哪个环节有问题。
因为官方建议是使用nginx代理文件的方式,所以决定先用nginx来搞一下看看能不能正确使用,然后再进行对比。
3. 使用nginx代理文件
3.1 nginx 配置
cat server.conf
server {
listen 88;
charset utf-8;
location /analize/ {
alias /home/deploy/search/log-search/analize_word/;
access_log /data/nginx/log/analize_word.log api;
}
}
对应的文件为 /home/deploy/search/log-search/analize_word/test.txt
cat /home/deploy/search/log-search/analize_word/test.txt
钟丽
陈港生
3.2 ES配置
cat elasticsearch-7.1.1/config/analysis-ik/IKAnalyzer.cfg.xml
<?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"></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_stopwords">words_location</entry> -->
</properties>
重启ES
在修改完后等了一会儿可以在es的日志中看到
[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] 重新加载词典完毕...
说明确实成功了。
3.3 增加词汇不生效
接下来出现了比较诡异的东西。
当我再在文件中添加一个词的时候更新就无法成功了。
cat /home/deploy/search/log-search/analize_word/test.txt
钟丽
陈港生
陈默涵
对应的es的日志
[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:57:51,092][INFO ][o.w.a.d.Monitor ] [ES01] [Dict Loading] http://10.76.7.49:88/analize/test.txt
[2019-08-15T17:57:51,095][INFO ][o.w.a.d.Monitor ] [ES01] 重新加载词典完毕...
反复试了几次都是这样,感觉很奇怪。冷静下来分析,应该是对数据长度的识别有问题。又去翻了翻IK上的issue。最后找到了这条
顿时觉得和我现在的状态十分的吻合。
简单描述一下,就是nginx默认会对响应进行压缩gzip和分片chuncked
进行验证,当文件中只有两个词,返回的response header是
HTTP/1.1 200 OK
Server: openresty
Date: Thu, 15 Aug 2019 09:49:11 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 17
Last-Modified: Thu, 15 Aug 2019 09:29:05 GMT
Connection: keep-alive
ETag: "5d5525e1-11"
Accept-Ranges: bytes
这里可以看到有Content-Length 这个header
当编程三个词的时候response header变成了这样
HTTP/1.1 200 OK
Server: openresty
Date: Thu, 15 Aug 2019 10:53:39 GMT
Content-Type: text/plain; charset=utf-8
Last-Modified: Thu, 15 Aug 2019 09:57:15 GMT
Transfer-Encoding: chunked
Connection: keep-alive
Vary: Accept-Encoding
ETag: W/"5d552c7b-1b"
Content-Encoding: gzip
这里没有了Content-Length 这个header,
有了一个Transfer-Encoding:chunked 和一个Content-Encoding:gzip
这两个header是为了提升传输的效率和对内容进行压缩的。
IK插件可能是因为某种原因未对这中情况做支持,只有有Content-Length的时候才能正确的识别。
3.4 修改nginx配置进行验证
server {
listen 88;
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;
}
}
这个时候再从浏览器请求,观察返回的header
HTTP/1.1 200 OK
Server: openresty
Date: Fri, 16 Aug 2019 03:26:59 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 27
Last-Modified: Thu, 15 Aug 2019 09:57:15 GMT
Connection: keep-alive
ETag: "5d552c7b-1b"
Accept-Ranges: bytes
这个时候有了Conten-Length,当然,如果想要ES加载必须编辑一下这个文件,再增加一些东西。
修改文件
cat /home/deploy/search/log-search/analize_word/test.txt
钟丽
陈港生
陈默涵
流水人间
在ES的日志中等待一会儿,便可以看到
[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的基于文件的热词更新设置完成。
4.spring boot tomcat的设置
上面把nginx的调通也就找到了原因,知道原因了问题就好解决了。再调试原有的springboot代码,发现返回的header中果然没有Content-Length
研究一下tomcat的相关特性。
在tomcat当中,是使用一个respone buffer的缓存来存储即将发回的数据,如果这个buffer没有使用完,默认的情况下,tomcat使用的就是常规的方式,就是一次性返回,这个时候在response header当中是有Content-Length。如果这个buffer写满了而你还有数据要歇的时候,这个时候就先要进行一次会写,这个时候tomcat的响应就变成了chuncked的模式了。还有一种情况,如果显示的进行flush操作,就是response.gerWriter wirter.flush也会导致变成chuncked响应。
因此,在springboot tomcat项目中,需要将buffer size设置的更大(具体看自己的业务需求),同时不能显式的去调用flush操作。 这样就ok了。
对应的代码
@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);
}
}
至此,问题解决
这个其实算是IK插件的一个小小bug,估计后面就该修复了。
参考
https://imququ.com/post/transfer-encoding-header-in-http.html