elasticsearch之completion suggester


前言

我们来看一下自动完成的建议器——是一个导航功能,提供自动完成、搜索功能,可以在用户输入时引导用户查看相关结果,从而提高搜索精度。但并不适用于拼接检查或者像termphrase建议那样的功能。
如果说在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"
      }
    }
  }
}