elasticsearch可以说是目前应用非常广泛的搜索引擎了,然而如果想要能够完全的满足检索的需求,还是有些需要注意的点的。
ik分词器,是elasticsearch检索中文时最常用的分词器。其有两种分词模式:
ik_max_word
会将文本做最细粒度的拆分,比如:
中华人民共和国人民大会堂 => 中华人民共和国、中华人民、中华、华人、人民共和国、人民、共和国、大会堂、大会、会堂
ik_smart
会做最粗粒度的拆分,比如:
中华人民共和国人民大会堂 => 中华人民共和国、人民大会堂
常规的最常用的使用方式就是,数据插入存储时用 ik_max_word模式分词,而检索时,用ik_smart模式分词,即:索引时最大化的将文章内容分词,搜索时更精确的搜索到想要的结果。
模糊匹配
mapping:
{"_doc":{"properties":{"content":{"type":"text","analyzer":"ik_max_word","search_analyzer":"ik_smart"}}}}
query:
{"bool":{"must":[{"match_phrase":{"content":"\u72fc\u5d3d"}}]}}
该检索能够满足大部分的检索需求,也是最常用的检索方式
模糊匹配&精确匹配
有时候,检索不仅需要支持模糊匹配,还需要支持完全匹配
mapping:
{"_doc":{"properties":{"content":{"type":"text","analyzer":"ik_max_word","search_analyzer":"ik_smart","fields":{"keyword":{"type":"keyword","ignore_above":256}}}}}}
通过给字段增加fileds “keyword”, 类型为keyword, 则该fields就可以通过 content.keyword 访问
query:
{"bool":{"must":[{"term":{"content.keyword":"\u6211\u559c\u6b22"}}]}}
通过以上方式则可以实现精准匹配
解决因分词问题导致用一些词检索不到数据的情况
上文我们讲过,索引是用ik_max_word分词,检索时用ik_smart分词,可以说,ik_smart的分词结果是ik_max_word分词结果的一个子集,那么理论上来讲,肯定是能够检索到数据的,那么为什么有时会检索不到呢?
对此官方网站对match_phrase的解释如下:
Like the match query, the match_phrase query first analyzes the query string to produce a list of terms. It then searches for all the terms, but keeps only documents that contain all of the search terms, in the same positions relative to each other.
意思就是说用match_phrase查找时,查找分词器分出的词的位置和要建索引时分出的词的相对位置一样。
例如:
原文:我喜欢一家四口的生活
ik_max_word分词结果
分词 | 位置 |
我 | 0 |
喜欢 | 1 |
一家 | 2 |
一 | 3 |
家 | 4 |
四口 | 5 |
四 | 6 |
口 | 7 |
检索词: 一家四口
ik_smart分词结果
分词 | 位置 |
一家 | 0 |
四口 | 1 |
从上面可以看出,查找时ik_smart将语句分为了 一家 和 四口 两个词,位置分别为 0 和 1 ,而ik_max_word建索引时,一家 和 四口 的位置分别是 2 和 5,一个位置相邻,一个位置不相邻,在match_phrase看来,这种是不匹配的,所以用ik_smart分词短语时无法查到或者查全数据。
为了解决match_phrase这个问题,我们可以辅以使用standard分词器。
standard分词器大家都比较熟,针对于汉字就是一个一个分,这种肯定是可以查全的。但一个一个字分的话,每个字对应的文档集合非常多,如果数据量达到了百亿,在求交集,计算距离时,效果非常差。而这里我们可以将其跟ik分词器配合使用,既利用ik分词器的优势,又可以利用standard分词器进行兜底。
但是,此时还有一个问题,就是standard分词只是针对单个字的分词,ik_max_word分出了很多词,如果有“好人”,“好人的”这种分词结果,那么搜索“好人”,match_phrase仅能够搜索出“好人”对应的结果,同样符合预期的“好人的”的结果是检索不到的。
针对这种情况,我们利用match_phrase_prefix代替match_phrase,match_phrase_prefix 与 match_phrase查询类似,但是会对最后一个Token在倒排序索引列表中进行通配符搜索。即match_phrase_prefix将会找到“好人”,“好人的”对应的结果。
mapping:
{"_doc":{"properties":{"content":{"type":"text","analyzer":"ik_max_word","search_analyzer":"ik_smart","fields":{"keyword":{"type":"keyword","ignore_above":256},"standard":{"type":"text","analyzer":"standard"}}}}}}
query:
{"query":{"bool":{"must":[{"bool":{"should":[{"match_phrase_prefix":{"content":"\u6211\u559c\u6b22"}},{"match_phrase_prefix":{"content.standard":"\u6211\u559c\u6b22"}}]}}]}}}
总结
以上就是elasticsearch的检索相关知识,因为es修改mapping相当的麻烦,所以大家在创建索引时,一定要尽可能的将mapping考虑周全,避免之后的修改。
当然,为了以后万一修改的时候减小成本,大家创建索引后,尽量同时创建别名,然后利用别名访问该索引,这样之后就可以通过修改别名指向,从而无缝替换旧索引,不影响线上服务。
附录
如果仅仅是给索引字段增加fields,是不需要重建索引的,可以直接修改mapping:
curl -H "Content-Type: application/json;" -X POST 'http://127.0.0.1:9200/my_index/_doc/_mapping?pretty' -d ' {"_doc":{"properties":{"title":{"type":"text","analyzer":"ik_max_word","search_analyzer":"ik_smart","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"content":{"type":"text","analyzer":"ik_max_word","search_analyzer":"ik_smart","fields":{"keyword":{"type":"keyword","ignore_above":256}}}}}} '
修改完mapping后,新的数据就会按照新的mapping来走了,如果想要历史数据也同步新的mapping的话,则利用_update_by_query更新即可:
curl -H "Content-Type: application/json;" -X POST 'http://127.0.0.1:9200/my_index/_update_by_query?conflicts=proceed'
更新时间跟数据量的多少有关,数据越多,则更新所需时间越长。