在7.0+版本中es官方贴心的为我们提供了一个新的字段类型search_as_you_type 来帮我实现开箱即用的搜索建议功能,官方文档中提到它的工作原理是创建一系列子字段,这些子字段被分析为索引词,可以通过部分匹配整个索引文本值的查询进行有效匹配。在早之前官方也为我们提供了,suggests_completion、suggests_phrase、suggest_term等实现自动补全纠错等功能。但是suggest只能提供前缀搜索建议并且存储在内存中构建成本很高(磁盘成本远远低于内存成本这也是选择search_as_you_type的一个原因),可以仔细观察市面上如淘宝,京东,微博等电商都是只支持前缀索引。B站是个例外支持了中缀索引,因为我们的业务特点中缀索引比重更大所以放弃了suggester下面就不多赘述。感兴趣的朋友可以翻阅官方文档
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/search-suggesters.html#completion-suggester
需要注意的是es7以后的版本min_gram和max_gram的粒度默认是不大于1,也就是说分词是一个字符一个字符逐个分的。如果粒度需要大于1需要设置一下index.max_ngram_diff大于等于它们的差值,否则会报错。
要想我们的搜索建议强大且高效其内在原理我们需要吃透,下面先直观的感受下search_as_you_type帮我们创建的子字段的效果。其实官方文档的效果已经很直观了https://www.elastic.co/guide/en/elasticsearch/reference/7.17/analysis-edgengram-tokenfilter.html
ngram我个人理解为gram(克)为最小一个单位的意思,应用到es中对应这一个字符,即排列组合按最小单位分割默认一到两个字符就会看到下面的切分结果,ngram3也很好理解了只是多出一些如qui uic ick的三字符切分
很好理解edge中文翻译过来就是边缘的意思,意味着edge_ngram只能从最左侧边缘位置开始分割
一个 shingle 过滤器在 token 级别执行此操作,因此如果你有文本 “foo bar baz” 并再次使用 in_shingle_size 为2且 max_shingle_size 为3,则你将生成以下 token:
foo, foo bar, foo bar baz, bar, bar baz, baz
为什么仍然包含单 token 输出? 这是因为默认情况下,shingle 过滤器包含原始 token,因此原始标记生成令牌 foo,bar 和 baz,然后将其传递给 shingle token 过滤器,生成标记foo bar,foo bar baz 和 bar baz。 所有这些 token 组合在一起形成最终 token 流。 你可以通过将 output_unigrams 选项设置为 false 来禁用此行为,也即不需要最原始的 token:foo, bar 及 baz
下一个清单显示了 shingle token 过滤器的示例; 请注意,min_shingle_size 选项必须大于或等于2。
PUT my_index
PUT my_index
{
"settings": {
"analysis": {
"analyzer": {
"shingle": {
"type": "custom",
"tokenizer": "ik_max_word",
"filter": [
"shingle-filter"
]
}
},
"filter": {
"shingle-filter": {
"type": "shingle",
"min_shingle_size": 2,
"max_shingle_size": 3,
"output_unigrams": false
}
}
}
}
}
在这里,我们定义了一个叫做 shingle-filter 的过滤器。最小的 shangle 大小是2,最大的 shingle 大小是3。同时我们设置 output_unigrams 为 false,这样最初的那些 token 将不被包含在最终的结果之中。
下面我们来做一个例子,看看显示的结果:
POST search_suggest_complete/_search
{
"query" : {
"match": {
"sayt":{
"query": "比克"
}
}
}
}
下图是开发测试时候为了对比构建的index所以有很多没有意义字段自信忽略
比我我们有特殊要求,要让给了广告费的产品或者是近期新产品排在前面该如何实现(需求,让守护地球的战士排在前面)。我们通过使用function_score加权,这里为了演示新增一个字段。如果检测到type字段为1就代表当前产品给了广告费我们需要将它向前推
GET /search_suggest_complete/_search
{
"query": {
"function_score": {
"query": {
"multi_match": {
"query": "bike",
"type": "bool_prefix",
"fuzziness": 2,
"operator": "and",
"fields": [
"sayt",
"sayt._2gram",
"sayt._3gram",
"sayt._index_prefix"
]
}
},
"functions": [
{
"filter": {
"term": {
"type": 1
}
},
"weight": 2
}
],
"boost": 1,
"score_mode": "sum"
}
}
}
可以看到此时守护地球比克分数直接翻了一倍到达了搜索建议的最上方,类似需求都换汤不换药都可以采用function_score实现。一个简单的搜索建议大概就是这些,如需拼音搜索建议记得指定分析器中加入pingyin其它的慢慢探索吧。