Elasticsearch:构建自动补全功能 - Autocomplete

news2025/9/10 14:57:09

什么是自动补全(autocomplete)功能呢?我们举一个很常见的例子。 每当你去谷歌并开始打字时,就会出现一个下拉列表,其中列出了建议。 这些建议与查询相关并帮助用户完成查询。

 Autocomplete 正如维基百科所说的:

Autocomplete 或单词完成是一个功能,应用程序预测使用的其余单词正在键入

它也知道你键入或键入前方搜索。 它通过提示用户在键入文本的可能性和替代方案来帮助他们导航或指导用户。 它减少用户在执行任何搜索操作之前需要输入的字符数量,从而增强用户的搜索体验。

可以通过使用任何数据库来实现 Autocomplete。 在这篇文章中,我们将使用 Elasticsearch 构建自动补全功能。

Elasticsearch 是免费及开发,分布式和基于JSON的搜索引擎,建立在Lucene的顶部。更多关于 Elasticsearch 的介绍,请阅读文章 “Elasticsearch 简介”。

方法

在 Elasticsearch 中,可能有多种构建自动完成功能的方法。 我们将讨论以下方法。

  • Prefix query
  • Edge ngram
  • Completion suggester

Prefiex query

这种方法涉及使用针对自定义字段的前缀查询(prefix query)。 该字段的值可以作为 keyword 存储,因此将多个 terms(单词)存储在一起作为一个术语。 可以使用关键字分词器(keyword tokenizer)来完成这一点。 这种方法遭受了缺点:

  • 由于只支持词首匹配,不能匹配正文中间的 query。
  • 这种类型的查询未针对大型数据集进行优化,可能会导致延迟增加。
  • 由于这是一个查询,因此不会过滤掉重复的结果。 处理此方法的一种解决方法是使用聚合查询对结果进行分组,然后过滤掉结果。 这涉及服务器端的一些处理

Edge Ngrams

有关 edge ngrams 的介绍请参阅之前的文章 “Elasticsearch: Ngrams, edge ngrams, and shingles”。

这种方法涉及在索引和搜索时使用不同的分析器。 索引文档时,可以应用带有 edge n-gram 过滤器的自定义分析器。 在搜索时,可以应用标准分析器。 这可以防止查询被拆分。

Edge N-gram tokeniser 首先将文本分解为自定义字符(空格、特殊字符等)上的单词,然后仅从字符串的开头保留 n-gram。

这种方法也适用于匹配文本中间的查询。 这种方法通常查询速度很快,但可能会导致索引速度变慢和索引存储量变大。

Completion suggester

Elasticsearch 附带一个名为 Completion Suggester 的内部解决方案。 它使用称为有限状态传感器 (Finite State Transducer - FST) 的内存数据结构。 Elasticsearch 以每个段为基础存储 FST,这意味着建议会随着更多新节点的添加而水平扩展。

实施 Completion Suggester 时要记住的一些事情

  • Autosuggest 项应将 completion 类型作为其字段类型。
  • 输入字段可以为单个术语具有各种规范名称或别名。
  • 可以为每个文档定义权重以控制它们的排名。
  • 以小写形式存储所有术语有助于不区分大小写的匹配。
  • 可以启用上下文 suggesters 以支持按特定标准进行过滤或提升。

这种方法是实现自动完成功能的理想方法,但是,它也有一些缺点

  • 匹配总是从文​​本的开头开始。 所以在 movies 数据集中搜索 america 不会产生任何结果。 一种克服方法是在空格上标记输入文本并将所有短语保留为规范名称。 这样 Captain America: Civil War:内战将被存储为:
Captain America: Civil War
America: Civil War
Civil War
War

不支持突出(highlight)显示匹配的词。

没有可用的排序机制。 对建议进行排序的唯一方法是通过权重。 当需要任何自定义排序(如字母排序或按上下文排序)时,这会产生问题。

实现

让我们在 Elasticsearch 中实现上述方法。 我们将使用 movies 数据来构建我们的示例索引。 为了便于参考,我们使用如下的命令来创建 movies 索引:

PUT movies
{
  "settings": {
    "index": {
      "analysis": {
        "filter": {},
        "analyzer": {
          "keyword_analyzer": {
            "filter": [
              "lowercase",
              "asciifolding",
              "trim"
            ],
            "char_filter": [],
            "type": "custom",
            "tokenizer": "keyword"
          },
          "edge_ngram_analyzer": {
            "filter": [
              "lowercase"
            ],
            "tokenizer": "edge_ngram_tokenizer"
          },
          "edge_ngram_search_analyzer": {
            "tokenizer": "lowercase"
          }
        },
        "tokenizer": {
          "edge_ngram_tokenizer": {
            "type": "edge_ngram",
            "min_gram": 2,
            "max_gram": 5,
            "token_chars": [
              "letter"
            ]
          }
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "fields": {
          "keywordstring": {
            "type": "text",
            "analyzer": "keyword_analyzer"
          },
          "edgengram": {
            "type": "text",
            "analyzer": "edge_ngram_analyzer",
            "search_analyzer": "edge_ngram_search_analyzer"
          },
          "completion": {
            "type": "completion"
          }
        },
        "analyzer": "standard"
      }
    }
  }
}

如果我们看到映射,我们会发现 name 是一个 multi-fields 字段,其中包含多个字段,每个字段都以不同的方式进行分析。

  • 使用关键字分词器分析 Fieldname.keywordstring,因此它将用于前缀查询方法
  • 字段 name.edgengram 使用 Edge Ngram 分词器进行分析,因此它将用于 Edge Ngram 方法。
  • Field name.completion 存储为 completion 类型,因此它将用于 Completion Suggester。

我们使用如下命令索引所有的电影:

POST movies/_bulk
{ "index" : {"_id" : "1"} }
{ "name" : "Spider-Man: Homecoming" }
{ "index" : {"_id" : "2"} }
{ "name" : "Ant-man and the Wasp" }
{ "index" : {"_id" : "3"} }
{ "name" : "Avengers: Infinity War Part 2" }
{ "index" : {"_id" : "4"} }
{ "name" : "Captain Marvel" }
{ "index" : {"_id" : "5"} }
{ "name" : "Black Panther" }
{ "index" : {"_id" : "6"} }
{ "name" : "Avengers: Infinity War" }
{ "index" : {"_id" : "7"} }
{ "name" : "Thor: Ragnarok" }
{ "index" : {"_id" : "8"} }
{ "name" : "Guardians of the Galaxy Vol 2" }
{ "index" : {"_id" : "9"} }
{ "name" : "Doctor Strange" }
{ "index" : {"_id" : "10"} }
{ "name" : "Captain America: Civil War" }
{ "index" : {"_id" : "11"} }
{ "name" : "Ant-Man" }
{ "index" : {"_id" : "12"} }
{ "name" : "Avengers: Age of Ultron" }
{ "index" : {"_id" : "13"} }
{ "name" : "Guardians of the Galaxy" }
{ "index" : {"_id" : "14"} }
{ "name" : "Captain America: The Winter Soldier" }
{ "index" : {"_id" : "15"} }
{ "name" : "Thor: The Dark World" }
{ "index" : {"_id" : "16"} }
{ "name" : "Iron Man 3" }
{ "index" : {"_id" : "17"} }
{ "name" : "Marvel’s The Avengers" }
{ "index" : {"_id" : "18"} }
{ "name" : "Captain America: The First Avenger" }
{ "index" : {"_id" : "19"} }
{ "name" : "Thor" }
{ "index" : {"_id" : "20"} }
{ "name" : "Iron Man 2" }
{ "index" : {"_id" : "21"} }
{ "name" : "The Incredible Hulk" }
{ "index" : {"_id" : "22"} }
{ "name" : "Iron Man" }

让我们从 prefix query 方法开始,尝试查找以 th 开头的电影。

查询将是:

GET movies/_search?filter_path=**.hits
{
  "query": {
    "prefix": {
      "name.keywordstring": {
        "value": "th"
      }
    }
  }
}

查询的结果是:

{
  "hits": {
    "hits": [
      {
        "_index": "movies",
        "_id": "7",
        "_score": 1,
        "_source": {
          "name": "Thor: Ragnarok"
        }
      },
      {
        "_index": "movies",
        "_id": "15",
        "_score": 1,
        "_source": {
          "name": "Thor: The Dark World"
        }
      },
      {
        "_index": "movies",
        "_id": "19",
        "_score": 1,
        "_source": {
          "name": "Thor"
        }
      },
      {
        "_index": "movies",
        "_id": "21",
        "_score": 1,
        "_source": {
          "name": "The Incredible Hulk"
        }
      }
    ]
  }
}

结果是公平的,但是像 Captain America: The Winter SoldierGuardians of the Galaxy 这样的电影被遗漏了,因为前缀查询只匹配文本的开头而不是中间。

让我们尝试寻找另一部以 am 开头的电影。

GET movies/_search?filter_path=**.hits
{
  "query": {
    "prefix": {
      "name.keywordstring": {
        "value": "am"
      }
    }
  }
}

这里我们没有得到任何结果,尽管 Captain America 满足这个条件。 这就印证了Prefix query 不能用于正文中间匹配的一点。

让我们运行相同的搜索,但使用 Edge Ngram 方法。

GET movies/_search?filter_path=**.hits
{
  "query": {
    "match": {
      "name.edgengram": "am"
    }
  }
}

上面运行的结果是:

{
  "hits": {
    "hits": [
      {
        "_index": "movies",
        "_id": "10",
        "_score": 1.5922177,
        "_source": {
          "name": "Captain America: Civil War"
        }
      },
      {
        "_index": "movies",
        "_id": "14",
        "_score": 1.3930962,
        "_source": {
          "name": "Captain America: The Winter Soldier"
        }
      },
      {
        "_index": "movies",
        "_id": "18",
        "_score": 1.3930962,
        "_source": {
          "name": "Captain America: The First Avenger"
        }
      }
    ]
  }
}

让我们再次尝试寻找 Captain America,但这次使用更大的短语 captain america the:

GET movies/_search?filter_path=**.hits
{
  "query": {
    "match": {
      "name.edgengram": "captain america the"
    }
  }
}

使用 Edge N-gram 方法,我们得到以下电影:

{
  "hits": {
    "hits": [
      {
        "_index": "movies",
        "_id": "21",
        "_score": 1.0249562,
        "_source": {
          "name": "The Incredible Hulk"
        }
      },
      {
        "_index": "movies",
        "_id": "17",
        "_score": 0.9822227,
        "_source": {
          "name": "Marvel’s The Avengers"
        }
      },
      {
        "_index": "movies",
        "_id": "2",
        "_score": 0.94290996,
        "_source": {
          "name": "Ant-man and the Wasp"
        }
      },
      {
        "_index": "movies",
        "_id": "13",
        "_score": 0.94290996,
        "_source": {
          "name": "Guardians of the Galaxy"
        }
      },
      {
        "_index": "movies",
        "_id": "15",
        "_score": 0.906623,
        "_source": {
          "name": "Thor: The Dark World"
        }
      },
      {
        "_index": "movies",
        "_id": "8",
        "_score": 0.8730254,
        "_source": {
          "name": "Guardians of the Galaxy Vol 2"
        }
      },
      {
        "_index": "movies",
        "_id": "14",
        "_score": 0.7365507,
        "_source": {
          "name": "Captain America: The Winter Soldier"
        }
      },
      {
        "_index": "movies",
        "_id": "18",
        "_score": 0.7365507,
        "_source": {
          "name": "Captain America: The First Avenger"
        }
      }
    ]
  }
}

如果我们观察我们的短语,只有两个建议是有意义的。 匹配这么多术语的原因是 match 子句的功能。 匹配包括所有包含 captain OR america OR the 的文件。 由于该字段是使用 ngram 分析的,因此也会包含更多建议(如果存在)。

让我们尝试使用针对相同短语 captain america the 的 suggest 查询。 建议查询的编写方式略有不同。

GET movies/_search?filter_path=suggest
{
  "suggest": {
    "movie-suggest": {
      "prefix": "captain america the",
      "completion": {
        "field": "name.completion"
      }
    }
  }
}

结果我们得到以下电影:

{
  "suggest": {
    "movie-suggest": [
      {
        "text": "captain america the",
        "offset": 0,
        "length": 19,
        "options": [
          {
            "text": "Captain America: The First Avenger",
            "_index": "movies",
            "_id": "18",
            "_score": 1,
            "_source": {
              "name": "Captain America: The First Avenger"
            }
          },
          {
            "text": "Captain America: The Winter Soldier",
            "_index": "movies",
            "_id": "14",
            "_score": 1,
            "_source": {
              "name": "Captain America: The Winter Soldier"
            }
          }
        ]
      }
    ]
  }
}

让我们尝试相同的查询,但这次使用了错别字 captain america the。

GET movies/_search?filter_path=suggest
{
  "suggest": {
    "movie-suggest": {
      "prefix": "captain amrica the",
      "completion": {
        "field": "name.completion"
      }
    }
  }
}

上面的电影建议没有返回结果,因为不支持模糊性。 我们可以通过以下方式更新查询以包含对模糊性的支持:

GET movies/_search?filter_path=suggest
{
  "suggest": {
    "movie-suggest": {
      "prefix": "captain amrica the",
      "completion": {
        "field": "name.completion",
        "fuzzy": {
          "fuzziness": 1
        }
      }
    }
  }
}

以上查询返回以下结果:

{
  "suggest": {
    "movie-suggest": [
      {
        "text": "captain amrica the",
        "offset": 0,
        "length": 18,
        "options": [
          {
            "text": "Captain America: The First Avenger",
            "_index": "movies",
            "_id": "18",
            "_score": 10,
            "_source": {
              "name": "Captain America: The First Avenger"
            }
          },
          {
            "text": "Captain America: The Winter Soldier",
            "_index": "movies",
            "_id": "14",
            "_score": 10,
            "_source": {
              "name": "Captain America: The Winter Soldier"
            }
          }
        ]
      }
    ]
  }
}

结论

可以使用多种方法在 ElasticSearch 中实现自动完成功能。 Completion Suggester 涵盖了实现功能齐全且快速的自动完成所需的大多数情况。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/387442.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vulnhub raven2复现

1.扫描全网段,找出了存活主机ip为192.168.85.144 nmap 192.168.85.0/24 2.nmap扫描端口 nmap -p1-65535 192.168.85.144 3.访问此网站,没找到什么地方可以利用漏洞 ,查看中间件为wordpress 4.使用dirb对该网站进行目录扫描 dirb http://1…

刷题笔记3 | 203. 移除链表元素、707设计链表,206.反转链表

目录 203. 移除链表元素 707、设计链表 206.反转链表 203. 移除链表元素 题意:删除链表中等于给定值 val 的所有节点。 示例 1: 输入:head [1,2,6,3,4,5,6], val 6 输出:[1,2,3,4,5] 示例 2: 输入:h…

18跨越语言:不同语言间进行RPC通信

在最开始介绍gRPC时我们讲到,gRPC具有灵活的兼容性,可以支持很多种编程语言,下面我们就使用在后端领域最常用的两种编程语言Go和Java,来体验一下gRPC在不同语言的项目间是如何进行通信的。 逻辑架构 由上图我们可以看出,Go语言设计gRPC的服务端,Java语言设计gRPC的客户端…

欢迎来到 BharatBox,这是一个以来自印度的知名艺术家和品牌为特色的文化元宇宙中心

通过 Brinc 的客户 Heftyverse 娱乐公司,将印度艺术家、电影制片厂、体育品牌和音乐公司聚集在这个全新虚拟中心。 The Sandbox 与 Brinc 的联营公司推出 BharatBox,这是一个全新的文化中心,由来自印度的娱乐、艺术和体育范畴的主要合作伙伴组…

吴恩达监督机器学习:回归和分类(一)

文章目录机器学习简介机器学习是什么quiz监督学习回归问题分类问题quiz无监督学习聚类问题quiz练习测试:监督学习与无监督学习线性回归线性回归模型第一部分基本术语线性回归模型第二部分quiz选学实验:线性回归模型代价函数公式quiz代价函数的直观理解qu…

SpringBoot+Seata在多数据源和feign中的简单使用

SpringBootSeata简单使用 目录seata执行过程安装seata下载seata使用自定义配置文件,NACOS为注册中心结合springboot实现AT模式1.多数据源引入依赖bootstrap.yml配置在使用的方法上用GlobalTransactional注解调用接口正常时调用接口报错时回滚2.配合feignseata优缺点seata执行过…

「并发编程实战」接口幂等性设计的最佳实现(8种实现方案)

「并发编程实战」接口幂等性设计的最佳实现(8种实现方案) 文章参考: 实战!聊聊幂等设计 基于幂等表思想的幂等实践 追忆四年前:一段关于我被外企CTO用登录注册吊打的不堪往事 弹力设计篇之“幂等性设计” 一、什么是幂…

【Spark分布式内存计算框架——Structured Streaming】2. Structured Streaming 核心设计与编程模型

核心设计 2016年,Spark在2.0版本中推出了结构化流处理的模块Structured Streaming,核心设计如下: 第一点:Input and Output(输入和输出) Structured Streaming 内置了很多 connector 来保证 input 数据源…

永春堂1300系统开发|解析永春堂1300模式商城的五大奖项

电商平台竞争越来越激烈,各种营销方式也是层出不穷,其中永春堂1300营销模式,以其无泡沫和自驱动性强等特点风靡一时。在这套模式中,虽然单型价格差异较大,但各种奖励的设计,巧妙的兼顾了平台和所有会员的利…

【C语言】float 关键字

🚩write in front🚩 🔎大家好,我是謓泽,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流🔎 🏅2021|2022年度博客之星物联网与嵌入式开发TOP5|TOP4…

编程课的PPT

编程课 第一讲 主讲: 郝老师 一 什么是程序? 二 程序能做什么? 三 程序的那些事 一 什么是程序? 计算机程序是一组计算机能识别和执行的指令 一 什么是程序? “计算机程序是一组计算机能识别和执行的指令”上面的这句话是啥意思呢…

【Java|golang】1487. 保证文件名唯一---golang中string方法的坑

给你一个长度为 n 的字符串数组 names 。你将会在文件系统中创建 n 个文件夹:在第 i 分钟,新建名为 names[i] 的文件夹。 由于两个文件 不能 共享相同的文件名,因此如果新建文件夹使用的文件名已经被占用,系统会以 (k) 的形式为新…

ACM---大一第三周周赛(Floyd算法+并查集算法学习周)

🚀write in front🚀 📝个人主页:认真写博客的夏目浅石.CSDN 🎁欢迎各位→点赞👍 收藏⭐️ 留言📝​ 📣系列专栏:ACM周训练题目合集.CSDN 💬总结&#xff1a…

【python】JSON数据类型与Python数据类型之间的转化

注:最后有面试挑战,看看自己掌握了吗 文章目录JSON格式文件JSON格式序列化与反序列化作用JSON常用数据结构键值对的集合值的有序列表JSON数据类型与Python数据类型之间的转化JSON格式和python的区别读写json文件dump 把python 写到json文件load 把json写…

王道计算机组成原理课代表 - 考研计算机 第七章 输入输出系统 究极精华总结笔记

本篇博客是考研期间学习王道课程 传送门 的笔记,以及一整年里对 计算机组成 知识点的理解的总结。希望对新一届的计算机考研人提供帮助!!! 关于对 “输入输出系统” 章节知识点总结的十分全面,涵括了《计算机组成原理》…

阿里云(CentOS)中MySQL8忘记密码的解决方法

阿里云(CentOS)中MySQL8忘记密码的解决方法 方法 在 skip-grant-tables 模式下启动 MySQL,该模式下启动 MySQL 时不启动授权表功能,可以直接免密码登录 实现 编辑 /etc/my.cnf 文件 vim /etc/my.cnf在 [mysqld] 区域末尾添加配置,设置免密…

数组之双指针题

文章目录一、最长连续不重复子序列1.题目介绍2.思路3.二、长度最小的子数组1.题目介绍2.思路3.代码三、数组元素的目标和1.题目介绍2.思路3.代码总结其实在之前我写过不少双指针得题解,刷题专练之数组移除元素 刷题专练之翻转题练习这两篇文章的题解基本就是双指针法…

Protobuf简介

Protobuf简介 1. Protocol Buffers1.1. 什么是Protocol Buffers?1.2. 选择你最喜欢的语言1.3. 如何开始2. Protocol Buffer Basics: C++2.1. 问题领域2.2. 在哪里找到示例代码2.3. 定义协议格式(Defining Your Protocol Format)1. Protocol Buffers Protocol Buffers(协议缓冲…

SpringBoot项目的快速创建方式(包含第一个程序的运行)

目录 一、IDEA所用的版本以及插件 二、操作步骤 一、IDEA所用的版本以及插件 idea的版本: idea2022版本下载安装配置与卸载详细步骤(包含运行第一个java程序教程)_idea2022下载_云边的快乐猫的博客-CSDN博客 如果英文看不懂就点击&#x1…

性能优化(1)-请求响应优化

一、请求和响应优化 目的:更快的内容到达时间。 1.减少DNS查找:每次主机名的解析都需要一次网络往返,从而增加了请求的延迟时间,同时还会阻塞后续的请求。 重用 TCP 连接:尽可能的使用持久连接,以消除因 TCP 握手和慢启动导致的延迟。 3.减…