绊脚石乃是进身之阶。

Day 2 - ES 6.x拼音分词高亮爬坑记

Advent | 作者 abia | 发布于2018年12月02日 | | 阅读数:8821

大家好,我是来自尚德机构ES平台的负责人,白凡,今天为大家分享一些在6.x版本中拼音分词高亮问题爬坑的心路历程~,其实问题不复杂,主要介绍下思路。

首先简单讲下背景~可能在很多公司的很多部门,都有使用到ES。包括在尚德,由于很多部门的业务都涉及到ES,于是我们为了统一管理及维护,专门成立了ES平台部门,主要扮演的是类似于op dba角色,帮助业务部门部署维护ES集群,并根据业务需求提供解决方案。当然,除此之外,我们也会在公司内部推荐业务方去尝试除了日志和搜索以外的应用场景,比如分布式计算存储、监控、安全等方面。毕竟ES相比于其他组建,搭建部署更加方便,更轻量级,查询方式更丰富。所以,现如今在尚德机构,ES平台不仅用于了传统的日志和搜索领域,也在分布式数据存储和计算方面有很多应用。当然,这里只是为大家提供一些ES应用场景及其团队构建的思路。主要还是ES这个工具确实好用。

广告先做到这,回到正文。所以,前段日子,我们接收了一个新的业务部门需求,大致是:他们之前使用的自己搭建ES 2.x集群,现在接入到我们6.x的平台上来。我们帮忙设计了mapping,数据写入及同步方案之后,数据就慢慢接入进来。但问题也随即出现,原来在2.x上使用正常的拼音高亮mapping,在6.x上只能检索但无法高亮了?

2.x field如下:
"index" : {
"analysis" : {
"analyzer" : {
"pinyin_analyzer" : {
"tokenizer" : "my_pinyin"
}
},
"tokenizer" : {
"my_pinyin" : {
"type" : "pinyin",
"keep_full_pinyin" : false,
"limit_first_letter_length" : 16,
"lowercase" : true,
"remove_duplicated_term":true,
"keep_first_letter":true,
"keep_separate_first_letter" :true
}
}
}
}
POST /medcl/doc/_mapping
{
"properties": {
"name":{
"analyzer": "pinyin_analyzer",
"type": "string"
}
}
}

可以从上面例子看出,这个analyzer并没有问题,但是在搜索时,能得到结果,却无法高亮,即分词结果中start_offset及end_offset为0,这个如何解决呢?

回到medcl的拼音分词项目:
https://github.com/medcl/elast ... inyin
其中,有个配置项引起了我们的注意:

图片1.png


没跑了,应该是要将这个参数设置为false。
并且查看了源码,在PinyinTokenizer这个类下面,看到了这一行:

图片2.png


确定了我们的思路,于是乎,在tokenizer中将此参数设为false,如下:
"tokenizer" : {
"my_pinyin" : {
"type" : "pinyin",
"keep_full_pinyin" : true,
"keep_original" : false,
"limit_first_letter_length" : 16,
"lowercase" : true,
"remove_duplicated_term":true,
"ignore_pinyin_offset": false,
"keep_first_letter":true,
"keep_separate_first_letter" :true
}
}

写入一条数据,高亮没问题了,问题“看似”解决了。
当然,没有那么简单。因为在批量写入一部分数据后,总会报如下异常:
startOffset must be non-negative, and endOffset must be >= startOffset
这个异常,导致数据无法写入集群。
这里又是为什么呢?
这个问题,我也搞了一段时间,始终没找到很好的解决方案,此处只能先@medcl。
只是猜测在end()或者reset()方法内,需要lastOffset置0或者offsetAtt清空。但尝试了几次,依然报错。。。

这就比较头疼了,不过好在条条道路通罗马。在某次蹲坑过程中,灵感如尿崩。

如果Tokenizer解决不了,为何不仅用filter就行了呢?可以先用其他分词器,按我们业务的需求进行分词,再用filter,将分词过滤为拼音呢?

大致思路如下:
目前我们这个业务,需要如对于“尚德机构”这个词,搜索“shang”,“shangde”,“deji”时,能返回结果并高亮。
所以我们先用ngram分词,将“尚德机构”这个词分为“尚”,“尚德”,“徳机”,“德机构”等等。。
再用pinyin filter将各分词过滤为拼音,即“shang”,“shangde”,“deji”等。
并在搜索时,采用standard分词。
Mapping如下:
{
"settings": {
"analysis": {
"analyzer": {
"pinyin_analyzer": {
"tokenizer": "my_ngram",
"filter": [
"pinyin_filter"
]
}
},
"tokenizer": {
"my_ngram": {
"type": "ngram",
"min_gram": 1,
"max_gram": 50,
"token_chars": [
"letter",
"digit",
"punctuation",
"symbol"
]
}
},
"filter": {
"pinyin_filter": {
"type": "pinyin",
"keep_full_pinyin": false,
"keep_joined_full_pinyin": true,
"keep_none_chinese_in_joined_full_pinyin": true,
"none_chinese_pinyin_tokenize": false,
"remove_duplicated_term": true
}
}
}
},
"mappings": {
"abia321": {
"properties": {
"name": {
"type": "text",
"analyzer": "pinyin_analyzer",
"search_analyzer": "standard",
"term_vector": "with_positions_offsets"
}
}
}
}
}
最后,高亮问题解决,数据写入问题同样解决。
当然有朋友肯定还会需要搜索拼音首字母进行搜索,如搜“s”,“sd”,“dj”,也返回结果。
其实,只需要再专门设置个field,并调整pinyin filter参数,
搜索时用bool查询,逻辑should查询,同时对完整拼音field和拼音首字母field进行搜索即可。
在此就不做过多赘述。

当然,这里仅仅只是提供一种ES在选择analyzer,tokenizer,filter解决需求的思路。拼音分词这个问题,还是需要等待后续修复

最后,这里有较为完整的issue:
https://github.com/medcl/elast ... s/169

[尊重社区原创,转载请保留或注明出处]
本文地址:http://elasticsearch.cn/article/6166


10 个评论

推荐使用 markdown 重新进行排版,否则代码区域看起来很不方便
的确推荐使用 pinyin 做 filter,做分词还是借助其他 tokenlizer 比较好
abia

abia 回复 rochy

好的,昨天搞了老半天这个编辑页面,似乎普通编辑栏下,无法进行粘贴。我看怎么优化一下格式
学习了,pinyin作为filter
填坑中...
abia

abia 回复 medcl

哈哈,加油^0^~
@abia 回忆起来了,如果你要使用位置为了正常使用高亮,那么你要配置的分析器必须保证输出的 term 里面没有叠词,也就是位置交叠,否则就会报错,ignore_pinyin_offset 就不能设置为 false。
@abia 是每次都报错还是偶尔,给我一个复现的完整 rest 看看。
abia

abia 回复 medcl

似乎是必现问题,每次都报错,试了几次,直接使用pinyin tokenizer,把ignore_pinyin_offset设置为false,其他配置随意,将某个字段的analyzer设置为pinyin。然后用java sdk 批量不间断写入一批数据,每次在写到40个左右之后,就startOffset must be non-negative, and endOffset must be >= startOffset。
这是我同事当时提的issue,其中mapping基本就是他描述的这样。就是需要把里面ignore_pinyin_offset设置为false。https://github.com/medcl/elasticsearch-analysis-pinyin/issues/185
abia

abia 回复 medcl

这个"startOffset must be non-negative, and endOffset must be >= startOffset",应该是reset()方法内this.processedSortCandidate = false这个参数没设置为false引起。自己测试,写入已经没问题。提了PR,可以看看是不是这个原因~~
https://github.com/medcl/elasticsearch-analysis-pinyin/pull/206/commits/7cbc3d8926c8549b1049b90e90fce415097990be

要回复文章请先登录注册