一.sug概述
对提供的搜索词(suggest text)返回相关的提示词
二.四种suggester
通用option字段及含义
option | 含义 |
---|---|
text | 搜索词,因为一个search可以存在多个suggester,所以可以设置全局text,也可对每个suggester单独设置,单独设置时以单独为准 |
field | 返回提示词的字段 |
analyzer | 搜索词text分词器,默认和field的分词器相同 |
size | 每个text返回size个提示词 |
sort | 对于text的每个分出来的词(suggest text term),返回的提示词如何排序
|
suggest_mode | 用来控制①返回什么提示词②对哪些搜索词的分词返回提示词
|
1.Term suggester
term sug提供提示词的依据是和搜索词的编辑距离,直观理解就是根据相似程度。ES会先对搜索词进行分词处理(分词器可以手动设定,默认和要搜索字段的分词器相同),然后对每一个分出来的词返回相似词。
term sug的option字段及含义
option | 含义 |
---|---|
lowercase_terms | 把搜索词分词小写 |
max_edits | 提示词与搜索词分词的最大编辑距离,取值范围[1,2],默认2 |
prefix_length | 至少匹配的前缀长度(TODO:汉字长度咋算),默认1 |
min_word_length | 返回提示词的最小长度,默认4 |
shard_size | 每个shard返回到coordinate node 的提示词数,增大时提高返回精准度,具体参考 terms分组计数精确度问题中关于shard_size 和 size的说明。 |
max_inspections | 乘shard_size的因子,加大时在各分片上监测更多的拼写矫正结果,性能换精度,默认5。估计也用不到 |
min_doc_freq | 大于1的整数或百分比值,表示提示词最少在几篇,或百分之多少的文档中出现。默认为0,不生效。 |
max_term_freq | 大于1的整数或百分比值,表示提示词最多出现在几篇或百分之多少的文档里。设置此值的背景是需要拼写检查的场景,对于某些频率特别高的词,打搜索词时出错概率低,不返回提示词。 |
string_distance | 五种计算编辑距离的算法...... |
Term suggester示例
1、数据准备
curl -u $user:passoword -H "Content-Type:application/json" -XPUT "http://$ip:$port/blogs" -d '{"settings":{"index":{"number_of_shards":2,"number_of_replicas":1}}}'
curl -u $user:passoword -H "Content-Type:application/json" -XPUT "http://$ip:$port/blogs/_mapping/_doc?pretty" -d '{"properties":{"body":{"type":"text"}}}'
curl -u $user:passoword -H "Content-Type:application/json" -XPOST "http://$ip:$port/blogs/_doc/_bulk?pretty" --data-binary @test.data
{ "index" : { "_index" : "blogs"} }
{ "body":"Lucene is cool"}
{ "index" : { "_index" : "blogs"} }
{ "body": "Elasticsearch builds on top of lucene"}
{ "index" : { "_index" : "blogs"} }
{ "body": "Elasticsearch rocks"}
{ "index" : { "_index" : "blogs"} }
{ "body": "Elastic is the company behind ELK stack"}
{ "index" : { "_index" : "blogs"} }
{ "body": "elk rocks"}
{ "index" : { "_index" : "blogs"} }
{ "body": "elasticsearch is rock solid"}
suggest就是一种特殊类型的搜索,DSL内部的"text"指的是api调用方提供的文本,也就是通常用户界面上用户输入的内容。这里的lucne是错误的拼写,模拟用户输入错误。 "term"表示这是一个term suggester。 "field"指定suggester针对的字段,另外有一个可选的"suggest_mode"。 范例里的"missing"实际上就是缺省值
curl -u $user:passoword -H "Content-Type:application/json" -XPOST "http://$ip:$port/blogs/_doc/_search?pretty" -d '{
"suggest":{
"my-suggestion":{
"text":"lucne rocks",
"term":{
"suggest_mode":"missing",
"field":"body"
}
}
}
}'
结果:
{
"took":79,
"timed_out":false,
"_shards":{
"total":2,
"successful":2,
"skipped":0,
"failed":0
},
"hits":{
"total":0,
"max_score":0,
"hits":[
]
},
"suggest":{
"my-suggestion":[
{
"text":"lucne",
"offset":0,
"length":5,
"options":[
{
"text":"lucene",
"score":0.8,
"freq":2
}
]
},
{
"text":"rocks",
"offset":6,
"length":5,
"options":[
{
"text":"rock",
"score":0.75,
"freq":1
}
]
}
]
}
}
在返回结果里"suggest" -> "my-suggestion"部分包含了一个数组,每个数组项对应从输入文本分解出来的token(存放在"text"这个key里)以及为该token提供的建议词项(存放在options数组里)。
示例里返回了"lucne","rock"这2个词的建议项(options)
#suggest_mode换成popular再试试?
两个term的相似性是如何判断的? ES使用了一种叫做Levenstein edit distance的算法,其核心思想就是一个词改动多少个字符就可以和另外一个词一致。
2.Phrase suggester
Phrase suggester在Term suggester的基础上,会考量多个term之间的关系,比如是否同时出现在索引的原文里,相邻程度,以及词频等等。
curl -u $user:$password -H "Content-Type:application/json" -XPOST "http://$ip:$port/blogs/_doc/_search?pretty" -d '{
"suggest":{
"my-suggestion":{
"text":"lucne and elasticsear rock",
"phrase":{
"field":"body",
"highlight":{
"pre_tag":"<em>",
"post_tag":"</em>"
}
}
}
}
}'
# 结果
{
"took":30,
"timed_out":false,
"_shards":{
"total":2,
"successful":2,
"skipped":0,
"failed":0
},
"hits":{
"total":0,
"max_score":0,
"hits":[
]
},
"suggest":{
"my-suggestion":[
{
"text":"lucne and elasticsear rock",
"offset":0,
"length":26,
"options":[
{
"text":"lucene and elasticsearch rock",
"highlighted":"<em>lucene</em> and <em>elasticsearch</em> rock",
"score":0.012031202
},
{
"text":"lucne and elasticsearch rocks",
"highlighted":"lucne and <em>elasticsearch rocks</em>",
"score":0.009254823
},
{
"text":"lucne and elasticsearch rock",
"highlighted":"lucne and <em>elasticsearch</em> rock",
"score":0.008044719
},
{
"text":"lucene and elasticsear rock",
"highlighted":"<em>lucene</em> and elasticsear rock",
"score":0.007966585
},
{
"text":"lucne and elasticsear rocks",
"highlighted":"lucne and elasticsear <em>rocks</em>",
"score":0.0073081385
}
]
}
]
}
}
options直接返回一个phrase列表,由于加了highlight选项,被替换的term会被高亮。因为lucene和elasticsearch曾经在同一条原文里出现过,同时替换2个term的可信度更高,所以打分较高,排在第一位返回。Phrase suggester有相当多的参数用于控制匹配的模糊程度,需要根据实际应用情况去挑选和调试。
3.Completion suggester
主要针对的应用场景就是"Auto Completion"。此场景下用户每输入一个字符的时候,就需要即时发送一次查询请求到后端查找匹配项,在用户输入速度较高的情况下对后端响应速度要求比较苛刻。因此实现上它和前面两个Suggester采用了不同的数据结构,索引并非通过倒排来完成,而是将analyze过的数据编码成FST和索引一起存放。对于一个open状态的索引,FST会被ES整个装载到内存里的,进行前缀查找速度极快。但是FST只能用于前缀查找,这也是Completion Suggester的局限所在。
#需要重新建立索引数据
PUT /blogs_completion/
{
"mappings": {
"tech": {
"properties": {
"body": {
"type": "completion"
}
}
}
}
}
POST _bulk/?refresh=true
{ "index" : { "_index" : "blogs_completion", "_type" : "tech" } }
{ "body": "Lucene is cool"}
{ "index" : { "_index" : "blogs_completion", "_type" : "tech" } }
{ "body": "Elasticsearch builds on top of lucene"}
{ "index" : { "_index" : "blogs_completion", "_type" : "tech" } }
{ "body": "Elasticsearch rocks"}
{ "index" : { "_index" : "blogs_completion", "_type" : "tech" } }
{ "body": "Elastic is the company behind ELK stack"}
{ "index" : { "_index" : "blogs_completion", "_type" : "tech" } }
{ "body": "the elk stack rocks"}
{ "index" : { "_index" : "blogs_completion", "_type" : "tech" } }
{ "body": "elasticsearch is rock solid"}
##查找
POST blogs_completion/_search?pretty
{ "size": 0,
"suggest": {
"blog-suggest": {
"prefix": "elastic i",
"completion": {
"field": "body"
}
}
}
}
#结果
{
"took": 33,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 0,
"max_score": 0,
"hits": []
},
"suggest": {
"blog-suggest": [
{
"text": "elastic i",
"offset": 0,
"length": 9,
"options": [
{
"text": "Elastic is the company behind ELK stack",
"_index": "blogs_completion",
"_type": "tech",
"_id": "AWh05W0vrgjL5DnrjuWz",
"_score": 1,
"_source": {
"body": "Elastic is the company behind ELK stack"
}
}
]
}
]
}
}
值得注意的一点是Completion Suggester在索引原始数据的时候也要经过analyze阶段,取决于选用的analyzer不同,某些词可能会被转换,某些词可能被去除,这些会影响FST编码结果,也会影响查找匹配的效果。
比如我们删除上面的索引,重新设置索引的mapping,将analyzer更改为"english"
PUT /blogs_completion/
{
"mappings": {
"tech": {
"properties": {
"body": {
"type": "completion",
"analyzer": "english"
}
}
}
}
}
#bulk api索引同样的数据后,执行下面的查询:
POST blogs_completion/_search?pretty
{ "size": 0,
"suggest": {
"blog-suggest": {
"prefix": "elastic i",
"completion": {
"field": "body"
}
}
}
}
#结果
{
"took": 7,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 0,
"max_score": 0,
"hits": []
},
"suggest": {
"blog-suggest": [
{
"text": "elastic i",
"offset": 0,
"length": 9,
"options": []
}
]
}
}
#居然没有匹配结果了,多么费解!原来我们用的english analyzer会剥离掉stop word,而is就是其中一个,被剥离掉了!
#用analyze api测试一下:
POST _analyze?analyzer=english
{
"text": "elasticsearch is rock solid"
}
#结果
{
"tokens": [
{
"token": "elasticsearch",
"start_offset": 0,
"end_offset": 13,
"type": "<ALPHANUM>",
"position": 0
},
{
"token": "rock",
"start_offset": 17,
"end_offset": 21,
"type": "<ALPHANUM>",
"position": 2
},
{
"token": "solid",
"start_offset": 22,
"end_offset": 27,
"type": "<ALPHANUM>",
"position": 3
}
]
}
FST只编码了这3个token,并且默认的还会记录他们在文档中的位置和分隔符。 用户输入"elastic i"进行查找的时候,输入被分解成"elastic"和"i",FST没有编码这个“i” , 匹配失败。
好吧,如果你现在还足够清醒的话,试一下搜索"elastic is",会发现又有结果,why? 因为这次输入的text经过english analyzer的时候is也被剥离了,只需在FST里查询"elastic"这个前缀,自然就可以匹配到了。
其他能影响completion suggester结果的,还有诸如"preserve_separators","preserve_position_increments"等等mapping参数来控制匹配的模糊程度。以及搜索时可以选用Fuzzy Queries,使得上面例子里的"elastic i"在使用english analyzer的情况下依然可以匹配到结果。
因此用好Completion Sugester并不是一件容易的事,实际应用开发过程中,需要根据数据特性和业务需要,灵活搭配analyzer和mapping参数,反复调试才可能获得理想的补全效果。
回到篇首Google搜索框的补全/纠错功能,如果用ES怎么实现呢?我能想到的一个的实现方式:
- 在用户刚开始输入的过程中,使用Completion Suggester进行关键词前缀匹配,刚开始匹配项会比较多,随着用户输入字符增多,匹配项越来越少。如果用户输入比较精准,可能Completion Suggester的结果已经够好,用户已经可以看到理想的备选项了。
- 如果Completion Suggester已经到了零匹配,那么可以猜测是否用户有输入错误,这时候可以尝试一下Phrase Suggester。
- 如果Phrase Suggester没有找到任何option,开始尝试term Suggester。
精准程度上(Precision)看: Completion > Phrase > term, 而召回率上(Recall)则反之。从性能上看,Completion Suggester是最快的,如果能满足业务需求,只用Completion Suggester做前缀匹配是最理想的。 Phrase和Term由于是做倒排索引的搜索,相比较而言性能应该要低不少,应尽量控制suggester用到的索引的数据量,最理想的状况是经过一定时间预热后,索引可以全量map到内存。
Suggesters | Elasticsearch Guide [8.3] | Elastic
4.Context Suggester
https://www.elastic.co/guide/en/elasticsearch/reference/5.2/suggester-context.html#