文档章节

ElasticSearch 重写IK分词器源码设置mysql热词更新词库

BakerZhu
 BakerZhu
发布于 2018/07/22 15:16
字数 1657
阅读 1K
收藏 15

常用热词词库的配置方式

1.采用IK 内置词库
优点:部署方便,不用额外指定其他词库位置
缺点:分词单一化,不能指定想分词的词条

2.IK 外置静态词库
优点:部署相对方便,可以通过编辑指定文件分词文件得到想要的词条
缺点:需要指定外部静态文件,每次需要手动编辑整个分词文件,然后放到指定的文件目录下,重启ES后才能生效

3.IK 远程词库
优点:通过指定一个静态文件代理服务器来设置IK分词的词库信息
缺点:需要手动编辑整个分词文件来进行词条的添加, IK源码中判断头信息Last-Modified  ETag 标识来判断是否更新,有时不生效

结合上面的优缺点,决定采用Mysql作为外置热词词库,定时更新热词 和 停用词。

准备工作

1.下载合适的ElasticSearch对应版本的IK分词器:https://github.com/medcl/elasticsearch-analysis-ik
2.我们来查看它config文件夹下的文件:
因为我本地安装的是ES是5.5.0版本,所以下载的IK为5.5.0的适配版
3.分析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">words_location</entry> -->
	<!--用户可以在这里配置远程扩展停止词字典-->
	<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>

ext_dict:对应的扩展热词词典的位置,多个热词文件之间使用分号来进行间隔
ext_stopwords:对应扩展停用词词典位置,多个之间用分号进行间隔
remote_ext_dict:远程扩展热词位置 如:https://xxx.xxx.xxx.xxx/ext_hot.txt
remote_ext_stopwords:远程扩展停用词位置 如:https://xxx.xxx.xxx.xxx/ext_stop.txt

4.除了config/ 文件夹中IKAnalyzer.cfg.xml 文件,我们开下config文件夹下其他文件的作用:
Dictionary中单例方法public static synchronized Dictionary initial(Configuration cfg)
 

private DictSegment _MainDict;

private DictSegment _SurnameDict;

private DictSegment _QuantifierDict;

private DictSegment _SuffixDict;

private DictSegment _PrepDict;

private DictSegment _StopWords;
...
public static synchronized Dictionary 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);
					}
				}
				
				return singleton;
			}
		}
	}
	return singleton;
}

initial中 load*中方法是利用config中其他文本文件来初始化Dictionary中的上面声明的成员变量:
_MainDict : 主词典对象,也是用来存储热词的对象
_SurnameDict : 姓氏词典
_QuantifierDict : 量词词典,例如1个中的 个 2两种的两
_SuffixDict : 后缀词典
_PrepDict : 副词/介词词典
_StopWords : 停用词词典

修改Dictionary源码

Dictionary类:更新词典 this.loadMySQLExtDict()

private void loadMySQLExtDict() {
	Connection conn = null;
	Statement stmt = null;
	ResultSet rs = null;
	try {
		Path file = PathUtils.get(getDictRoot(), "jdbc-loadext.properties");
		prop.load(new FileInputStream(file.toFile()));

		logger.info("jdbc-reload.properties");
		for(Object key : prop.keySet()) {
			logger.info(key + "=" + prop.getProperty(String.valueOf(key)));
		}

		logger.info("query hot dict from mysql, " + prop.getProperty("jdbc.reload.sql") + "......");

		conn = DriverManager.getConnection(
				prop.getProperty("jdbc.url"),
				prop.getProperty("jdbc.user"),
				prop.getProperty("jdbc.password"));
		stmt = conn.createStatement();
		rs = stmt.executeQuery(prop.getProperty("jdbc.reload.sql"));

		while(rs.next()) {
			String theWord = rs.getString("word");
			logger.info("hot 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);
			}
		}
	}
}

Dictionary类:更新停用词 this.loadMySQLStopwordDict()

private void loadMySQLStopwordDict() {
	Connection conn = null;
	Statement stmt = null;
	ResultSet rs = null;

	try {
		Path file = PathUtils.get(getDictRoot(), "jdbc-loadext.properties");
		prop.load(new FileInputStream(file.toFile()));

		logger.info("jdbc-reload.properties");
		for(Object key : prop.keySet()) {
			logger.info(key + "=" + prop.getProperty(String.valueOf(key)));
		}

		logger.info("query hot stopword dict from mysql, " + prop.getProperty("jdbc.reload.stopword.sql") + "......");

		conn = DriverManager.getConnection(
				prop.getProperty("jdbc.url"),
				prop.getProperty("jdbc.user"),
				prop.getProperty("jdbc.password"));
		stmt = conn.createStatement();
		rs = stmt.executeQuery(prop.getProperty("jdbc.reload.stopword.sql"));

		while(rs.next()) {
			String theWord = rs.getString("word");
			logger.info("hot stopword from mysql: " + theWord);
			_StopWords.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);
			}
		}
	}
}

对外暴露方法:

public void reLoadSQLDict() {
	this.loadMySQLExtDict();
	this.loadMySQLStopwordDict();
}

MySQLDictReloadThread Runnable实现类,去执行reLoadSQLDict() 加载热词:

import org.apache.logging.log4j.Logger;
import org.elasticsearch.common.logging.ESLoggerFactory;


/**
 * Created with IntelliJ IDEA.
 *
 * @author: zhubo
 * @description: 定时执行
 * @time: 2018年07月22日 13:05:24
 * @modifytime:
 */
public class MySQLDictReloadThread implements Runnable {

    private static final Logger logger = ESLoggerFactory.getLogger(MySQLDictReloadThread.class.getName());

    @Override
    public void run() {
        logger.info("reloading hot_word and stop_worddict from mysql");
        Dictionary.getSingleton().reLoadSQLDict();
    }
}

最后代码为定时调用:

其中一些细节就不讲述了。

jdbc-loadext.properties

jdbc.url=jdbc:mysql://xxx.xxx.xxx.xxx:3306/stop_word?useUnicode=true&characterEncoding=UTF-8&characterSetResults=UTF-8
jdbc.user=xxxxxx
jdbc.password=xxxxxxx
jdbc.reload.sql=select word from hot_words
jdbc.reload.stopword.sql=select stopword as word from hot_stopwords

文件放于此位置

打包

因为我们链接的是mysql数据库,所以maven项目要引入mysql驱动:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>6.0.6</version>
</dependency>

仅仅这样还不够,还需要修改plugin.xml文件(遇到了这个坑,修改pom好久新引入的依赖打包总打不进去):

准备完毕:执行打包。 mvn clean package

打包完毕。 上传,重启进行实验啦。^_^

实验结果

数据库插入记录

GET http://172.16.11.119:9200/g_index/_analyze?text=真是山炮&analyzer=ik_smart
{
    "tokens": [
        {
            "token": "真是山炮",
            "start_offset": 0,
            "end_offset": 4,
            "type": "CN_WORD",
            "position": 0
        }
    ]
}
GET http://172.16.11.119:9200/g_index/_analyze?text=大耳朵兔子&analyzer=ik_smart
{
    "tokens": [
        {
            "token": "大耳朵兔子",
            "start_offset": 0,
            "end_offset": 5,
            "type": "CN_WORD",
            "position": 0
        }
    ]
}
GET http://172.16.11.119:9200/g_index/_analyze?text=大耳朵兔子你真是山炮&analyzer=ik_smart
{
    "tokens": [
        {
            "token": "大耳朵兔子",
            "start_offset": 0,
            "end_offset": 5,
            "type": "CN_WORD",
            "position": 0
        },
        {
            "token": "你",
            "start_offset": 5,
            "end_offset": 6,
            "type": "CN_CHAR",
            "position": 1
        },
        {
            "token": "真是山炮",
            "start_offset": 6,
            "end_offset": 10,
            "type": "CN_WORD",
            "position": 2
        }
    ]
}
GET http://172.16.11.119:9200/g_index/_analyze?text=大耳朵兔子你真是山炮&analyzer=ik_max_word
{
    "tokens": [
        {
            "token": "大耳朵兔子",
            "start_offset": 0,
            "end_offset": 5,
            "type": "CN_WORD",
            "position": 0
        },
        {
            "token": "耳朵",
            "start_offset": 1,
            "end_offset": 3,
            "type": "CN_WORD",
            "position": 1
        },
        {
            "token": "耳",
            "start_offset": 1,
            "end_offset": 2,
            "type": "CN_WORD",
            "position": 2
        },
        {
            "token": "朵",
            "start_offset": 2,
            "end_offset": 3,
            "type": "CN_WORD",
            "position": 3
        },
        {
            "token": "兔子",
            "start_offset": 3,
            "end_offset": 5,
            "type": "CN_WORD",
            "position": 4
        },
        {
            "token": "兔",
            "start_offset": 3,
            "end_offset": 4,
            "type": "CN_WORD",
            "position": 5
        },
        {
            "token": "子",
            "start_offset": 4,
            "end_offset": 5,
            "type": "CN_CHAR",
            "position": 6
        },
        {
            "token": "你",
            "start_offset": 5,
            "end_offset": 6,
            "type": "CN_CHAR",
            "position": 7
        },
        {
            "token": "真是山炮",
            "start_offset": 6,
            "end_offset": 10,
            "type": "CN_WORD",
            "position": 8
        },
        {
            "token": "真是",
            "start_offset": 6,
            "end_offset": 8,
            "type": "CN_WORD",
            "position": 9
        },
        {
            "token": "山炮",
            "start_offset": 8,
            "end_offset": 10,
            "type": "CN_WORD",
            "position": 10
        },
        {
            "token": "炮",
            "start_offset": 9,
            "end_offset": 10,
            "type": "CN_WORD",
            "position": 11
        }
    ]
}

(⊙o⊙)… 我也不知道为什么会举出这种例子,算了就它吧。。。 山炮の

小弟比较笨中间遇到了一些坑,试了好几次才完成,^_^ , 有啥不明白的地方可以交流额

 

 

© 著作权归作者所有

BakerZhu
粉丝 109
博文 517
码字总数 423077
作品 0
通州
程序员
私信 提问
加载中

评论(2)

z
zz_80
博主,我按这个做的时候会出现java.sql.SQLNonTransientConnectionException: Could not create connection to database server.的情况。mysql是5.7的,es是6.4的,用6.0.3的驱动就报这个错误;用5.1.46的驱动就会报[2018-10-09T14:11:37,121][ERROR][o.e.b.ElasticsearchUncaughtExceptionHandler] [] fatal error in thread [elasticsearch[_D8Tp9M][generic][T#1]], exiting
java.lang.ExceptionInInitializerError: null
  at java.lang.Class.forName0(Native Method) ~[?:1.8.0_181]
  at java.lang.Class.forName(Class.java:264) ~[?:1.8.0_181]
腾讯云社区小编
腾讯云社区小编
【腾讯云+社区邀请您入驻】
您好~
我是腾讯云+社区的运营小编张艳晶,腾讯云+社区是由腾讯云全新打造的一个技术交流社区~我们已推出一个自媒体分享计划,资源包括流量推广、云服务器、域名、技术作者交流群、腾讯云周边礼物等。打开地址只需简单申请此计划即可。如果有什么问题的话可以加我微信详聊哦~
我的微信:15909794458
申请地址: https://cloud.tencent.com/developer/support-plan
Elasticsearch中文分词研究

一、ES分析器简介 ES是一个实时搜索与数据分析引擎,为了完成搜索功能,必须对原始数据进行分析、拆解,以建立索引,从而实现搜索功能; ES对数据分析、拆解过程如下: 首先,将一块文本分成...

zhaipengfei1231
2018/04/18
0
0
elasticsearch教程--中文分词器作用和使用

目录 概述 环境准备 认识中文分词器 常用的中文分词器 IK Analyzer hanlp中文分词器 彩蛋 概述 上一篇博文记录了elasticsearch插件安装和管理, 在地大物博的祖国使用es,不得不考虑中文分词器...

java_龙
2018/11/05
1.5K
0
Elasticsearch 中文分词器 IK 配置和使用

Elasticsearch 内置的分词器对中文不友好,会把中文分成单个字来进行全文检索,不能达到想要的结果 IK Analysis for Elasticsearch:https://github.com/medcl/elasticsearch-analysis-ik ik...

吴伟祥
2018/12/21
120
0
Elasticsearch实践(四):IK分词

环境:Elasticsearch 6.2.4 + Kibana 6.2.4 + ik 6.2.4 Elasticsearch默认也能对中文进行分词。 我们先来看看自带的中文分词效果: 结果: 我们发现,是按照每个字进行分词的。这种在实际应用...

飞鸿影
2018/12/06
0
0
ElasticSearch的ik分词插件开发

ik插件,说白了,就是通过封装ik分词器,与ElasticSearch对接,让ElasticSearch能够驱动该分词器。那么,具体怎么与ElasticSearch对接呢?从下往上走,总共3步: 一、封装IK分析器 与Elastic...

萧十一郎君
2014/05/26
3.5K
1

没有更多内容

加载失败,请刷新页面

加载更多

数据结构与算法之美_15_二分查找(上):如何用最省内存的方式实现快速查找功能?

今天学习一种针对有序数据集合的查找算法:二分查找(Binary Search)算法,也叫折半查找。 先看一道思考题,假设我们有 1000 万个整数数据,每个数据占 8 个字节,如何设计数据结构和算法,...

SP_K
34分钟前
24
0
Docker 记录

Docker Docker 分为社区版(Community Edition) 和企业版( Enterprice Edition) 一般使用CE,EE收费 查看linux内核版本: uname -r 查看centos 版本:cat /etc/redhat-release Centos 如果是最...

天空飘来五个字儿
43分钟前
80
0
网站变灰

平时到哀悼日的时候,我们看到很多知名网站都变成灰色了 只需要加下面的样式即可 html { filter: url(“data:image/svg+xml;utf8,<svg xmlns=\’http://www.w3.org/2000/svg\’><filter id=\...

三蹦子
49分钟前
123
0
网站首页蒙灰CSS样式

每次全国哀悼日,各大网站首页都变成了灰色,添加以下全局CSS样式,可以实现此效果: html,html *{  filter:gray!important;  filter:progid:DXImageTransform.Microsoft.BasicImage(...

达时索
56分钟前
133
0
1.NET Core 概述

.NET Core 概述 .NET Core是一个免费的、开源的、跨平台的、广泛使用的Web框架;它是由微软维护的。社区广泛参与支持的一个框架。.NET Core可以运行在:Windows、MacOS以及Linux操作系统上。...

osc_8j0twt2u
今天
60
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部