ElasticSearch(三)-基于Mysql热更新IK词典

本文详细介绍了如何在ElasticSearch8.1.0环境中实现IK分词器的热更新,通过Mysql数据库存储扩展词典,避免了频繁重启ES服务器。讨论了全量更新与增量更新的选择,并提供了具体步骤,包括配置、代码修改和测试示例。
摘要由CSDN通过智能技术生成

前言

        眼前有景道不得,崔颢题诗在上头;(转载:https://blog.51cto.com/u_13270529/5962113)

在IK分词器中添加扩展词典或远程扩展词典,每次词典更新后都需要重启ES服务器,这在生产环境中是绝对不被允许的;如果我们把扩展词典数据存放在三方组件中如Redis、Mysql中ElasticSearch每隔一分钟去Redis或Mysql中同步最新的词典数据来更新词库,这样每次更新词库时不用重启ElasticSeach服务器就可以达到IK词典数据热更新;

热更新是全量更新还是增量更新?字典本身数据量并不大,充其量也就小几十万,一般也不会把所有的词语都放在词库中,而且数据库中的字典数据是为整个 ES 集群服务的,增量的话考虑的细节就会很多,同时在进行字典更新的时候是不会影响先有字典的使用的,综合来看全量更新更好。

一.环境参数

        ElasticSearch 8.1.0

        Mysql 8.0.28

二.热更新词典步骤

1.在Mysql中新建“扩展词典”,“扩展停止词典”数据表

# 扩展停用词典数据表
CREATE TABLE stop_words (word VARCHAR (200));
# 扩展词典数据表
CREATE TABLE ext_words (word VARCHAR (200));

2.下载对应ES版本的IK分词器源码包,并使用Idea打开项目

     下载地址: https://github.com/medcl/elasticsearch-analysis-ik/releases/tag/v8.1.0

3.pom.xml中添加如下依赖

# Mysql依赖用于ES重Mysql数据库中拉取词典数据
        <!-- 引入Mysql依赖 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version> 8.0.20</version>
        </dependency>

# Hutool核心依赖只使用其下的StrUtil.isNotBlank()这一个方法判断字符串是否为空串
        <!-- Hutool工具类 -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-core</artifactId>
            <version>5.6.3</version>
        </dependency>

4.修改org.wltea.analyzer.dic包中Dictionar类

        4.1.注释Dictionar类中的initial方法

        4.2.注释initial方法后加入如下代码

	/**
	 * 词典初始化 由于IK Analyzer的词典采用Dictionary类的静态方法进行词典初始化
	 * 只有当Dictionary类被实际调用时,才会开始载入词典, 这将延长首次分词操作的时间 该方法提供了一个在应用加载阶段就初始化字典的手段
	 *
	 * @return Dictionary
	 */
	public static synchronized void initial(Configuration cfg) {
		if (singleton == null) {
			synchronized (Dictionary.class) {
				if (singleton == null) {

					singleton = new Dictionary(cfg);
					singleton.loadMainDict();
					singleton.loadSurnameDict();
					singleton.loadQuantifierDict();
					singleton.loadSuffixDict();
					singleton.loadPrepDict();
					singleton.loadStopWordDict();

					if(cfg.isEnableRemoteDict()){
						// 建立监控线程
						for (String location : singleton.getRemoteExtDictionarys()) {
							// 10 秒是初始延迟可以修改的 60是间隔时间 单位秒
							pool.scheduleAtFixedRate(new Monitor(location), 10, 60, TimeUnit.SECONDS);
						}
						for (String location : singleton.getRemoteExtStopWordDictionarys()) {
							pool.scheduleAtFixedRate(new Monitor(location), 10, 60, TimeUnit.SECONDS);
						}
					}

					new Thread(()->{
						Properties pro = new Properties();
						try {
							// 读取Mysql配置文件,mysql.properties配置文件自行在IK文件夹下的config目录中新建
							pro.load(new FileInputStream(PathUtils.get(singleton.getDictRoot(), "mysql.properties").toFile()));
						} catch (IOException e) {
							e.printStackTrace();
						}

						while (true){
							try {
								TimeUnit.SECONDS.sleep(5);
								logger.info("开始从MySQL加载.....");
								// 读取配置文件中url、user、password用来创建Mysql连接
								try (Connection conn = DriverManager.getConnection(pro.getProperty("mysql.url"), pro.getProperty("mysql.user"), pro.getProperty("mysql.password"))) {
									// 重数据库中加载词典
									reLoadFromMySQL(conn, pro);
								}
								logger.info("从MySQL加载完成.....");
							}catch (Exception e){
								logger.error("load from mysql error..",e);
							}
						}
					}).start();
				}
			}
		}
	}

	// 加载连接驱动
	static {
		try {
			Class.forName("com.mysql.cj.jdbc.Driver");
		} catch (ClassNotFoundException ignored) {
		}
	}

	// 重Mysql中重新加载词典
	private static void reLoadFromMySQL(Connection conn, Properties pro) throws Exception {
		logger.info("从MySQL重新加载词典...start");
		// 新开一个实例加载词典,减少加载过程对当前词典使用的影响
		Dictionary tmpDict = new Dictionary(getSingleton().configuration);
		tmpDict.configuration = getSingleton().configuration;
		// IK分词器自身的加载远程词典方法
		tmpDict.loadMainDict();
		// 重Mysql中加载扩展词典
		tmpDict.reloadExtDictFromMySQL(conn);
		// IK分词器自身的加载远程停用词典方法
		tmpDict.loadStopWordDict();
		// 重Mysql中加载停用词典
		tmpDict.reloadStopDictFromMySQL(conn);
		getSingleton()._MainDict = tmpDict._MainDict;
		getSingleton()._StopWords = tmpDict._StopWords;
		logger.info("从MySQL重新加载词典完毕...end");
	}

	// 重Mysql中加载停用词典
	private void reloadStopDictFromMySQL(Connection conn) throws SQLException {
		try (Statement statement = conn.createStatement();ResultSet rs = statement.executeQuery("select word from stop_words")) {
			while(rs.next()) {
				String word = rs.getString("word");
				if (StrUtil.isNotBlank(word)){
					_StopWords.fillSegment(word.toCharArray());
				}
			}
		}
	}

	// 重Mysql中加载扩展词典
	private void reloadExtDictFromMySQL(Connection conn) throws SQLException {
		try (Statement statement = conn.createStatement(); ResultSet rs = statement.executeQuery("select word from ext_words")) {
			while(rs.next()) {
				String word = rs.getString("word");
				if (StrUtil.isNotBlank(word)){
					_MainDict.fillSegment(word.toCharArray());
				}
			}
		}
	}

5.使用Maven打包插件打工具类包

6.进入ElasticSearch下的IK分词器根目录

        6.1.删除原有elasticsearch-analysis-ik-8.1.0.jar

        6.2.添加上面打好的elasticsearch-analysis-ik-7.16.0.jar

        6.3.添加Mysql的连接驱动包

        6.4.添加Hutool-core依赖包

形如:

7.进入IK分词器根目录下的config目录中新建mysql.properties文件及socketPolicy.policy文件

配置内容:

mysql.properties:

mysql.url=jdbc:mysql://localhost:3306/ik?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false
mysql.user=root
mysql.password=123456

socketPolicy.policy:

grant {
   permission java.net.SocketPermission "*:*","accept,connect,resolve";
   permission java.lang.RuntimePermission "setContextClassLoader";
};

8.进入ES根目录下的config文件夹编辑jvm.options文件添加如下配置

# 解决ES控制台中文乱码
-Dfile.encoding=GBK

# socketPolicy.policy文件的绝对路径
-Djava.security.policy=D:\software\ElasticSearch8\elasticsearch-8.1.0\plugins\elasticsearch-analysis-ik-8.1.0\config\socketPolicy.policy

9.启动ElasticSearch

三.测试

# 1.使用IK分词器ik_max_word粒度对“弗雷尔卓德”进行分词测试
GET /_analyze
{
    "text":"弗雷尔卓德",
    "analyzer":"ik_max_word"
}


响应:
{
    "tokens": [
        {
            "token": "弗",
            "start_offset": 0,
            "end_offset": 1,
            "type": "CN_CHAR",
            "position": 0
        },
        {
            "token": "雷",
            "start_offset": 1,
            "end_offset": 2,
            "type": "CN_CHAR",
            "position": 1
        },
        {
            "token": "尔",
            "start_offset": 2,
            "end_offset": 3,
            "type": "CN_CHAR",
            "position": 2
        },
        {
            "token": "卓",
            "start_offset": 3,
            "end_offset": 4,
            "type": "CN_CHAR",
            "position": 3
        },
        {
            "token": "德",
            "start_offset": 4,
            "end_offset": 5,
            "type": "CN_CHAR",
            "position": 4
        }
    ]
}


# 2.在Mysql扩展词典表ext_words中添加“弗雷尔卓德”记录后再次测试分词效果(无需重启ES服务器)
GET /_analyze
{
    "text":"弗雷尔卓德",
    "analyzer":"ik_max_word"
}


响应:
{
    "tokens": [
        {
            "token": "弗雷尔卓德",
            "start_offset": 0,
            "end_offset": 5,
            "type": "CN_WORD",
            "position": 0
        }
    ]
}


# 3.在Mysql扩展停用词典表stop_words中添加“弗雷尔卓德”记录后再次测试分词效果(无需重启ES服务
GET /_analyze
{
    "text":"弗雷尔卓德",
    "analyzer":"ik_max_word"
}


响应:
{
    "tokens": []
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值