elasticsearch的ik分词器实现词库热更新的三种方案
elasticsearch的ik分词器实现词库热更新的三种方案
零、ik github官网地址
elasticsearch-analysis-ik 源码包 下载地址:
https://github.com/medcl/elasticsearch-analysis-ik
注:
- 后面内容为ik更新词库方法,elasticsearch安装ik插件不再赘述;
- 以下ik词库更新方法基于elasticsearch7.2.0
- 以下内容均为实现热词更新;停用词同理
一、ik本地文件读取方式
ik插件本来已为用户提供自定义词典扩展功能,只要修改配给文件即可:
1、添加自定义词典文件
在elasticsearch-7.2.0/plugins/ik/config目录下创建custom目录,并在目录内创建mydict.dic文件;
mydict.dic文件中添加自定义热词;
2、修改IKAnalyzer.cfg.xml配置文件
在elasticsearch-7.2.0/plugins/ik/config目录下修改IKAnalyzer.cfg.xml,修改内容如下:
<!--用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict">/custom/mydict.dic</entry>
二、ik远程词库调用
1、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),一个是
Last-Modified
,一个是ETag
,这两者都是字符串类型,只要有一个发生变化,该插件就会去抓取新的分词进而更新词库。 - 该 http 请求返回的内容格式是一行一个分词,换行符用
\n
即可。
满足上面两点要求就可以实现热更新分词了,不需要重启 ES 实例。
可以将需自动更新的热词放在一个 UTF-8 编码的 .txt 文件里,放在 nginx 或其他简易 http server 下,当 .txt 文件修改时,http server 会在客户端请求该文件时自动返回相应的 Last-Modified 和 ETag。可以另外做一个工具来从业务系统提取相关词汇,并更新这个 .txt 文件。
个人体会:nginx方式比较简单容易实现,建议使用;
2、在服务中实现http请求,并连接数据库实现热词管理实例:
1)、编写http请求服务接口demo
@RestController
@RequestMapping("/keyWord")
@Slf4j
public class KeyWordDict {
private String lastModified = new Date().toString();
private String etag = String.valueOf(System.currentTimeMillis());
@RequestMapping(value = "/hot", method = {RequestMethod.GET,RequestMethod.HEAD}, produces="text/html;charset=UTF-8")
public String getHotWordByOracle(HttpServletResponse response,Integer type){
response.setHeader("Last-Modified",lastModified);
response.setHeader("ETag",etag);
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
String sql = "";
final ArrayList<String> list = new ArrayList<String>();
StringBuilder words = new StringBuilder();
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
conn = DriverManager.getConnection(
"jdbc:oracle:thin:@192.168.114.13:1521:xe",
"test",
"test"
);
if(ObjectUtils.isEmpty(type)){
type = 99;
}
switch (type){
case 0:
sql = "select word from IK_HOT_WORD where type=0 and status=0";
break;
case 1:
sql = "select word from IK_HOT_WORD where type=1 and status=0";
break;
default:
sql = "select word from IK_HOT_WORD where type=99";
break;
}
stmt = conn.createStatement();
rs = stmt.executeQuery(sql);
while(rs.next()) {
String theWord = rs.getString("word");
System.out.println("hot word from mysql: " + theWord);
words.append(theWord);
words.append("\n");
}
return words.toString();
} catch (Exception e) {
e.printStackTrace();
} finally {
if(rs != null) {
try {
rs.close();
} catch (SQLException e) {
log.error("资源关闭异常:",e);
}
}
if(stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
log.error("资源关闭异常:",e);
}
}
if(conn != null) {
try {
conn.close();
} catch (SQLException e) {
log.error("资源关闭异常:",e);
}
}
}
return null;
}
@RequestMapping(value = "/update", method = RequestMethod.GET)
public void updateModified(){
lastModified = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date());
etag = String.valueOf(System.currentTimeMillis());
}
}
注:
- updateModified方法为单独更新lastModified与etag,用于判断ik是否需要重新加载远程词库,具体关联数据库操作代码时自行扩展
2)、ik配置文件修改
-
文件目录:/data/elasticsearch-7.2.0/plugins/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"></entry> <!--用户可以在这里配置自己的扩展停止词字典--> <entry key="ext_stopwords"></entry> <!--用户可以在这里配置远程扩展字典 --> <entry key="remote_ext_dict">http://192.168.xx.xx:8080/keyWord/hot?type=0</entry> <!--用户可以在这里配置远程扩展停止词字典--> <!-- <entry key="remote_ext_stopwords">words_location</entry> --> </properties>
三、重写ik源码连接mysql/oracle更新词库
1、下载 elasticsearch-analysis-ik 源码包
https://github.com/medcl/elasticsearch-analysis-ik
2、修改ik插件源码(以oracle为例,mysql对应修改配置即可)
1)、添加jdbc配置文件
在项目根目录下的config目录中添加config\jdbc-reload.properties配置文件:
jdbc.url=jdbc:oracle:thin:@192.168.xxx.xx:1521:xe
jdbc.user=test
jdbc.password=test
jdbc.reload.sql=select word from IK_HOT_WORD where type=0 and status=0
jdbc.reload.stop_word.sql=select word from IK_HOT_WORD where type=1 and status=0
period_time_seconds=60
2)、在Dictionary类中添加一下代码:
在Dictionary类中添加db驱动:
static {
try {
//驱动默认选择oracle
logger.info("初始化驱动开始..............");
Class.forName("oracle.jdbc.driver.OracleDriver");
logger.info("初始化驱动完成..............");
} catch (Exception e) {
logger.error("初始化驱动失败..............",e);
}
}
在Dictionary构造方法中添加db配置文件读取方法:
//添加db配置文件读取
try {
Path file = PathUtils.get(getDictRoot(), "jdbc-reload.properties");
dbProps.load(new FileInputStream(file.toFile()));
} catch (IOException e) {
logger.error("db配置文件读取失败", e);
}
在Dictionary类中添加创建数据连接方法:
private Connection getConn() {
try {
// 创建数据连接
conn = DriverManager.getConnection(
dbProps.getProperty("jdbc.url"),
dbProps.getProperty("jdbc.user"),
dbProps.getProperty("jdbc.password")
);
} catch (Exception e) {
logger.error("创建数据连接失败..............",e);
}
return conn;
}
在Dictionary类中添加从oracle加载热词热更新词典:
public void loadOracleHotDict() throws Exception {
Statement stmt = null;
ResultSet rs = null;
try {
logger.info("hot words loading");
conn = getConn();
String sql = dbProps.getProperty("jdbc.reload.sql");
if(sql != null && !"".equals(sql)){
stmt = conn.createStatement();
rs = stmt.executeQuery(sql);
while(rs.next()) {
String theWord = rs.getString("word");
logger.info("hot word from oracle: " + theWord);
_MainDict.fillSegment(theWord.trim().toCharArray());
}
}
logger.info("hot words load end");
} catch (Exception e) {
logger.error("热词更新异常:",e);
throw new Exception("热词更新异常:",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);
}
}
}
}
在Dictionary类中添加调用上面热词更新的方法:
public void reLoadSQLDict() throws Exception {
this.loadOracleHotDict();
this.loadMyOracleStopWordDict();
}
在dic目录下创建OracleDictReloadThread类:
public class OracleDictReloadThread implements Runnable {
private static final Logger logger = ESPluginLoggerFactory.getLogger(OracleDictReloadThread.class.getName());
@Override
public void run() {
logger.info("reloading hot_word and stop_word dict from oracle");
try {
Dictionary.getSingleton().reLoadSQLDict();
} catch (Exception e) {
logger.error("调用热词更新方法异常:",e);
}
}
}
在Dictionary类的initial初始化方法中添加数据库更新热词线程调用:
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);
}
}
//定时调用数据库查询热词
logger.info("查数据库更新线程启动--------------------");
pool.scheduleAtFixedRate(new OracleDictReloadThread(), 10, Integer.parseInt(dbProps.getProperty("period_time_seconds")), TimeUnit.SECONDS);
}
}
}
}
3)、添加数据maven库依赖
-
在pom.xml文件中添加数据库依赖
<!--<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency>--> <dependency> <groupId>com.oracle.ojdbc</groupId> <artifactId>ojdbc8</artifactId> <version>19.3.0.0</version> </dependency>
-
根据es版本修改ik对应版本
<elasticsearch.version>7.2.0</elasticsearch.version>
-
在src\main\assemblies\plugin.xml中添加配置使得数据库相关依赖一并打包
在中添加:
<!--<dependencySet> <outputDirectory/> <useProjectArtifact>true</useProjectArtifact> <useTransitiveFiltering>true</useTransitiveFiltering> <includes> <include>mysql:mysql-connector-java</include> </includes> </dependencySet>--> <dependencySet> <outputDirectory/> <useProjectArtifact>true</useProjectArtifact> <useTransitiveFiltering>true</useTransitiveFiltering> <includes> <include>com.oracle.ojdbc:ojdbc8</include> </includes> </dependencySet>
4)、maven打包项目
3、注意事项
如更新ik插件以后,出现报错如下:
java.security.AccessControlException: access denied (java.net.SocketPermission172.16.xxx.xxx:3306 connect,resolve)
这是jar的安全策略的错误(具体没有深究),解决方案如下:
1、在ik源码的config中创建文件socketPolicy.policy
grant {
permission java.net.SocketPermission "business.mysql.youboy.com:3306","connect,resolve";
};
注:如有其它相关错误,请自行添加
2、在服务器上的es中的config目录文件jvm.option添加如下代码配置上面的文件路径
-Djava.security.policy=/data/elasticsearch-6.5.3/plugins/ik/config/socketPolicy.policy
参考文章:
https://blog.csdn.net/weixin_43315211/article/details/99650363