• 在 Elasticsearch 中实现自动完成功能 1:Prefix queries


    自动完成与搜索功能不同 - 我们应该在用户键入下一个字符后立即更新自动完成选项,每秒都会访问数据库,过滤数百万条记录,而不会导致任何性能下降!

    Elasticsearch 是一种可以轻松实现此类功能的技术,它是一种基于 Apache Lucene 库构建的搜索和分析引擎。 Elasticsearch 具有分布式、多租户架构,具有内置路由和重新平衡功能,使其易于扩展。 它是一种广泛使用的数据存储,用于存储、搜索和分析大量数据。

    在这个由三部分组成的博客文章系列中,我将详细介绍如何使用 Elasticsearch 中提供的各种选项来实现自动完成功能。 在第一部分(即这篇文章)中,我们将讨论前缀查询 - prefix queries。 在第二部分中,我们将了解 n-grams,在最后部分中,我们将讨论 complete suggesters。

    出于示例目的,我们将使用存储电影数据的索引。 为了简单起见,title 将是该索引中唯一存在的属性。 由于 Elasticsearch 为其操作公开了 REST 接口,因此你可以使用任何基于 REST 的工具与其进行通信。

    本系列假设你对 Elasticsearch 有基本的了解。 如果你是 Elasticsearch 的新手,我强烈建议你阅读 “Elastic:开发者上手指南”。

    那么让我们开始吧?

    前缀查询 - Prefix queries

    前缀查询是 Elasticsearch 中自动完成实现的最简单形式。 我们在存储字段时不做任何特殊的事情,大部分工作都是在查询时完成的。 该字段被索引(存储!)为一个简单的文本/关键字字段,并且允许我们根据传递的前缀匹配文档的查询用于查询它。

    让我们创建一个索引来运行前缀查询:

    1. PUT /movies
    2. {
    3. "mappings": {
    4. "properties": {
    5. "title": {
    6. "type": "keyword",
    7. "fields": {
    8. "analyzed_title": {
    9. "type": "text"
    10. }
    11. }
    12. }
    13. }
    14. }
    15. }

    创建索引时,我们需要提供映射,指示我们打算存储的数据类型。 出于以下示例的目的,title 被映射为 keyword 字段,也被映射为支持全文查询的文本字段。 使用 Elasticsearch 的多字段功能可以将一个字段映射为多种类型。

    keyword 字段和 text 字段之间的主要区别在于关键字字段不被分析,即我们传递到关键字字段的数据按原样存储。 对文本字段进行分析,即分词化、可能进行转换(例如小写、词干等),并存储在倒排索引中。 倒排索引是一种数据结构,用于存储从术语到它们出现的文档位置的映射,从而实现高效的全文搜索。有关 keyword 和 text 类型的区别,请详细参阅文档 “Elasticsearch:Text vs. Keyword - 它们之间的差异以及它们的行为方式”。

    为了测试如何分析我们的数据,我们可以使用 _analyze API。 让我们看看我们的主标题字段将如何分析:

    1. GET /movies/_analyze
    2. {
    3. "text": "Chamber of Secrets",
    4. "field": "title"
    5. }

    上面命令的响应为:

    1. {
    2. "tokens": [
    3. {
    4. "token": "Chamber of Secrets",
    5. "start_offset": 0,
    6. "end_offset": 18,
    7. "type": "word",
    8. "position": 0
    9. }
    10. ]
    11. }

    因此,它只返回一个 token。 为什么? 没错,就是因为它是关键字字段! 让我们测试一下我们 analyzed_title 的表现:

    1. GET /movies/_analyze
    2. {
    3. "text": "Chamber of Secrets",
    4. "field": "title.analyzed_title"
    5. }

    上面命令的响应为:

    1. {
    2. "tokens": [
    3. {
    4. "token": "chamber",
    5. "start_offset": 0,
    6. "end_offset": 7,
    7. "type": "",
    8. "position": 0
    9. },
    10. {
    11. "token": "of",
    12. "start_offset": 8,
    13. "end_offset": 10,
    14. "type": "",
    15. "position": 1
    16. },
    17. {
    18. "token": "secrets",
    19. "start_offset": 11,
    20. "end_offset": 18,
    21. "type": "",
    22. "position": 2
    23. }
    24. ]
    25. }

    正如所料,它被分解为三个 token。 此外,token 是小写的。 这是为什么? 因为,即使我们不指定任何分析器,默认的标准分析器也会应用于执行基于语法的标记化的文本字段,并且还将这些标记小写。 文本分析是一种高度可配置的过程,由一个或多个字符过滤器、分词器以及一个或多个在管道中运行的分词过滤器组成。 我们可以创建自己的分析器,也可以定制内置分析器。有关分词器的详细介绍,请阅读文章 “Elasticsearch: analyzer”。

    让我们将一些哈利波特电影添加到我们的索引中,即让我们索引一些文档:

    1. POST /movies/_doc
    2. {
    3. "title": "Harry Potter and the Chamber of Secrets"
    4. }
    5. POST /movies/_doc
    6. {
    7. "title": "Harry Potter and the Prisoner of Azkaban"
    8. }

    让我们尝试使用前缀查询来查询我们的主 title 字段(关键字)。 前缀查询是术语级别查询的一种,用于查询非分析字段。 我们将尝试两个不同的请求 - 第一个请求使用 title 中第一个单词的前缀,另一个请求使用标题中第二个单词的前缀:

    1. GET /movies/_search?filter_path=**.hits
    2. {
    3. "query": {
    4. "prefix": {
    5. "title": "Harr"
    6. }
    7. }
    8. }

     上面的响应为:

    1. {
    2. "hits": {
    3. "hits": [
    4. {
    5. "_index": "movies",
    6. "_id": "er9oHIsByaLf0EuTh81O",
    7. "_score": 1,
    8. "_source": {
    9. "title": "Harry Potter and the Chamber of Secrets"
    10. }
    11. },
    12. {
    13. "_index": "movies",
    14. "_id": "e79oHIsByaLf0EuTjc3H",
    15. "_score": 1,
    16. "_source": {
    17. "title": "Harry Potter and the Prisoner of Azkaban"
    18. }
    19. }
    20. ]
    21. }
    22. }

    我们做另外一个查询:

    1. GET /movies/_search?filter_path=**.hits
    2. {
    3. "query": {
    4. "prefix": {
    5. "title": "Pott"
    6. }
    7. }
    8. }

    上述查询返回:

    1. {
    2. "hits": {
    3. "hits": []
    4. }
    5. }

    也即没有任何的结果。

    tilte 是关键字字段,我们必须提供具有正确大小写的前缀。 如果我们在查询中传递 “harr”,它将不匹配。 第一个请求按预期返回上面索引的两个文档。 但第二个请求不会返回任何内容。 这是因为这个查询不支持中缀(在 title 中间匹配)匹配。

    如果我们想在 title 内进行匹配,我们应该使用 match_phrase_prefix - 一种用于在分析的文本字段上进行前缀匹配的查询类型:

    1. GET /movies/_search?filter_path=**.hits
    2. {
    3. "query": {
    4. "match_phrase_prefix": {
    5. "title.analyzed_title": {
    6. "query": "pott"
    7. }
    8. }
    9. }
    10. }

    上述命令返回的结果为:

    1. {
    2. "hits": {
    3. "hits": [
    4. {
    5. "_index": "movies",
    6. "_id": "er9oHIsByaLf0EuTh81O",
    7. "_score": 0.18232156,
    8. "_source": {
    9. "title": "Harry Potter and the Chamber of Secrets"
    10. }
    11. },
    12. {
    13. "_index": "movies",
    14. "_id": "e79oHIsByaLf0EuTjc3H",
    15. "_score": 0.18232156,
    16. "_source": {
    17. "title": "Harry Potter and the Prisoner of Azkaban"
    18. }
    19. }
    20. ]
    21. }
    22. }

    当我们搜索 analyzed_title 时,“pott” 前缀与属于我们两个文档的标记 “potter” 匹配。 因此,两份文件均被召回。

    前缀乱序怎么办? 由于 title 中的单词被分词,我们期望 “potter harry” 与两个文档匹配。 但这是一个短语前缀查询,它尊重输入的顺序。 如果我们想要无序匹配,我们可以使用 match_bool_prefix。

    1. GET /movies/_search
    2. {
    3. "query": {
    4. "match_phrase_prefix": {
    5. "title.analyzed_title": {
    6. "query": "potter harry"
    7. }
    8. }
    9. }
    10. }

    上述查询将不会返回任何的结果。而如下的查询:

    1. GET /movies/_search?filter_path=**.hits
    2. {
    3. "query": {
    4. "match_bool_prefix": {
    5. "title.analyzed_title": {
    6. "query": "pott harr"
    7. }
    8. }
    9. }
    10. }

    将返回如下的结果:

    1. {
    2. "hits": {
    3. "hits": [
    4. {
    5. "_index": "movies",
    6. "_id": "er9oHIsByaLf0EuTh81O",
    7. "_score": 1,
    8. "_source": {
    9. "title": "Harry Potter and the Chamber of Secrets"
    10. }
    11. },
    12. {
    13. "_index": "movies",
    14. "_id": "e79oHIsByaLf0EuTjc3H",
    15. "_score": 1,
    16. "_source": {
    17. "title": "Harry Potter and the Prisoner of Azkaban"
    18. }
    19. }
    20. ]
    21. }
    22. }

    这就是我要讨论的使用前缀查询自动完成的全部内容。 在选择此作为实现自动完成功能的方法时,我们需要考虑一些事项:

    • 这是最不推荐的方法,与其他自动完成(另外的两篇文章)实现相比,这种方法被认为是最慢的方法。 搜索速度很慢,因为我们在索引字段时没有做任何有助于自动完成查询的工作。 它被索引为一个简单的文本字段,将文档与查询文本进行匹配的大部分工作都是在搜索时完成的。 它将转到倒排索引并检查是否有任何标记以查询中提供的文本开头,这是一项昂贵的操作。
    • 在 Elasticsearch 的最新版本中,为术语级别前缀查询添加了 index_prefixes 选项,该选项允许通过将前缀存储在单独的字段中来加速前缀查询。
    • 如果你已经有一个工作索引并且不需要更新映射,那么前缀查询将是适合你的方法,因为自动完成不是系统中频繁使用的功能之一。 但如果是这样,那么你可能会遇到性能问题。 最好使用本系列下一部分中讨论的方法之一并重新索引数据。

    如果你想了解这种方法的详细实现,请阅读 “Elasticsearch:创建一个 autocomplete 输入系统 - 前端 + 后端”。

  • 相关阅读:
    11、Python 闭包实现原理
    基于JAVA疫情防控期间人员档案追演示录像上计算机毕业设计源码+系统+mysql数据库+lw文档+部署
    一维卷积神经网络
    香港服务器怎么看是CN2 GT线路还是CN2 GIA线路?
    vue实现调用手机拍照、录像功能
    【HarmonyOS】HarmonyOS Test测试用例中一些断言API的使用
    LeetCode刷题(python版)——Topic63. 不同路径 II
    Log4j2配置属性详解(图+文+案例)
    【js】Object.assign
    Nexus私服,使用Maven上传到仓库时提示xx/metadata.xml响应码 502
  • 原文地址:https://blog.csdn.net/UbuntuTouch/article/details/133760818