前言
我们来看一下自动完成的建议器——是一个导航功能,提供自动完成、搜索功能,可以在用户输入时引导用户查看相关结果,从而提高搜索精度。但并不适用于拼接检查或者像term
和phrase
建议那样的功能。
如果说在2000年左右,自动完成还是很炫酷的功能,那么现在它是必备的了——任何没有自动完成功能的搜索引擎都是很古老的。用户期望一个良好的自动完成来帮助用户实现更快的(特别是移动端)以及更好的(比如输入e
,搜索引擎就应该知道用户想要查找的是elasticsearch
)搜索。
一个优秀的自动完成将降低搜索引擎的负载,特别是在用户有一些快速搜索可用时,也就是直接跳转到主流的搜索结果而无须执行完整的搜索。
完成建议器:completion suggester
为了告诉elasticsearch我们准备将建议存储在自动完成的FST中,需要在映射中定义一个字段并将其type
类型设置为completion
:
PUT s5
{
"mappings":{
"doc":{
"properties": {
"title": {
"type": "completion",
"analyzer": "standard"
}
}
}
}
}
PUT s5/doc/1
{
"title":"Lucene is cool"
}
PUT s5/doc/2
{
"title":"Elasticsearch builds on top of lucene"
}
PUT s5/doc/3
{
"title":"Elasticsearch rocks"
}
PUT s5/doc/4
{
"title":"Elastic is the company behind ELK stack"
}
PUT s5/doc/5
{
"title":"the elk stack rocks"
}
PUT s5/doc/6
{
"title":"elasticsearch is rock solid"
}
GET s5/doc/_search
{
"suggest": {
"my_s5": {
"text": "elas",
"completion": {
"field": "title"
}
}
}
}
建议结果不展示了!
上例的特殊映射中,支持以下参数:
- analyzer,要使用的索引分析器,默认为simple。
- search_analyzer,要使用的搜索分析器,默认值为analyzer。
- preserve_separators,保留分隔符,默认为true。 如果您禁用,您可以找到以Foo Fighters开头的字段,如果您建议使用foof。
- preserve_position_increments,启用位置增量,默认为true。如果禁用并使用停用词分析器The Beatles,如果您建议,可以从一个字段开始b。注意:您还可以通过索引两个输入来实现此目的,Beatles并且 The Beatles,如果您能够丰富数据,则无需更改简单的分析器。
- max_input_length,限制单个输入的长度,默认为50UTF-16代码点。此限制仅在索引时使用,以减少每个输入字符串的字符总数,以防止大量输入膨胀基础数据结构。大多数用例不受默认值的影响,因为前缀完成很少超过前缀长于少数几个字符。
除此之外,该建议映射还可以定义在已存在索引字段的多字段:
PUT s6
{
"mappings": {
"doc": {
"properties": {
"name": {
"type": "text",
"fields": {
"suggest": {
"type": "completion"
}
}
}
}
}
}
}
PUT s6/doc/1
{
"name":"KFC"
}
PUT s6/doc/2
{
"name":"kfc"
}
GET s6/doc/_search
{
"suggest": {
"my_s6": {
"text": "K",
"completion": {
"field": "name.suggest"
}
}
}
}
如上示例中,我们需要索引餐厅这样的地点,而且每个地点的name
名称字段添加suggest
子字段。
上例的查询将肯德基(KFC)和开封菜(kfc)都返回。
PUT s7
{
"mappings": {
"doc": {
"properties": {
"name": {
"type": "text",
"fields": {
"suggest": {
"type": "completion",
"analyzer":"keyword",
"search_analyzer":"keyword"
}
}
}
}
}
}
}
PUT s7/doc/1
{
"name":"KFC"
}
PUT s7/doc/2
{
"name":"kfc"
}
GET s7/doc/_search
{
"suggest": {
"my_s7": {
"text": "K",
"completion": {
"field": "name.suggest"
}
}
}
}
建议结果如下:
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 0,
"max_score" : 0.0,
"hits" : [ ]
},
"suggest" : {
"my_s7" : [
{
"text" : "K",
"offset" : 0,
"length" : 1,
"options" : [
{
"text" : "KFC",
"_index" : "s7",
"_type" : "doc",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"name" : "KFC"
}
}
]
}
]
}
}
上述的建议结果中,只有KFC
被返回。更多的细节控制可以搭配不同的分析器来完成。
多数的情况下,我们将在单独的字段中、单独的索引中甚至是单独的集群中保存建议。这对于主搜索引擎的性能提升和扩展建议器都是非常有利的。
除此之外,还可以使用input
和可选的weight
属性,input
是建议查询匹配的预期文本,weight
是建议评分方式(也就是权重)。例如:
PUT s8
{
"mappings": {
"doc":{
"properties":{
"title":{
"type": "completion"
}
}
}
}
}
添加数据的几种形式:
PUT s8/doc/1
{
"title":{
"input":"blow",
"weight": 2
}
}
PUT s8/doc/2
{
"title":{
"input":"block",
"weight": 3
}
}
上例分别添加两个建议并设置各自的权重值。
PUT s8/doc/3
{
"title": [
{
"input":"appel",
"weight": 2
},
{
"input":"apple",
"weight": 3
}
]
}
上例以列表的形式添加建议,设置不同的权重。
上例是为多个建议设置相同的权重。 查询的结果由权重决定:
GET s8/doc/_search
{
"suggest": {
"my_s8": {
"text": "app",
"completion": {
"field": "title"
}
}
}
}
其他
_source
为了减少不必要的响应,我们可以对建议结果做一些过滤,比如加上_source
:
GET s8/doc/_search
{
"suggest": {
"completion_suggest": {
"text": "appl",
"completion": {
"field": "title"
}
}
},
"_source": "title"
}
好吧,虽然我们只有一个字段!
size
除了_source
,我们还可以指定size
参数:
GET s8/doc/_search
{
"suggest": {
"completion_suggest": {
"prefix": "app",
"completion": {
"field": "title",
"size": 1
}
}
},
"_source": "title"
}
size
参数指定返回建议数(默认为5),需要注意的是,size must be positive
,也就是说size
参数必须是积极的——非0非负数! skip_duplicates 我们的建议可能是来自不同的文档,这其中就会有一些重复建议项,我们可以通过设置skip_duplicates:true
来修改此行为,如果为true
则会过滤掉结果中的重复建议文档:
GET s8/doc/_search
{
"suggest": {
"completion_suggest": {
"prefix": "app",
"completion": {
"field": "title",
"size": 5,
"skip_duplicates":true
}
}
},
"_source": "title"
}
但需要注意的是,该参数设置为true
的话,可能会降低搜索速度,因为需要访问更多的建议结果项,才能过滤出来前N个。
最后,完成建议器还支持正则表达式查询,这意味着我们可以将前缀表示为正则表达式:
GET s5/doc/_search
{
"suggest": {
"completion_suggest": {
"regex": "e[l|e]a",
"completion": {
"field": "title"
}
}
}
}