springboot基于Elasticsearch6.x版本进行ES同义词、停用词(停止词)插件配置,远程词典热加载及数据库词典热加载总结,es停用词热更新,es同义词热更新
前言:ES版本差异较大,建议跨版本的同学,可以先了解一下版本区别,建议不要跨版本使用插件或者进行项目调试。
本总结主要基于6.x版本的6.5.1(6.2.2实测可用),分词器为IK,下载地址:https://github.com/medcl/elasticsearch-analysis-ik
不做ES入门普及,直入正题。
ES操作系统:win10(如ES部署在linux,相应操作需调整)
正题
请先阅读官方热更新文档:
热更新IK分词使用方法
目前该插件支持热更新IK分词,通过上文在IK配置文件中提到的如下配置<!-用户可以在这里配置远程扩展字典-> <输入 密钥 = “ remote_ext_dict ” >位置</ entry ><!-用户可以在这里配置远程扩展停止字典-> <输入 密钥 = “ remote_ext_stopwords “ >位置</ entry >
其中location是指一个网址,其中http://yoursite.com/getCustomDict,该请求只需满足以下两点即可完成分词热更新。该http请求需要返回两个头部(header),一个是Last-Modified,一个是ETag,这两个都是字符串类型,只要有一个发生变化,
该插件就会去抓取新的分词套筒更新词库。该http请求返回的内容格式是一行一个分词,换行符用\n即可。满足上面两点要求就可以实现热更新分词了,不需要重启ES实例。可以将需自动更新的热词放在一个UTF-8编码的.txt文件里,放在nginx或其他简易http服务器下,当.txt文件修改时,
http服务器会在客户端请求该文件时自动返回相应的Last-Modified和ETag。可以另外做一个工具来从业务系统提取相关词汇,
并更新这个.txt文件。
注意:需要远程热加载,需要放开对应的远程访问安全策略
在java安装的jre/lib/security 下的java.policy文件内,
grant中添加一行
permission java.net.SocketPermission “*”, “connect,resolve”; 或者将*改为需要放开的host:port
非常重要,否则ES将无权限访问远程服务器
停用词(停止词)
由官方文档可知,对于停用词,我们可以直接在IK的配置文件ik/config/IKAnalyzer.cfg.xml配置
词典内容格式:
停用词远程词典文件热加载:
重启ES:这里为了看到ES加载停用词词典的效果,cmd默认编码为unicode,需要改为utf-8,
在ES/bin/ 内进入控制台,使用chcp 65001 进入utf-8界面,然后elasticsearch.bat启动ES。
成功启动ES后,ES会加载一次词典,并把词典内容展示
到此停用词热加载词典文件加载成功。
同义词远程词典热加载
需要安装同义词插件
地址:https://github.com/bells/elasticsearch-analysis-dynamic-synonym
但是现在正式的同义词插件是没有6.x版本的,所以我是通过修改5.x低版本的同义词插件到6.x的。
6.x版本同义词插件提供:
扫码或搜索‘程序员修炼宝典’关注公众号 回复:同义词插件
具体的修改方法:
https://blog.csdn.net/like_java_/article/details/107381018
下载完插件后,在ES/plugin下新建文件夹:dynamic-synonym,并将下载的插件解压到这个文件夹内,注意不要将插件压缩文件留在该文件夹内。
修改同义词插件的配置文件:plugin-descriptor.properties
elasticsearch.version=6.x 将版本修改为自己ES的对应版本,另外注释掉jvm site isolate 否则启动报错
Unknown properties in plugin descriptor: [jvm, site, isolated]
- description:插件的描述信息,用来描述该插件的作用
- version:插件的版本信息
- name:插件在elasticsearch plugin中显示的名称
- classname:插件的入口,需要实现Iplugin接口
- java.version:插件采用的java版本信息
- elasticsearch.version:插件发布到elasticsearch的那个特定版本上
- 可选属性(作用暂时未知)
- site:true表示发布为网站形式,_site目录下的内容将会起作用。
- jvm:true表示设置的classname对应的类将会被加载,对于依赖的资源,配置等信息也需要打包成jar
isolation:如果插件应该有自己的类加载器,则为真。传递false是不赞成的,它只用于支持插件相互之间有很强的依赖性。如果这是如果不指定,则默认隔离插件。
重启ES,成功启动则说明同义词插件已载入ES。
ES同义词热加载的实现:
同义词远程加载需要在创建索引时加入:
PUT /test_remote_index
{
"settings":{"index" : {"analysis" : {"analyzer" : {"synonym" : {"tokenizer" : "whitespace","filter" : ["remote_synonym"]}},"filter" : {"remote_synonym" : {"type" : "dynamic_synonym","synonyms_path" : "http://127.0.0.1:9090/synonyms.txt", //在这里改为你的远程同义词词典即可"interval": 30},"local_synonym" : {"type" : "dynamic_synonym","synonyms_path" : "synonym.txt"}}}}
}
}
创建完索引后,在该索引内所有的数据,均可通过同义词词典进行同义词查询增强。
同义词词典这里采用的是同级同义词:
加载远程词典进行ES同义词、停用词热更新的问题:
1.热更新时效问题:
阅读官方文档可知,ES的更新取决于获取远程词典的lastmodify和Etag;由于我的词典是放在tomcat服务器中的,tomcat服务器在远程调用响应中是没有ETag属性的,所以ES只能根据lastmodify来进行更新,然而lastmodify只精确到分钟,一旦在同一分钟内多次调整词典,ES是监控不到词典变化,影响词典的及时热更新。
2.词典动态配置问题
由于词典以文件的方式保存在服务器中,如果想要实现词典的修改,是非常麻烦的,所以实现远程词典的动态配置是必要的。
3.更新同义词、停用词ES历史数据未生效问题
在成功配置停用词、同义词后,使用中发现一个问题,在ES更新停用词后,之前已载入ES的数据,依然是按照旧停用词词典进行的分词。ES更新同义词词典后,之前已建好的索引内的数据,查询时仍然按照旧的同义词词典进行。因此,需要使ES历史数据动态更新。
问题解决方案:
1.热更新时效问题解决方案:
通过阅读官方文档,我们知道ES停用词远程热加载配置,有另一种方法即引入API。
通过API,响应ETag和lastModify即可满足ES及时更新。
<entry key="remote_ext_stopwords">http://host:port/goods-search/goodsSearchRuleController/getDictionary?path=文件路径</entry>
因此需要一个后端接口如下:
/*** ES调用,实现词典热更新API from File* Description: <br> * * @author author<br>* @taskId <br>* @param request* @param response* @return <br>*/@RequestMapping(value = "/getDictionary", method = {RequestMethod.HEAD, RequestMethod.POST, RequestMethod.GET})public String getDictionary(HttpServletRequest request, HttpServletResponse response) {DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");// es最后更新词库时间LocalDateTime esDictionaryLastUpdateTime = LocalDateTime.now();cacheUtil.setValue("searchrule", "esDictionaryLastUpdateTime", esDictionaryLastUpdateTime.format(dateTimeFormatter));logger.info("===================开始进行ES热更新 {}===============", esDictionaryLastUpdateTime);try {String path = request.getParameter("path");File file = new File(path);// 词典最后更新时间LocalDateTime fileLastModifiedTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(file.lastModified()), ZoneId.systemDefault());cacheUtil.setValue("searchrule", "fileLastModifiedTime", fileLastModifiedTime.format(dateTimeFormatter));InputStream fileInput = new FileInputStream(file);BufferedReader read = new BufferedReader(new InputStreamReader(fileInput, "utf-8"));StringBuffer buff = new StringBuffer();String str = null;while ((str = read.readLine()) != null) {buff.append(str + "\n");}String content = buff.toString().trim();read.close();fileInput.close();response.addHeader("ETag", MD5Util.encryption(content));response.addHeader("Last-Modified", file.lastModified() + "");return content;}catch (Exception e) {logger.error("ES实时热更新词典异常!", e.getMessage(), e);response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());return HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase();}}
lastmodify为词典文件的最后更新时间,ETag为文件内容的md5值。
2.词典动态配置问题解决方案
既然通过API可以实现ES热更新词典,响应也是通过response直接返回词典内容。
那为了实现词典的可动态配置。我们也可以直接将词典放入数据库,在这里从数据库读取也可以。
<entry key="remote_ext_stopwords">http://host:port/goods-search/goodsSearchRuleController/getDictionaryFromDB?analysisType=1</entry>
/*** ES调用,实现词典热更新API from DB* Description: <br> * * @author author<br>* @taskId <br>* @param request* @param response* @return <br>*/@RequestMapping(value = "/getDictionaryFromDB", method = {RequestMethod.HEAD, RequestMethod.POST, RequestMethod.GET})public String getDictionaryFromDB(HttpServletRequest request, HttpServletResponse response) {DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");// es最后更新词库时间 记录时间是为了解决ES历史数据更新的问题LocalDateTime esDictionaryLastUpdateTime = LocalDateTime.now();cacheUtil.setValue("searchrule", "esDictionaryLastUpdateTime", esDictionaryLastUpdateTime.format(dateTimeFormatter));String analysisType = request.getParameter("analysisType");//1表示停用词 2表示同义词if (analysisType == null || (!"1".equals(analysisType) && !"2".equals(analysisType))) {logger.error("参数不正确!");response.setStatus(HttpStatus.PRECONDITION_FAILED.value());return "analysisType incorrect";}logger.info("===================开始进行ES {}热更新 {}===============", analysisType, esDictionaryLastUpdateTime);try {FilterWordContentResponse contentResponse = searchRuleService.getDictionaryFromDB(analysisType);if (contentResponse == null) {logger.warn("数据库暂无可用词典!");response.addHeader("ETag", MD5Util.encryption(""));response.addHeader("Last-Modified", new Date(0) + "");return "";}response.addHeader("ETag", MD5Util.encryption(contentResponse.getContent()));response.addHeader("Last-Modified", contentResponse.getLastModify() + "");return contentResponse.getContent();}catch (Exception e) {logger.error("ES实时热更新词典异常!", e.getMessage(), e);response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());return HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase();}}@Overridepublic FilterWordContentResponse getDictionaryFromDB(String analysisType) {String content = null;Date lastModify = null;SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");FilterWordContentResponse contentResponse = new FilterWordContentResponse();if ("1".equals(analysisType)) {List<String> stopwords = searchFilterWordMapper.selectByTypeAndStatus();lastModify = searchFilterWordMapper.selectStopwordLastModify();content = analysisList(stopwords);if (content == null) {return null;}cacheUtil.setValue("searchrule", "stopwordLastModifiedTime", dateFormat.format(lastModify));}else if ("2".equals(analysisType)) {List<SynonymWordsResponse> synonymList = searchFilterWordMapper.selectSynonymWords();lastModify = searchFilterWordMapper.selectSynonymLastModify();content = analysisList(synonymList);if (content == null) {return null;}cacheUtil.setValue("searchrule", "synonymwordLastModifiedTime", dateFormat.format(lastModify));}contentResponse.setContent(content);contentResponse.setLastModify(lastModify);return contentResponse;}
Etag为读出数据库词典内容的md5值,lastmodify是数据库内词典的最新更新时间。
3.更新同义词、停用词ES历史数据未生效问题解决方案
ES自身带有一个更新历史索引的API,updateByQuery,可按正则更新目标索引。
http://127.0.0.1:9200/*/_update_by_query?conflicts=proceed
因此,我们可通过springboot调用该API实现ES热更新词典后历史数据的更新。
该更新历史数据暂时设置为定时更新,为了不造成ES和工程系统压力,我们只需ES在停用词、同义词词库有变动并成功更新后执行一次历史数据更新。
细心地同学可能注意到了,我在解决热更新时效问题的代码中记录的三个时间:
词库最后的更新时间,ES加载词库的更新时间,ES历史数据的更新时间
通过这三个时间确保updateByQuery在每次热加载后只执行一次。
代码如下:
/*** 更新索引历史数据,用于同义词及停用词典热更新后,ES历史数据的新规则同步* 未进行速率测试* Description: <br> * * @author <br>* @taskId <br>* @return <br>*/@PostMapping("/updateByQuery")public BssResult<?> updateByQuery(){try {LocalDateTime oldesLastUpdateByQueryTime = null;LocalDateTime wordLastModifiedTime = null;DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");String esLastUpdateByQueryTimeStr = cacheUtil.getValue("searchrule", "esLastUpdateByQueryTime");String stopwordLastModifiedTimeStr = cacheUtil.getValue("searchrule", "stopwordLastModifiedTime");String synonymwordLastModifiedTimeStr = cacheUtil.getValue("searchrule", "synonymwordLastModifiedTime");String esDictionaryLastUpdateTimeStr = cacheUtil.getValue("searchrule", "esDictionaryLastUpdateTime");if (esLastUpdateByQueryTimeStr == null) {logger.info("无更新ES历史数据记录! 需要进行更新~");}else {oldesLastUpdateByQueryTime = LocalDateTime.parse(esLastUpdateByQueryTimeStr,dateTimeFormatter);}if (stopwordLastModifiedTimeStr == null || synonymwordLastModifiedTimeStr == null) {return new BssResult<>(BusiCodeEnum.SYSTEM_ERROR, "文件最后修改时间为空!");}LocalDateTime stopwordLastModifiedTime = LocalDateTime.parse(stopwordLastModifiedTimeStr,dateTimeFormatter);LocalDateTime synonymwordLastModifiedTime = LocalDateTime.parse(synonymwordLastModifiedTimeStr,dateTimeFormatter);wordLastModifiedTime = stopwordLastModifiedTime.isBefore(synonymwordLastModifiedTime) ? synonymwordLastModifiedTime : stopwordLastModifiedTime;if (esDictionaryLastUpdateTimeStr == null) {return new BssResult<>(BusiCodeEnum.SYSTEM_ERROR, "ES最新加载词典时间为空!");}LocalDateTime esDictionaryLastUpdateTime = LocalDateTime.parse(esDictionaryLastUpdateTimeStr,dateTimeFormatter);if ((oldesLastUpdateByQueryTime == null || oldesLastUpdateByQueryTime.isBefore(wordLastModifiedTime))) {//表示未按照最新词典更新历史数据while (true) {if (esDictionaryLastUpdateTime.isAfter(wordLastModifiedTime)) { //es最后更新词库时间在文件最后修改后,说明最新词典已加载到es//最后更新es历史数据时间 历史数据只在ES更新词库后更新一次LocalDateTime esLastUpdateByQueryTime = LocalDateTime.now();cacheUtil.setValue("searchrule", "esLastUpdateByQueryTime", esLastUpdateByQueryTime.format(dateTimeFormatter));return searchRuleService.updateByQuery();}else {logger.warn("ES未加载最新词典,一秒后重试~");Thread.sleep(1000);}}}else {return new BssResult<>(BusiCodeEnum.SUCCESS, "无需更新!词典最后更新时间【"+wordLastModifiedTime+"】,ES最后更新词典时间【"+esDictionaryLastUpdateTime+"】,ES索引最后更新时间【"+oldesLastUpdateByQueryTime+"】");}} catch (UnknownHostException e) {logger.error("ES历史数据更新异常!", e.getMessage(), e);return new BssResult<>(BusiCodeEnum.SYSTEM_ERROR);}catch (Exception e) {logger.error("ES历史数据更新异常!", e.getMessage(), e);return new BssResult<>(BusiCodeEnum.SYSTEM_ERROR);}}@Overridepublic BssResult<?> updateByQuery() throws UnknownHostException {TransportClient client = searchStrategyUtil.getTransportClient();logger.info("连接es成功--------------------------");UpdateByQueryRequestBuilder updateByQuery = UpdateByQueryAction.INSTANCE.newRequestBuilder(client);updateByQuery.source("*").abortOnVersionConflict(false);BulkByScrollResponse response = updateByQuery.get();logger.info("ES历史数据更新成功----------------");return new BssResult<>(BusiCodeEnum.SUCCESS, response.getUpdated());}
这里需要注意的是,ES的版本区别,导致updateByQuery调用方式不同,这里采用的是tcp连接方式来执行的更新,跳槽tcp的端口注意一下与http是不同的。
/*** transportClient http方式连接* Description: <br> * * @author <br>* @taskId <br>* @return* @throws UnknownHostException <br>*/public TransportClient getTransportClient() throws UnknownHostException {Settings settings = Settings.builder().put("cluster.name", "es").put("client.transport.sniff", true) //自动翻译为节点树.put("client.transport.ping_timeout", "60s").put("client.transport.nodes_sampler_interval","20s").build();;try {/* TransportAddress master = new TransportAddress(InetAddress.getByName(host), httpPort);TransportClient client = new PreBuiltTransportClient(settings).addTransportAddress(master);//单机版 */TransportClient client = new PreBuiltTransportClient(settings).addTransportAddresses(new TransportAddress(InetAddress.getByName(localHost1), localHttpPort1), new TransportAddress(InetAddress.getByName(localHost2), localHttpPort2), new TransportAddress(InetAddress.getByName(localHost3), localHttpPort3));return client;}catch (UnknownHostException e) {throw new UnknownHostException();}}
如果有什么问题或优化建议,希望能留言,共同学习进步,谢阅!
springboot基于Elasticsearch6.x版本进行ES同义词、停用词(停止词)插件配置,远程词典热加载及数据库词典热加载总结,es停用词热更新,es同义词热更新相关推荐
- 全新SpringBoot整合Elasticsearch6.xxx搜索引擎实战
SpringBoot整合Elasticsearch6.xxx搜索引擎实战 一.导入相关依赖包 二.配置application.properties文件 三.ES存储实体类及注解 1.测试创建索引 四. ...
- Elasticsearch 同义词(dynamic-synonym插件)远程热词更新
文章目录 Elasticsearch 同义词(dynamic-synonym)远程热词更新 零.版本说明 一.同义词本地文件读取方式(可不用插件) 1.添加同义词文件 2.创建索引,并配置同义词过滤 ...
- 用python批量更新es数据根据id_Python Elasticsearch批量操作客户端
基于Python实现的Elasticsearch批量操作客户端 by:授客 QQ:1033553122 1.代码用途 Elasticsearch客户端,目的在于实现批量操作,如下: <1> ...
- ELK系列(十)、ES中文分词器IK插件安装和配置远程词库热加载
简介 IK Analyzer是一个开源的,基于Java语言开发的轻量级的中文分词工具包: 最初,它是以开源项目Luence 为应用主体的,结合词典分词和文法分析算法的中文分词组件:从 3.0 版本开始 ...
- ElasticSearch6.x版本的SpringBoot增删改查操作和ElasticSearch6.x版本的过滤查询
文章目录 一.教学讲解视频 二.SpringBoot操作ElasticSearch前期准备工作 三.SpringBoot增删改查ElasticSearch 1.新增修改 2.删除 3.查询 四.Ela ...
- native react 更新机制_react-native热更新全方位讲解
最近在研究热更新技术,看了网上各个大佬的博客,整体流程上总是卡壳.跳了几天坑,刚刚终于把简单的热更新流程跑通,现在也正在一边学习更新,一边整理资料,在此篇博客上记录操作流程,希望我的实践能帮助各位同行 ...
- 【热更新】游戏热更新方案
游戏热更新方案 热更新演化 热更新方案 [1] 进程切换 1.1 利用fork.exec切换 1.2 利用网关切换 1.3 微服务 - 进程切换注意要点 [2] 动态库替换 [3] 脚本语言热更新 热 ...
- ik分词器的热词更新_ik与拼音分词器,拓展热词/停止词库
说明:本篇文章讲述elasticsearch分词器插件的安装,热词库停止词库的拓展,文章后面提到elasticsearch ,都是以es简称. 以下分词器的安装以ik分词器和pinyin分词器为例说明 ...
- uniapp 热更新和整包更新
uniapp 热更新和整包更新 版本校验接口返回 自动更新 自动下载APK并安装 弹出下载APK手动安装 参考资料 版本校验接口返回 https://192.168.1.113/public/mobi ...
最新文章
- mysql 查询数据 程序_MySQL 查询数据
- vue cli 4 多环境_Vue 前端uni-app多环境配置部署服务器的问题
- smem – Linux 内存监视软件
- Linux 如何取进程运行时间,linux -- 获取进程执行时间
- 大数据在金融领域的应用及问题时
- 班扎古鲁白玛的沉默(见与不见)
- dockerfile COPY
- [转载] python __import__ 搜索路径详解
- 11.7动手动脑作业
- Atitit 数据库结果集映射 ResultSetHandler 目录 1. 常见的四种配置ResultSetHandler	1 2. Dbutil	1 3. Mybatis 致敬	3 4. H
- “鬼影”浅析 - 反病毒,信息安全,网络安全,反木马,病毒资讯平台,安全解决方案,电脑使用技巧,杀毒软件交流,anti-virus,民间反病毒联盟
- 自媒体原创检测工具,了解了这个离收获大量粉丝不会远啦!
- 当天剩余时间,当月剩余时间(秒数),用于redis设置过期时间
- 回调函数,监听函数 关系 个人学习理解
- 值得一生珍藏的经典台词
- 华为云数据库助力微鲤科技智能升级
- cs231n学习笔记——图像分类
- IDEA:IDEA中文翻译插件的安装与使用
- python期末试卷 答案_《python》期末考试卷A及答案
- 使用 Enigma Virtual Box 打包单文件