为key_words提供更加完整的倒排索引。
如:时态转化(like | liked),单复数转化(man | men),全写简写(china | cn),同义词(small | little)等。
如:china 搜索时,如果条件为cn是否可搜索到。
如:dogs,搜索时,条件为dog是否可搜索到数据。
如果可以使用简写(cn)或者单复数(dog&dogs)搜索到想要的结果,那么称为搜索引擎normalization人性化。
normalization是为了提升召回率的(recall),就是提升搜索能力的。
normalization是配合分词器(analyzer)完成其功能的。
分词器的功能就是处理Document中的field的。就是创建倒排索引过程中用于切分field数据的。
如:I think dogs is human’s best friend.在创建倒排索引的时候,使用分词器实现数据的切分。
上述的语句切分成若干的词条,分别是: think dog human best friend。
常见搜索条件有:think、 human、 best、 friend,很少使用is、a、the、i这些数据作为搜索条件。
1 ES默认提供的常见分词器
要切分的语句:Set the shape to semi-transparent by calling set_trans(5)
standard analyzer - 是ES中的默认分词器。标准分词器,处理英语语法的分词器。
切分后的key_words:set, the, shape, to, semi, transparent, by, calling, set_trans, 5。
这种分词器也是ES中默认的分词器。切分过程中会忽略停止词等(如:the、a、an等)。
会进行单词的大小写转换。过滤连接符(-)或括号等常见符号。
simple analyzer - 简单分词器。切分后的key_words:set, the, shape, to, semi, transparent, by, calling, set, trans。
就是将数据切分成一个个的单词。使用较少,经常会破坏英语语法。
whitespace analyzer - 空白符分词器。切分后的key_words:Set, the, shape, to, semi-transparent, by, calling, set_trans(5)。就是根据空白符号切分数据。如:空格、制表符等。使用较少,经常会破坏英语语法。
language analyzer - 语言分词器,如英语分词器(english)等。
切分后的key_words:set, shape, semi, transpar, call, set_tran, 5。
根据英语语法分词,会忽略停止词、转换大小写、单复数转换、时态转换等,应用分词器分词功能类似standard analyzer。
注意:搜索条件中的key_words也需要经过分词,且搜索条件中的条件数据使用的分词器与对应的字段使用的分词器是统一的。否则会导致搜索结果丢失。搜索条件的分词结果和测试的分词结果完全一致。
ES中提供的所有的英语相关的分词器,对中文的分词都是一字一词。
2 安装中文分词器
2.1 下载&打包IK分词器
使用git下载IK分词器,并使用maven实现打包。
git clone https://github.com/medcl/elasticsearch-analysis-ik.git
git checkout tags/v6.3.0
mvn clean
mvn compile
mvn package
打包后的资源在target/releases目录中,资源为zip压缩包。
IK也需要去链接ES,ES需要访问IK分词器。 IK在链接ES的时候,是需要使用ES提供的java版客户端api的。IK提供的源码中,是一个maven工程,pom.xml配置文件中依赖的ES客户端jar,使用的是6.3.0 。本地安装的什么版本ES,就修改为对应的ES版本即可。
IK默认提供的pom.xml文件中依赖的是elasticsearch6.3.0的客户端,需要手工修改pom.xml中的依赖。将6.3.0修改为6.3.1。github中提供的IK支持6.x相关版本的ES。
2.2 安装IK分词器
ES是一个开箱即用的工具。插件安装方式也非常简单。
将打包后的zip文件复制,并在ES安装目录的plugins目录中手工创建子目录,目录命名为ik。将zip解压缩到新建目录ik中。重新启动ES即可。
所有的分词器,都是针对词语的,不是语句的。拆分单元是词语,不是语句。
2.3 测试IK分词器
IK分词器提供了两种analyzer,分别是ik_max_word和ik_smart。
ik_max_word: 会将文本做最细粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,中华人民,中华,华人,人民共和国,人民,人,民,共和国,共和,国,国歌”,会穷尽各种可能的组合;
ik_smart: 会做最粗粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,国歌”。
上述内容引用自github官方数据。
GET _analyze
{
"text" : "中华人民共和国国歌",
"analyzer": "ik_max_word"
}
GET _analyze
{
"text" : "中华人民共和国国歌",
"analyzer": "ik_smart"
}
2.4 IK配置文件
IK的打包后,根目录中有配置文件:通常来说不需要关注下述两个配置文件。
plugin-security.policy: 是用于在JRE环境中授权的配置文件,相当于是$JAVA_HOME/jre/lib/security/java.policy。后期使用源代码修改实现热词更新的时候,很可能需要修改JDK中的java.policy文件。
plugin-descriptor.properties: 是ES启动的时候,加载IK插件时,IK插件上下文配置信息解析的配置文件。用于配置IK上下文信息,如:IK所在的ES版本是什么,IK所在的JDK环境版本是什么等。
IK的配置文件在ES安装目录/plugins/ik/config/中。
配置文件有:
main.dic : IK中内置的词典。 main dictionary。记录了IK统计的所有中文单词。一行一次。文件中未记录的单词,IK无法实现有效分词。如:蓝瘦香菇。不建议修改当前文件中的单词。这个是最核心的中文单词库。就好像,很多的网络词不会收集到辞海中一样。
quantifier.dic : IK内置的数据单位词典
suffix.dic :IK内置的后缀词典
surname.dic :IK内置的姓氏词典
stopword.dic :IK内置的英文停用词
preposition.dic :IK内置的中文停用词(介词)
IKAnalyzer.cfg.xml : 用于配置自定义词库的
自定义词库是用户手工提供的特殊词典,类似网络热词,特定业务用词等。
ext_dict - 自定义词库,配置方式为相对于IKAnalyzer.cfg.xml文件所在位置的相对路径寻址方式。相当于是用户自定义的一个main.dic文件。是对main.dic文件的扩展。不是热更新,是固定的扩展。
ext_stopwords - 自定义停用词,配置方式为相对于IKAnalyzer.cfg.xml文件所在位置的相对路径寻址方式。相当于是preposition.dic的扩展。
IK的所有的dic词库文件,必须使用UTF-8字符集。不建议使
用windows自带的文本编辑器编辑。Windows中自带的文本编辑器是使用GBK字符集。IK不识别,是乱码。
2.5 IK热词更新
如果使用本地自定义词库文件的形式定义最新词条,那么想让最新热词词条生效则需要重启ES。这样对系统的效率有很大的影响。
IK支持远程词库加载,可以通过配置或修改IK源代码的方式实现远程词库加载。
IK提供了HTTP协议形式的远程词库加载方式,不推荐使用,毕竟会对效率有一定的影响。
我们这里使用修改IK源代码的形式,从MYSQL数据库中动态加载词库。
需要修改的内容有:
Initial方法,是IK中核心类型Dictionary的初始化方法。是ES启动的时候,会加载的IK的类型中必然运行的一个初始化方法。修改初始化方法,是为了提供自定义代码的运行。
在org.wltea.analyzer.dic.Dictionary类中,修改其初始化方法initial,添加如下内容:
在org.wltea.analyzer.dic.Dictionary类中,新增方法:
/**
* 远程热词加载方法。
*/
public void loadHotDictionary(){
// 自定义的远程词库加载方法
this.loadMainHotDicFromDB();
// 加载自定义停用词典
this.loadStopwordDicFromDB();
}
// 用于记录配置文件
private static Properties properties = new Properties();
static{
try {
logger.info("regist database driver");
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
} catch (SQLException e) {
logger.error("regist database driver error : ", e);
}
}
/**
* 自定义方法, 用于访问数据库,加载热词。
*/
private void loadMainHotDicFromDB(){
Connection connection = null;
Statement stmt = null;
ResultSet rs = null;
try{
Path file = PathUtils.get(getDictRoot(), "hot-dic-db-source.properties");
properties.load(new FileInputStream(file.toFile()));
logger.info("properties info : " + properties);
connection = DriverManager.getConnection(
properties.getProperty("db.url"),
properties.getProperty("db.username"),
properties.getProperty("db.password")
);
logger.info("connection info : " + connection);
stmt = connection.createStatement();
logger.info("The sql for query main hot dictionary : "
+ properties.getProperty("db.reload.mainhotdic.sql"));
rs = stmt.executeQuery(properties.getProperty("db.reload.mainhotdic.sql"));
while(rs.next()){
String word = rs.getString("word");
logger.info("hot word from DB: " + word);
_MainDict.fillSegment(word.trim().toCharArray());
}
}catch(Exception e){
logger.error("error", e);
}finally{
if(rs != null) {
try {
rs.close();
} catch (SQLException e) {
logger.error("JDBC ResultSet Closing Error : ", e);
}
}
if(stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
logger.error("JDBC Statement Closing Error : ", e);
}
}
if(connection != null) {
try {
connection.close();
} catch (SQLException e) {
logger.error("JDBC Connection Closing Error : ", e);
}
}
}
}
/**
* 自定义方法, 用于访问数据库,加载自定义停用词。
*/
private void loadStopwordDicFromDB(){
Connection connection = null;
Statement stmt = null;
ResultSet rs = null;
try{
Path file = PathUtils.get(getDictRoot(), "hot-dic-db-source.properties");
properties.load(new FileInputStream(file.toFile()));
connection = DriverManager.getConnection(
properties.getProperty("db.url"),
properties.getProperty("db.username"),
properties.getProperty("db.password")
);
stmt = connection.createStatement();
logger.info("The sql for query stopword dictionary : "
+ properties.getProperty("db.reload.stopworddic.sql"));
rs = stmt.executeQuery(properties.getProperty("db.reload.stopworddic.sql"));
while(rs.next()){
String word = rs.getString("word");
logger.info("stopword from DB: " + word);
_StopWords.fillSegment(word.trim().toCharArray());
}
}catch(Exception e){
logger.error("error", e);
}finally{
if(rs != null) {
try {
rs.close();
} catch (SQLException e) {
logger.error("JDBC ResultSet Closing Error : ", e);
}
}
if(stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
logger.error("JDBC Statement Closing Error : ", e);
}
}
if(connection != null) {
try {
connection.close();
} catch (SQLException e) {
logger.error("JDBC Connection Closing Error : ", e);
}
}
}
}
新增一个线程类型org.wltea.analyzer.dic.HotDicLoadingTask,用于启动任务:
package org.wltea.analyzer.dic;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.common.logging.ESLoggerFactory;
public class HotDicLoadingTask implements Runnable {
private static final Logger logger = ESLoggerFactory.getLogger(HotDicLoadingTask.class.getName());
@Override
public void run() {
logger.info("==================reload hot dictionary from mysql database==================");
Dictionary.getSingleton().loadHotDictionary();
}
}
新增配置文件config/hot-dic-db-source.properties,提供数据库相关配置:
db.url=jdbc:mysql://192.168.2.106:3306/hotdic?useUnicode=true&charactorEncoding=utf8
db.username=root
db.password=root
db.reload.mainhotdic.sql=select word from tb_main_hot_dic
db.reload.stopworddic.sql=select word from tb_stopword_dic
修改pom.xml配置文件
修改ElasticSearch版本:
新增mysql驱动依赖:
修改assembly配置文件src/main/assemblies/plugin.xml:
增加下述内容:增加后,在package打包当前工程的时候,会在打包后的zip压缩包中增加mysql驱动jar包。否则需要手工添加。
根据配置文件config/hot-dic-db-source.properties创建对应的数据库database和table:
create database hotdic charset = utf8;
use hotdic;
create table tb_main_hot_dic(
id bigint not null auto_increment,
word varchar(255),
primary key (id)
);
create table tb_stopword_dic(
id bigint not null auto_increment,
word varchar(255),
primary key (id)
);
修改JDK中的配置文件$JAVA_HOME/jre/lib/security/java.policy。在grant{}中增加下述内容: 根据每个人不同的系统环境,修改IP和端口,其他不变。
permission java.lang.RuntimePermission "createClassLoader";
permission java.lang.RuntimePermission "getClassLoader";
permission java.net.SocketPermission "192.168.2.106:3306","connect,resolve";
JDK对外部执行应用也有权限限制,默认情况下,外部应用(如ES)在使用JDK相关内核组件的时候(如ClassLoader)、使用JDK网络访问其他应用的时候(如Socket连接等),都需要有对应的权限。这里就是修改本地JDK,让本地启动的应用程序访问JDK内核或通过JDK访问外部资源的时候,拥有权限,避免错误的可能。(低版本的ES或JDK可能不需要修改此文件)
打包IK
执行package命令,实现打包。打包完成后,在target/releases目录中会生成对应的zip压缩包。安装IK的方式不变。