ES -IK分词器分词、停用词基于API实现热更新

/ 默认分类 / 没有评论 / 1130浏览

官方介绍

ik的github有关于热更新的介绍

字典配置

IKAnalyzer.cfg.xml` can be located at `{conf}/analysis-ik/config/IKAnalyzer.cfg.xml` or `{plugins}/elasticsearch-analysis-ik-*/config/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">custom/mydict.dic;custom/single_word_low_freq.dic</entry>
	 <!--用户可以在这里配置自己的扩展停止词字典-->
	<entry key="ext_stopwords">custom/ext_stopword.dic</entry>
 	<!--用户可以在这里配置远程扩展字典 -->
	<entry key="remote_ext_dict">location</entry>
 	<!--用户可以在这里配置远程扩展停止词字典-->
	<entry key="remote_ext_stopwords">http://xxx.com/xxx.dic</entry>
</properties>

热更新 IK 分词使用方法

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

 	<!--用户可以在这里配置远程扩展字典 -->
	<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 即可。

说明

Last-Modified是上次更新时间 ETag是实体标签(Entity Tag)的缩写,根据实体内容(文本数据)生成的一段hash字符串,可以通过它的标识数据的修改状态,当数据发生改变时,ETag也随之改变

思路

分词文件可以存放在一个文本文件中,注意文件需要时UTF-8编码格式的。放在nginx或者其他简易http server下,当文件修改时,http server会在客户端请求该文件时自动返回相应的Last-Modified和ETag。

另外可以做一个工具来从业务系统提取相关词汇,再更新到这个文件中

如何设置ETag的值

首先ETag要满足以下三个条件: 1、当文件内容改变时,ETag值跟着改变 2、计算简单,不会特别消耗CPU(因此就不能使用MD5、SHA128、SHA256等算法) 3、必须支持横向扩展,也就是在不同的服务器节点上生成的ETag是一样的

参考Nginx中ETag的生成: 由Last-Modified和content_length表示为16进制组合而成

etag = '"' + Long.toHexString(lastModified) + '-' + Long.toHexString(contentLength) + '"';

有了Last-Modified为什么还需要ETag

1、原因就是因为某些服务器如果不能精确的获取到数据的最后修改时间的话,那么就无法通过Last-Modified来判断数据是否有更新了,就会导致词库更新不及时,出现延迟的问题。

2、Last-Modified一般只能精确到秒,如果数据更新的比较频繁的话,会导致识别不到更新,当然这点在IK分词器中不存在,因为IK分词器的热更新默认是一分钟获取一次。

3、如果我在一分钟内改了文件,发现改错了,又改回来了,那么这个时间虽然修改时间变了,但是因为内容没变,我是不希望更新它的。

综上所述,我们需要一个ETag来帮助我们判断是否更新了文本内容,同时ETag还需要根据文本内容来产生。

代码


@RequestMapping("updateHotWord")
    public void hotWord1(HttpServletResponse response, @RequestParam Integer type) throws IOException {
        List<String> dict = new ArrayList();
        if(type == 1){
            //TODO 直接读取数据库维护 热词
        }else{
            //TODO 直接读取数据库维护 停止词
        }
        String str = dict.parallelStream().collect(Collectors.joining("\n"));
        Long lastModified = Instant.now().toEpochMilli();
        String etag =   Long.toHexString(str.length());
        response.setHeader("Last-Modified",String.valueOf(lastModified));
        response.setHeader("ETag",etag);
        response.setContentType("text/plain;charset=utf-8");
        OutputStream out = response.getOutputStream();
        out.write(str.getBytes());
        out.flush();
    }

配置

修改ik分词器配置文件IKAnalyzer.cfg.xml

<properties>
        <comment>IK Analyzer 扩展配置</comment>
        <!--用户可以在这里配置自己的扩展字典 -->
        <entry key="ext_dict">custom/my_extend.dic</entry>
         <!--用户可以在这里配置自己的扩展停止词字典-->
        <entry key="ext_stopwords"></entry>
        <!--用户可以在这里配置远程扩展字典 -->
        <entry key="remote_ext_dict">http://localhost:8080/hot/updateHotWord?type=0</entry>
        <!--用户可以在这里配置远程扩展停止词字典-->
         <entry key="remote_ext_stopwords">http://localhost:8080/hot/updateHotWord?type=1</entry>
</properties> 

重启es 会发现启动时会显示读取的分词内容