最近要实现的一些功能需要让ES的同义词、扩展词、停止词能够热更新,达到让搜索更精确的目的。在网上看了很多相关的博客,现在热更新的方案已经实施成功,现在来总结一下。
ES版本:5.5.2
IK分词器版本:5.5.2
扩展词、停止词
我的ES使用的中文分词器是IK分词器,IK分词器支持一种热更新的方案,部署一个web服务器,提供一个http接口,通过modified和tag两个http响应头,来提供词语的热更新。
同义词
同义词的配置,Elasticsearch自带了一个synonym同义词插件,但是该插件只能使用文件或在分析器中静态地配置同义词,如果需要添加或修改,需要修改配置文件和重启,使用方式不够友好,我需要的是热更新。
基于以上的现有的方案,再加上我参考了两篇博客,决定采用这样的方案:
(1)修改ik分词器源码,然后手动支持从mysql中每隔一定时间,自动加载新的词库
(2)修改一款别人自研的一个可动态维护同义词的插件,也是同样的从mysql中每隔一定时间,自动加载新的词库
(3)在项目中对相应的文档用定时任务进行重建文档操作,因为热更新的词对旧文档无效。
附上两位的博客地址:
Elasticsearch之IK分词器热更新
一个简易的Elasticsearch动态同义词插件
万分感谢两位~
好了,下面进入正题
一、扩展词、停止词
1.下载IK分词器的源码
进入github,找到对应版本的ik分词器,下载源码,我这里是5.5.2版本的ES,所以我下载5.5.2版本的IK分词器
https://github.com/medcl/elasticsearch-analysis-ik/tree/v5.5.2
这是一个maven工程,下载下来后直接导入到eclipse中进行改造
2.修改源码
先看一下下载下来的源码的文件目录
这个-root后缀的项目就是我刚下载下来的ik的源码,这个源码上面的那个项目是我修改过后的源码。
由于我们是要动词库,所以我们直奔dic目录,找到Dictionary类,先看看它这个是怎么加载词典的。
看看他的构造函数:
initial方法:
根据上面原博主的思路,是在这个Dictionnary类中,写两个方法,用jdbc去mysql中分别查询扩展词和停止词,然后放到对应的词典中。然后再这个初始化的方法中,像原生的代码那样也调用一下这两个方法。再建立一个监控线程,定时去加载扩展词和停止词。
我在原博主的的基础上,把配置的数据库的属性初始化的时候封装到Dictionnary类中的一个属性中,包括加载的sql和监控线程隔多久进行一次扫描。这样方便进行配置。
(1)、先在Dictionnary类定义两个自己要的属性
DB_PROPERTIES属性是我们自己建的属性文件的文件名字
myProperties属性是用来把读取到的属性文件里的属性装起来
(2)、在config目录下创建db.properties文件
(3)、在构造方法中读取db.properties,并提供几个获取属性的方法
private Dictionary(Configuration cfg) {
this.configuration = cfg;
this.props = new Properties();
this.myProperties = new Properties();
this.conf_dir = cfg.getEnvironment().configFile().resolve(AnalysisIkPlugin.PLUGIN_NAME);
Path configFile = conf_dir.resolve(FILE_NAME);
Path myFile = cfg.getConfigInPluginDir().resolve(DB_PROPERTIES);
logger.info("加载属性文件db.properties的路径:" + myFile);
InputStream input = null;
InputStream myInput = null;
File file = myFile.toFile();
logger.info("file文件:" + file);
try {
myInput = new FileInputStream(file);
} catch (FileNotFoundException e1) {
logger.error("db.properties未找到", e1);
}
try {
logger.info("try load config from {}", configFile);
input = new FileInputStream(configFile.toFile());
} catch (FileNotFoundException e) {
conf_dir = cfg.getConfigInPluginDir();
configFile = conf_dir.resolve(FILE_NAME);
try {
logger.info("try load config from {}", configFile);
input = new FileInputStream(configFile.toFile());
} catch (FileNotFoundException ex) {
// We should report origin exception
logger.error("ik-analyzer", e);
}
}
if (input != null) {
try {
props.loadFromXML(input);
} catch (InvalidPropertiesFormatException e) {
logger.error("ik-analyzer", e);
} catch (IOException e) {
logger.error("ik-analyzer", e);
}
}
try {
myProperties.load(myInput);
} catch (IOException e) {
logger.error("加载db.properties文件失败!", e);
}
}
获取几个属性的方法
private String getUrl() {
String url = myProperties.getProperty("url");
return url;
}
private String getUser() {
String user = myProperties.getProperty("user");
return user;
}
private String getPassword() {
String password = myProperties.getProperty("password");
return password;
}
private int getInterval() {
Integer interval = Integer.valueOf(myProperties.getProperty("interval"));
return interval;
}
private String getExtWordSql() {
String extWordSql = myProperties.getProperty("extWordSql");
return extWordSql;
}
private String getStopWordSql() {
String stopWordSql = myProperties.getProperty("stopWordSql");
return stopWordSql;
}
(3)、写两个从数据库中读取扩展词和停止词的方法,再写一个重新加载扩展词和停止词的方法给监控线程去调用
private void loadMySQLExtDict() {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
logger.info("query ext dict from mysql, " + getUrl());
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection(getUrl(), getUser(), getPassword());
stmt = conn.createStatement();
String extWordSql = getExtWordSql();
if(!StringUtils.isNullOrEmpty(extWordSql)){
rs = stmt.executeQuery(extWordSql);
while (rs.next()) {
String theWord = rs.getString("main_keyword");
logger.info("main_keyword ext word from mysql: " + theWord);
_MainDict.fillSegment(theWord.trim().toCharArray());
}
}
} catch (Exception e) {
logger.error("erorr", e);
} finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
logger.error("error", e);
}
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
logger.error("error", e);
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
logger.error("error", e);
}
}
}
}
private void loadMySQLStopDict() {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
logger.info("query stop dict from mysql, " + getUrl());
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection(getUrl(), getUser(), getPassword());
stmt = conn.createStatement();
String stopWordSql = getStopWordSql();
if(!StringUtils.isNullOrEmpty(stopWordSql)){
rs = stmt.executeQuery(stopWordSql);
while (rs.next()) {
String theWord = rs.getString("main_keyword");
logger.info("main_keyword stop word from mysql: " + theWord);
_St