需求:有一个字段item_name,两条数据
出租车
出租汽车
如果使用ik_smart分词,出租车和出租汽车都不会分词
如果使用ik_max_word分词,出租车分为出租车、出租、租车。出租汽车分为出租汽车、出租、汽车。
搜索端使用的分词是ik_smart,而索引使用的分词是ik_max_word。
那当我搜索“出租车”的时候,是搜索不到出租汽车的
当我搜索“出租汽车”的时候,是搜索不到出租车的。
如果我搜索端使用的分词是ik_max_word,那我可能搜索到很多不想搜索到的内容,比如可能会搜索到“房屋出租”
解决方法:使用近义词组件
近义词组件已经是elasticsearch自带的了,所以不需要额外安装插件。
elastic synonym token filter官方参考:
想要ik analysis中使用synonym token filter,需要配置自己的分析器
创建一个test_index
PUT test_index
{
"settings": {
"number_of_shards": 1,
"analysis": {
"filter": {
"my_synonym_filter":{
"type":"synonym",
"synonyms_path":"analysis-ik/synonym.txt"
}
},
"analyzer": {
"ik_syno":{
"type":"custom",
"tokenizer":"ik_smart",
"filter":["my_synonym_filter"]
},
"ik_syno_max":{
"type":"custom",
"tokenizer":"ik_max_word",
"filter":["my_synonym_filter"]
}
}
}
},
"mappings": {
"doc":{
"properties": {
"item_name":{
"type": "text",
"analyzer": "ik_syno_max",
"search_analyzer": "ik_syno_max"
}
}
}
}
}
在elasticsearch plugins 下的analysis-ik下新增synonym.txt文件
其中synonym.txt中配置
出租车,出租汽车
synonym.txt的配置规则是:
a,b => c
a,b,c
第一种在分词的时候,a,b都会解析成为c,然后把c存入索引中
第二种在分词的时候,有a的地方,都会解析成a,b,c,把a,b,c存入索引中
添加两条测试数据
POST test_index/doc
{
"item_name":"对违反出租车运营规定的处罚"
}
POST test_index/doc
{
"item_name":"出租汽车经营者不按照规定配置出租汽车相关设备"
}
查询一下
GET test_index/doc/_search
{
"query": {
"match": {
"item_name": "出租汽车"
}
}
}
GET test_index/doc/_search
{
"query": {
"match": {
"item_name": "出租车"
}
}
}
都是可以搜索到的,实现了我们的需求
另外,还有一个热更新的问题
有一个解决方案是修改IK分词器的源码,基于mysql来热更新词库。
思路是:
ik-analysis在内存中维护了一个_MainDict。
新建一个线程,这个线程在项目启动的时候启动,之后查询数据库,将数据库中配置的词添加到内存_MainDict中,然后Thread.sleep 1秒。之后继续重复。
这样当进行分词的时候,从内存中取到的_MainDict基本上是最新的。
这样有两个好处:
1. 不需要重启es就能生效
2. 由于es是分布式的,可能有数百个节点,这样你就不用每次都一个一个节点上面去修改ik分词的词库了。
但这样的热更新也要思考
对于1,如果es不重新创建索引,那么即便es热更新对应这些词语,由于索引中的字段未更新,还是搜索不到的。
所以热更新的应用是有条件的。
比如说你的索引中只有几条与之相关的,那么你数据库中确定好,修改其modify_time,之后让logstash来更新这几条索引。
如果你的索引中很多条与之相关,那么还是要全部更新es相应索引。
对于2,这个的的确确是热更新的好处,但就目前我们集群两台机器,不需要这样做,比如添加近义词到synonym.txt中,那就在两台机器中的synonym.txt中都添加就可以了
基于以上,目前并没有采用基于mysql的热更新词库的功能