文档章节

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

键走偏锋
 键走偏锋
发布于 07/22 15:16
字数 1657
阅读 79
收藏 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⊙)… 我也不知道为什么会举出这种例子,算了就它吧。。。 山炮の

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

 

 

© 著作权归作者所有

共有 人打赏支持
键走偏锋
粉丝 72
博文 546
码字总数 567979
作品 0
通州
程序员
加载中

评论(1)

腾讯云社区小编
腾讯云社区小编
【腾讯云+社区邀请您入驻】
您好~
我是腾讯云+社区的运营小编张艳晶,腾讯云+社区是由腾讯云全新打造的一个技术交流社区~我们已推出一个自媒体分享计划,资源包括流量推广、云服务器、域名、技术作者交流群、腾讯云周边礼物等。打开地址只需简单申请此计划即可。如果有什么问题的话可以加我微信详聊哦~
我的微信:15909794458
申请地址: https://cloud.tencent.com/developer/support-plan
Elasticsearch中文分词研究

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

zhaipengfei1231
04/18
0
0
ElasticSearch的ik分词插件开发

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

萧十一郎君
2014/05/26
0
1
Elasticsearch学习笔记4: 分词和中文分词

分析器: 分析器作用是将指定字符串处理成多个单个的词条,创建倒排索引。当索引数据时,分析器会将对应的数据处理分词后,创建倒排索引,当搜索时,将待搜索的字符串处理成词条,再去倒排索...

子夜闻雪
2017/11/08
0
0
Elasticsearch 安装和配置

安装 elasticsearch 上传 elasticsearch-1.7.3.tar.gz 到 Linux环境 解压缩至 /usr/local 目录,并启动 elasticsearch 启动 elasticsearch 浏览器访问 http://192.168.81.132:9200/ ,出现如下......

o135248
04/16
0
0
全文搜索引擎 ElasticSearch

ElasticSearch是一个基于Lucene的分布式多用户全文搜索引擎,使用Json索引,提供RESTful API,有着极高的实时搜索性能。 基础知识 ES主要功能 全文搜索引擎(倒排索引:根据词找到 位置&频次...

麦拂沙
2017/05/25
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

python3.6 取整除法

python3.6 中取整除法运算逻辑如下: d 非零,那么商 q 满足这样的关系: a = qd + r ,且0 ≤ r n1=7//3#7 = 3*2 +1n2=-6.1//3#-7 = 3*(-3)+2'{},{}'.format(n1,n2) 从运行结果可以...

colinux
27分钟前
3
0
阶段总结——用虚拟机搭建一个高可用负载均衡集群架构

[toc] linux基本知识已经介绍完,现有一个业务需要操作,通过对这个项目的操作,可以复习、总结、巩固之前的知识点; ** 用13台虚拟机搭建一个高可用负载均衡集群架构出来,并运行三个站点,...

feng-01
30分钟前
0
0
mysql 设置utf8字符集 (CentOS)

1.查看数据库及mysql应用目前使用的编码方式 (1)链接mysql 客户端 (2)执行:status 结果: 2.修改mysql 应用的字符编码(server characterset ) (1)打开配置文件:vim /etc/mysql/my...

qimh
31分钟前
0
0
windows无法格式化u盘解决方法

1。点开始-运行-输入cmd-format f: /fs: fat32 (这里f:是指U盘所在盘符) 这个格式化会很慢 请耐心等待

大灰狼wow
42分钟前
0
0
MySql 8.0连接失败

原来,MySql 8.0.11 换了新的身份验证插件(caching_sha2_password), 原来的身份验证插件为(mysql_native_password)。而客户端工具Navicat Premium12 中找不到新的身份验证插件(caching_s...

放飞E梦想O
59分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部