【热更新IK词典】ElasticSearch IK 自动热更新原理与实现

一、热更新原理

elasticsearch开启加载外部词典功功能后,会每60s间隔进行刷新字典。具体原理代码如下所示:

public void loadDic(HttpServletRequest req,HttpServletResponse response){
    String eTag =req.getParameter("If-None-Match");
    try {
        OutputStream out= response.getOutputStream();
        List<String> list=new ArrayList<String>();
        list.add("中华人民共和国");
        list.add("我爱你爱我");
        String oldEtag = list.size() + "";
        StringBuffer sb=new StringBuffer();
        if (oldEtag != eTag) {
            for (String str : list) {
                if(StringUtils.isNotBlank(str)){
                    sb.append("\r\n");
                }
                sb.append(str+"\r\n");
            }
        }
        String data=sb.toString();
 
        response.setHeader("Last-Modified", String.valueOf(list.size()));
        response.setHeader("ETag",String.valueOf(list.size()));
        response.setContentType("text/plain; charset=gbk");
        out.write(data.getBytes());
        out.flush();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

二、配置说明

 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">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,该请求只需满足以下两点即可完成分词热更新。
  该 http 请求需要返回两个头部(header)

  1. 一个是 Last-Modified,

  2. 一个是 ETag

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

  满足上面两点要求就可以实现热更新分词了,不需要重启 ES 实例。还需要注意的是获取词典的url,必须支持head访问

 

下面自己做的访问远程扩展词典的web api 服务接口(这一步可以直接忽略,看第三个即可,这里只是想说明也可以使用下面这种方式)

[HttpHead, HttpGet, HttpPost]
public async Task<HttpResponseMessage> GetDictionary(string path)
{
    var response = this.Request.CreateResponse(HttpStatusCode.OK);
    var content = File.ReadAllText(path);
    response.Content = new StringContent(content, Encoding.UTF8);
    response.Headers.Age = TimeSpan.FromHours(1);
    response.Headers.ETag = EntityTagHeaderValue.Parse($"\"{content.ToMD5()}\"");
    return response;
}

三、Nginx 服务器自动更新(也可以使用tomcat8,Server服务即可)

 1、部署http服务

 #gzip  on;
    server {
        listen       8222;
        server_name  localhost;
      #nginx中文件的存放路径,这里是根目录下/dist文件夹,扩展词典的.txt文件放进去即可,注意分词必须每行一次"/n" 分行 
        root   /dist;
        location / {
#test.txt为扩展词典
           index  index.html index.htm index.php test.txt;
     
            #autoindex  on;
        }
    }

可以通过浏览器访问ip:端口号/扩展词典.txt文件进行测试

如:localhost:8222/test.txt

2、修改ik插件的配置文件

修改 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">mydict.dic;single_word_low_freq.dic</entry>
     <!--用户可以在这里配置自己的扩展停止词字典-->
    <entry key="ext_stopwords">ext_stopword.dic</entry>
    <!--用户可以在这里配置远程扩展字典 -->
    <entry key="remote_ext_dict">http://localhost:8080/test.txt</entry>
    <!--用户可以在这里配置远程扩展停止词字典-->
    <entry key="remote_ext_stopwords">http://localhost:8080/test_stopwords.txt</entry>
</properties>

3、验证

重启es,会看到如下日志信息,说明远程的词典加载成功了。

IK分词器加载成功

远程词库加载完成

使用postmant测试:

代码实现

1.首先将di词典放在Nginx目录下

2.通过url读取配置文件

 /**
     * @Description 加载远程词典内容
     * @param menuId 菜单id
     * @param request http请求
     * @return String 词典内容*/
    @RequestMapping(value = "/loadDictionary", produces = "text/html;charset=UTF-8")
    @ResponseBody
    public String loadDictionary(Integer menuId, HttpServletRequest request) {
        String filePath = getFilePath(request);
        String context = readDictionary(filePath);
        return context;
    }
 /**
     * @Description 获取词典路径
     * @param request HTTP请求
     * @return String 词典路径*/
    private String getFilePath(HttpServletRequest request) {
        return "http://" + request.getServerName() + ":" + request.getServerPort() + "/" + "RemoteDictionary.dic";
    }
/**
     * @Description 读取远程词典
     * @param filePath 词典路径
     * @return String 词典内容*/
    private String readDictionary(String filePath) {
        StringBuffer document = new StringBuffer();
        try {
            URL url = new URL(filePath);
            URLConnection conn = url.openConnection();
            BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String line = null;
            while ((line = reader.readLine()) != null) {
                document.append(line + System.getProperty("line.separator"));
            }
            reader.close();
        } catch (MalformedURLException e) {
            System.out.println("Unable to connect to URL: " + filePath);
        } catch (IOException e) {
            System.out.println("IOException when connecting to URL: " + filePath);
        }
        return document.toString();
    }

3.将修改好的词典内容保存

/**
     * @Description 保存词典
     * @param menuId 菜单id
     * @param context 词典内容
     * @param request HTTP请求
     * @return boolean 是否成果能够*/
    @RequestMapping(value = "/saveDictionary")
    @ResponseBody
    public boolean saveDictionary(Integer menuId, String context, HttpServletRequest request) {     //获取tomcat的路径
        String filePath = System.getProperty("catalina.home")+"\\webapps\\ROOT\\RemoteDictionary.dic";
        boolean result = writeDictionary(filePath, context);
        return result;
    }
/**
     * @Description 上传词典
     * @param filePath 词典路径
     * @param content 修改的内容
     * @return boolean 是否上传成功*/
    private boolean writeDictionary(String filePath, String content) {
        try {
            File f = new File(filePath);
            if (!f.exists()) {
                return false;
            }
            OutputStreamWriter write = new OutputStreamWriter(new FileOutputStream(f), "UTF-8");
            BufferedWriter writer = new BufferedWriter(write);
            writer.write(content);
            writer.close();
            return true;
        } catch (Exception e) {
            return false;
        }
    }

*注:本文为转载并添加个人修改部分,如有转载请标明转载。原文作者如果看见,请随时联系,随时可撤销。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值