vlambda博客
学习文章列表

读书笔记《elasticsearch-server-third-edition》超越全文搜索

Chapter 8. Beyond Full-text Searching

上一章完全致力于数据分析以及我们如何使用 Elasticsearch 执行它。我们学习了如何使用聚合、可用的聚合类型、每种类型中可用的聚合以及如何使用它们。在本章中,我们将回到查询相关的主题。在本章结束时,您将学习以下主题:

  • 什么是渗滤器以及如何使用它

  • Elasticsearch 的地理空间功能有哪些

  • 如何使用 Elasticsearch 建议器来使用和构建功能

  • 如何使用 Scroll API 高效地获取大量结果

Percolator


您有没有想过如果我们颠倒使用查询在 Elasticsearch 中查找文档的传统模型会发生什么?拥有一个文档并搜索与之匹配的查询是否有意义?毫不奇怪,这个模型非常有用,有一系列的解决方案。每当您对无限制的输入数据流进行操作时,您可以在其中搜索特定事件的发生情况,您可以使用这种方法。这可用于检测监控系统中的故障或“告诉我该商店何时可以提供具有定义标准的产品”功能。在本节中,我们将了解 Elasticsearch 过滤器的工作原理以及我们如何使用它来实现上述用例之一。

The index

在讨论渗透器功能时使用的所有示例中,我们将使用一个名为notifier的索引。上述索引是使用以下命令创建的:

curl -XPOST 'localhost:9200/notifier' -d '{
  "mappings": {
    "book" : {
      "properties" : {
        "title" : {
          "type" : "string"
        },
        "otitle" : {
          "type" : "string"
        },
        "year" : {
          "type" : "integer"
        },
        "available" : {
          "type" : "boolean"
        },
        "tags" : {
          "type" : "string",
          "index" : "not_analyzed"
        }
      }
    }
  }
}'

这很简单。它包含一个类型和五个字段,将在我们环游世界的过程中使用。

Percolator preparation

Elasticsearch 公开了 一种称为 .percolator 的特殊类型,它的处理方式不同。这意味着我们可以存储任何文档,也可以像在任何索引中的普通类型一样搜索它们。如果您查看任何 Elasticsearch 查询,您会注意到每个查询都是有效的 JSON 文档,这意味着我们也可以将其索引并存储为文档。问题是 percolator 允许我们反转搜索逻辑并搜索与给定文档匹配的查询。这是可能的,因为刚才讨论的两个特性:特殊的 .percolator 类型以及 Elasticsearch 中的查询是有效的 JSON 文档这一事实。

让我们回到 Chapter 2 中的 library 示例,索引您的数据,并尝试索引过滤器中的查询之一。我们假设当任何符合查询定义的标准的书籍可用时需要通知我们的用户。

查看以下 query1.json 文件,其中包含用户生成的示例查询:

{
  "query" : {
    "bool" : {
      "must" : {
        "term" : {
          "title" : "crime"
        }
      },
      "should" : {
        "range" : {
          "year" : {
            "gt" : 1900,
            "lt" : 2000
          }
        }
      },
      "must_not" : {
        "term" : {
          "otitle" : "nothing"
        }
      }
    }
  }
}

为了增强示例,我们还假设允许我们的用户使用我们假设的 用户界面定义过滤器。例如,我们的用户可能对 2010 年之前编写的可用书籍感兴趣。可以由这样的用户界面构建的示例查询如下所示(查询被写入 query2.json 文件):

{
  "query" : {
    "bool": {
      "must" : {
        "range" : {
          "year" : {
            "lt" : 2010
          }
        }
      },
      "filter" : {
        "term" : {
          "available" : true
        }
      }
    }
  }
}

现在,让我们在 percolator 中注册这两个查询(请注意,我们正在注册查询并且没有索引任何文档)。为此,我们将运行以下命令:

curl -XPUT 'localhost:9200/notifier/.percolator/1' -d @query1.json
curl -XPUT 'localhost:9200/notifier/.percolator/old_books' -d @query2.json

在前面的示例中,我们使用了两个完全不同的标识符。我们这样做是为了表明 我们可以使用最能描述查询的标识符。由我们决定希望以哪个名称注册查询。

我们现在可以使用我们的过滤器了。我们的应用程序将向 percolator 提供文档并检查是否有任何已注册的查询与文档匹配。这正是渗透器允许我们做的事情——反转搜索逻辑。我们不是索引文档并针对它们运行查询,而是存储查询并发送文档以查找匹配的查询。

让我们使用一个匹配两个存储查询的示例文档;它将具有所需的标题和发布日期,并会提及它当前是否可用。将此类文档发送到过滤器的命令如下所示:

curl -XGET 'localhost:9200/notifier/book/_percolate?pretty' -d '{
  "doc" : {
    "title": "Crime and Punishment",
    "otitle": "Преступлéние и наказáние",
    "author": "Fyodor Dostoevsky",
    "year": 1886,
    "characters": ["Raskolnikov", "Sofia Semyonovna Marmeladova"], 
      "tags": [],
    "copies": 0,
    "available" : true
  }
}'

正如我们预期的那样,两个查询都匹配,并且 Elasticsearch 响应包括匹配查询的标识符。这样的响应如下所示:

{
  "took" : 36,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "total" : 2,
  "matches" : [ {
    "_index" : "notifier",
    "_id" : "old_books"
  }, {
    "_index" : "notifier",
    "_id" : "1"
  } ]
}

这就像一个魅力。需要注意的一件非常重要的事情是此查询中使用的端点:_percolate。当我们想要使用渗滤器时,需要使用这个端点。 索引名称对应于存储查询的索引,类型与映射中定义的类型相同。

Note

响应格式包含有关索引和查询标识符的信息。当我们一次搜索多个索引时,会包含此信息。使用单个索引时,添加额外的查询参数 percolate_format=ids 将更改响应,如下所示:

  "matches" : [ "old_books", "1" ]

Getting deeper

因为在 percolator 中注册的查询实际上是文档,所以我们可以使用发送到 Elasticsearch 的普通查询来选择应该使用存储在 .percolator 类型中的哪些查询渗滤过程。这可能听起来很奇怪,但它确实提供了很多可能性。在我们的 库中,我们可以有多个用户组。让我们假设他们中的一些人有权借阅非常稀有的书籍,或者我们在城市中有几个分支机构,并且用户可以声明他或她想从哪里得到这本书。

让我们看看如何使用 percolator 来实现这些用例。为此,我们需要更新映射并包含分支信息。我们通过运行以下命令来做到这一点:

curl -XPOST 'localhost:9200/notifier/.percolator/_mapping' -d '{
  ".percolator" : {
    "properties" : {
      "branches" : {
        "type" : "string",
        "index" : "not_analyzed"
      }
    }
  }
}'

现在,为了注册查询,我们使用以下命令:

curl -XPUT 'localhost:9200/notifier/.percolator/3' -d '{
  "query" : {
    "term" : {
      "title" : "crime"
    }
  },
  "branches" : ["brA", "brB", "brD"]
}'

在前面的示例中,我们注册了一个显示用户兴趣的查询。我们假设的用户对 title 字段( term 查询对此负责)。他或她想从列出的三个分支之一借这本书。在指定映射时,我们定义 branches 字段是非分析字符串字段。我们现在可以在之前发送的文档中包含一个查询。让我们看看如何做到这一点。

我们的图书系统刚刚拿到了这本书,它已经准备好报告这本书并检查这本书是否对任何人感兴趣。为了检查这一点,我们发送了描述这本书的文档,并在这样的请求中添加了一个额外的查询——这个查询将限制用户只对 brB 分支感兴趣的用户.这样的请求如下所示:

curl -XGET 'localhost:9200/notifier/book/_percolate?pretty' -d '{
  "doc" : {
    "title": "Crime and Punishment",
    "otitle": "
Преступлéние и наказáние
",
    "author": "Fyodor Dostoevsky",
    "year": 1886,
    "characters": ["Raskolnikov", "Sofia Semyonovna Marmeladova"], 
      "tags": [],
    "copies": 0,
    "available" : true
  },
  "size" : 10,
  "filter" : {
    "term" : {
      "branches" : "brB"
    }
  }
}'

如果一切都正确执行,Elasticsearch 返回的响应应该如下所示(我们使用 3 作为标识符来索引我们的查询):

{
  "took" : 27,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "total" : 1,
  "matches" : [ {
    "_index" : "notifier",
    "_id" : "3"
  } ]
}

Controlling the size of returned results

当涉及到渗滤器时,结果的大小 会有所不同。单个文档匹配的查询越多,返回的结果就越多,Elasticsearch 将需要更多的内存。因此,还有一件事需要注意 - size 参数。它允许我们限制返回的匹配数。

Percolator and score calculation

在前面的示例中,我们使用单个术语查询过滤了查询,但我们根本没有考虑评分 过程。 Elasticsearch 允许我们在使用 percolator 时计算分数。让我们更改之前发送到过滤器的文档并对其进行调整,以便使用评分:

curl -XGET 'localhost:9200/notifier/book/_percolate?pretty' -d '{
  "doc" : {
    "title": "Crime and Punishment",
    "otitle": "Преступлéние и наказáние",
    "author": "Fyodor Dostoevsky",
    "year": 1886,
    "characters": ["Raskolnikov", "Sofia Semyonovna Marmeladova"], 
      "tags": [],
    "copies": 0,
    "available" : true
  },
  "size" : 10,
  "query" : {
    "term" : {
      "branches" : "brB"
    }
  },
  "track_scores" : true,
  "sort" : {
    "_score" : "desc"
  }
}'

如您所见,我们使用了 query 部分并包含一个附加的 track_scores 属性设置为 真的。这是必需的,因为默认情况下,Elasticsearch 不会因为性能原因计算 文档的分数。如果我们在渗透过程中需要分数,我们应该知道,在 CPU 处理能力方面,此类查询比不计算分数的查询要求稍高。

Note

在前面的示例中,我们告诉 Elasticsearch 根据分数降序对结果进行排序。这是打开 track_scores 时的默认行为,因此我们可以省略排序声明。在撰写本文时,按分数降序排序是唯一可用的选项。

Combining percolators with other functionalities

如果我们被允许使用查询连同发送的过滤文档,为什么我们不能使用其他 Elasticsearch 功能?当然,这是可能的。例如,以下 文档与聚合一起发送,结果将包括聚合计算:

curl -XGET 'localhost:9200/notifier/book/_percolate?pretty' -d '{
  "doc": {
    "title": "Crime and Punishment",
    "available": true
  },
  "aggs" : {
    "test" : {
      "terms" : {
        "field" : "branches"
      }
    }
  }
}'

正如我们所见,percolator 允许我们同时运行查询和聚合。查看以下示例文档:

curl -XGET 'localhost:9200/notifier/book/_percolate?pretty' -d '{
  "doc": {
    "title": "Crime and Punishment",
    "year": 1886,
    "available": true
  },
  "size" : 10,
  "highlight": {
    "fields": {
      "title": {}
    }
  }
}'

如您所见,它包含一个突出显示部分。 Elasticsearch 返回的响应片段如下所示:

  {
    "_index" : "notifier",
    "_id" : "3",
    "highlight" : {
      "title" : [ "<em>Crime</em> and Punishment" ]
    }
  }

Note

请注意,对于 percolator 功能支持的查询类型存在一些限制。在当前的实现中,percolator中没有父子关系,所以不能使用has_childtop_children has_parent

Getting the number of matching queries

有时您并不关心匹配的查询,您只需要匹配的 查询的数量。在这种情况下,针对标准 percolator 端点发送文档效率不高。 Elasticsearch 公开了 _percolate/count 端点以有效地处理此类情况。此类命令的示例如下:

curl -XGET 'localhost:9200/notifier/book/_percolate/count?pretty' -d '{
 "doc" : { ... }
 }'

Indexed document percolation

在渗透部分的最后一段,我们想向您展示另一件事 - 渗透已编入索引的文档的可能性。为此,我们需要对文档使用 GET 操作并提供有关应使用哪个渗透器索引的信息。让我们看看下面的命令:

curl -XGET 'localhost:9200/library/book/1/_percolate?percolate_index=notifier'

此命令检查 1 标识符来自 library 索引的文档与 percolate_index 参数。请记住,默认情况下,Elasticsearch 将在与文档相同的索引中使用过滤器;这就是我们指定 percolate_index 参数的原因。

Elasticsearch spatial capabilities


Elasticsearch等搜索服务器通常是从全文搜索的角度来看的。 Elasticsearch 作为 ELK(Elasticsearch、Logstash 和 Kibana)的一部分进行营销,也因其能够处理大量时间序列数据而闻名。然而,这只是整体观点的一部分。有时,提到的两个用例都不够。想象一下搜索本地服务。对于最终用户来说,最重要的是结果的准确性。准确性,我们不仅指全文搜索的正确结果,还指结果在位置方面尽可能接近。在某些情况下,这与对城市或街道等地名的文本搜索相同,但在其他情况下,我们会发现它非常有用能够根据我们索引文件的地理坐标进行搜索。这也是 Elasticsearch 能够处理的功能。

随着 Elasticsearch 2.2 的发布,geo_point 类型发生了很多变化,特别是在内部完成了所有优化。在 2.2 之前,geo_point 类型作为两个未分析的字符串值存储在索引中,这发生了变化。随着 Elasticsearch 2.2 的发布,geo_point 类型得到了 Apache Lucene 库的所有重大改进,现在更加高效。

Mapping preparation for spatial searches

为了讨论 空间搜索功能,让我们准备一个包含城市列表的索引。这将是一个非常简单的索引,其中包含一种名为 poi(代表兴趣点)、城市名称及其坐标的类型。映射如下:

{
  "mappings" : {
    "poi" : {
      "properties" : {
        "name" : { "type" : "string" },
        "location" : { "type" : "geo_point" }
      }
    }
  }
}

假设我们将此定义放入 mapping1.json 文件中,我们可以通过运行以下命令来创建索引:

curl -XPUT localhost:9200/map -d @mapping1.json

上述映射中唯一的新事物是 geo_point 类型,用于 location 字段。通过使用它,我们可以存储我们城市的地理位置并使用基于空间的功能。

Example data

我们的示例 documents1.json 包含文档的文件如下所示:

{ "index" : { "_index" : "map", "_type" : "poi", "_id" : 1 }}
{ "name" : "New York", "location" : "40.664167, -73.938611" }
{ "index" : { "_index" : "map", "_type" : "poi", "_id" : 2 }}
{ "name" : "London", "location" : [-0.1275, 51.507222] }
{ "index" : { "_index" : "map", "_type" : "poi", "_id" : 3 }}
{ "name" : "Moscow", "location" : { "lat" : 55.75, "lon" : 37.616667 }}
{ "index" : { "_index" : "map", "_type" : "poi", "_id" : 4 }}
{ "name" : "Sydney", "location" : "-33.859972, 151.211111" }
{ "index" : { "_index" : "map", "_type" : "poi", "_id" : 5 }}
{ "name" : "Lisbon", "location" : "eycs0p8ukc7v" }

为了执行批量请求,我们添加了有关我们文档的索引名称、类型和唯一标识符的信息;因此,我们现在可以使用以下命令轻松导入此数据:

curl -XPOST localhost:9200/_bulk --data-binary @documents1.json

我们应该仔细研究的一件事是 location 字段。我们可以使用各种符号进行协调。我们可以将纬度和经度值提供为字符串、一对数字或对象。请注意,提供地理位置的字符串和数组方法对纬度和经度参数有不同的顺序。最后一条记录表明,也有可能将协调作为 Geohash 值(符号是,详细描述在 http://en.wikipedia.org/wiki/Geohash)。

Additional geo_field properties

随着 Elasticsearch 2.2 的发布,geo_point 类型可以接受的参数数量已经减少,并且如下:

  • geohash:布尔型参数告诉Elasticsearch是否.geohash 应该创建字段。默认为 false,除非使用 geohash_prefix

  • geohash_precisiongeohash 的最大 大小class="literal">geohash_prefix

  • geohash_prefix:布尔值 参数告诉 Elasticsearch 索引 geohash 及其前缀。默认为 false

  • ignore_malformed:布尔 参数告诉 Elasticsearch 忽略写得不好的 geo_field< /code> 指向而不是拒绝整个文档。默认为 false,这意味着格式错误的 geo_field 数据将导致整个文档的索引错误。

  • lat_lon: Boolean 参数告诉 Elasticsearch 在两个名为 .lat.lon。默认为 false

  • precision_step:参数允许控制我们的数字地理点将如何被索引。

请记住,出于向后兼容的原因,未删除与 geohash 字段相关和 lat_lon 字段相关的属性。用户仍然可以使用它们。但是,查询不会使用它们,而是使用高度优化的数据结构,该结构在索引期间由 geo_point 类型构建。

Sample queries

现在让我们看看在需要地理数据搜索和全文搜索的现代应用程序中使用坐标和解决常见要求的几个示例。

Note

如果您对 Elasticsearch 用户可用的所有地理空间查询感兴趣,请参阅 https://www.elastic.co/guide/en/elasticsearch/reference/当前/地理查询.html

Distance-based sorting

让我们从一个非常常见的要求开始:按到给定点的距离对返回的结果进行排序。在我们的示例中,我们想要获取所有城市,并按照它们与法国首都巴黎的距离对它们进行排序。为此,我们向 Elasticsearch 发送以下查询:

curl -XGET localhost:9200/map/_search?pretty -d '{
  "query" : {
    "match_all" : {}
  },
  "sort" : [{
    "_geo_distance" : {
      "location" : "48.8567, 2.3508",
      "unit" : "km"
    }
  }]
}'

如果您还记得 第 4 章中的 数据排序 部分, Extending Your Querying Knowledge,你会注意到格式略有不同。我们使用 _geo_distance 键来指示按距离排序。我们必须给出基本位置(location 属性,在我们的例子中保存了巴黎的位置信息),我们需要指定可以在结果。可用的值是 kmmi,分别代表公里和英里。此类查询的结果如下:

{
  "took" : 5,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 5,
    "max_score" : null,
    "hits" : [ {
      "_index" : "map",
      "_type" : "poi",
      "_id" : "2",
      "_score" : null,
      "_source" : {
        "name" : "London",
        "location" : [ -0.1275, 51.507222 ]
      },
      "sort" : [ 343.17487356850313 ]
    }, {
      "_index" : "map",
      "_type" : "poi",
      "_id" : "5",
      "_score" : null,
      "_source" : {
        "name" : "Lisbon",
        "location" : "eycs0p8ukc7v"
      },
      "sort" : [ 1452.9506736367805 ]
    }, {
      "_index" : "map",
      "_type" : "poi",
      "_id" : "3",
      "_score" : null,
      "_source" : {
        "name" : "Moscow",
        "location" : {
          "lat" : 55.75,
          "lon" : 37.616667
        }
      },
      "sort" : [ 2483.837565935267 ]
    }, {
      "_index" : "map",
      "_type" : "poi",
      "_id" : "1",
      "_score" : null,
      "_source" : {
        "name" : "New York",
        "location" : "40.664167, -73.938611"
      },
      "sort" : [ 5832.645958617513 ]
    }, {
      "_index" : "map",
      "_type" : "poi",
      "_id" : "4",
      "_score" : null,
      "_source" : {
        "name" : "Sydney",
        "location" : "-33.859972, 151.211111"
      },
      "sort" : [ 16978.094780773998 ]
    } ]
  }
}

与其他排序示例一样,Elasticsearch 显示有关用于排序的值的信息。让我们看一下 突出显示的记录。正如我们所见,巴黎和伦敦之间的距离约为343 km,如果您查看传统地图,您会发现这是真的。

Bounding box filtering

我们要展示的下一个示例是将结果缩小到以给定矩形为边界的选定区域。如果我们想在地图上显示结果或者当我们允许用户标记地图区域进行搜索时,这非常方便。您已经在 章节的 过滤结果 部分阅读了有关过滤器的内容4扩展你的查询知识,但是我们没有提到空间过滤器。以下查询显示了我们如何使用边界框进行过滤:

curl -XGET localhost:9200/map/_search?pretty -d '{
  "query" : {
    "bool" : {
      "must" : { "match_all": {}},
      "filter" : {
        "geo_bounding_box" : {
          "location" : {
            "top_left" : "52.4796, -1.903",
            "bottom_right" : "48.8567, 2.3508"
          }
        }
      }
    }
  }
}'

在前面的示例中,我们通过提供左上角和右下角坐标选择了伯明翰和巴黎之间的地图片段。这两个角足以指定我们想要的任何矩形,Elasticsearch 将为我们完成剩下的计算。以下屏幕截图显示了地图上的指定矩形:

读书笔记《elasticsearch-server-third-edition》超越全文搜索

正如我们所见,我们数据中唯一符合条件的城市是伦敦。因此,让我们通过运行前面的查询来检查 Elasticsearch 是否知道这一点。现在让我们看看返回的结果:

{
  "took" : 38,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 1.0,
    "hits" : [ {
      "_index" : "map",
      "_type" : "poi",
      "_id" : "2",
      "_score" : 1.0,
      "_source" : {
        "name" : "London",
        "location" : [ -0.1275, 51.507222 ]
      }
    } ]
  }
}

如您所见,Elasticsearch 再次同意该地图。

Limiting the distance

最后一个示例 显示了下一个常见要求:将结果限制在距离给定点不超过定义距离的位置。例如,如果我们想将结果限制在距离巴黎 500km 半径范围内的所有城市,我们可以使用以下查询:

curl -XGET localhost:9200/map/_search?pretty -d '{
  "query" : {
    "bool" : {
      "must" : { "match_all": {}},
      "filter" : {
        "geo_distance" : {
          "location" : "48.8567, 2.3508",
          "distance" : "500km"
        }
      }
    }
  }
}'

如果一切顺利,Elasticsearch 应该只为前面的查询返回一条记录,并且该记录应该又是伦敦。但是,我们将把它留给您作为读者检查。

Arbitrary geo shapes

有时,使用 单个地理点或单个矩形是不够的。在这种情况下,需要更复杂的东西,而 Elasticsearch 通过为您提供定义形状的可能性来解决这个问题。为了向您展示我们如何在 Elasticsearch 中利用自定义形状限制,我们需要修改我们的索引或创建一个新索引并引入 geo_shape 类型。我们的新映射如下所示(我们将使用它来创建一个名为 map2 的索引):

{
  "mappings" : {
    "poi" : {
      "properties" : {
        "name" : { "type" : "string", "index": "not_analyzed" },
        "location" : { "type" : "geo_shape" }
      }
    }
  }
}

假设我们将前面的映射定义写入 mapping2.json 文件,我们可以使用以下命令创建索引:

curl -XPUT localhost:9200/map2 -d @mapping2.json

Note

Elasticsearch 允许我们为 geo_shape 类型设置几个属性。最常用的是 precision 参数。在索引期间,必须将形状转换为一组术语。要求的精度越高,生成的术语就越多,这直接反映在索引大小和性能上。精度可以用以下单位定义:ininchyd , , mi, 英里, km, 公里, m, , < code class="literal">cm, 厘米, 或 mm, 毫米。默认情况下,精度设置为 50m

接下来,让我们更改 我们的示例数据以匹配我们的新索引结构并创建 documents2.json 文件以下内容:

{ "index" : { "_index" : "map2", "_type" : "poi", "_id" : 1 }}
{ "name" : "New York", "location" : { "type": "point", "coordinates": [-73.938611, 40.664167] }}
{ "index" : { "_index" : "map2", "_type" : "poi", "_id" : 2 }}
{ "name" : "London", "location" : { "type": "point", "coordinates": [-0.1275, 51.507222] }}
{ "index" : { "_index" : "map2", "_type" : "poi", "_id" : 3 }}
{ "name" : "Moscow", "location" : { "type": "point", "coordinates": [ 37.616667, 55.75]}}
{ "index" : { "_index" : "map2", "_type" : "poi", "_id" : 4 }}
{ "name" : "Sydney", "location" : { "type": "point", "coordinates": [151.211111, -33.865143]}}
{ "index" : { "_index" : "map2", "_type" : "poi", "_id" : 5 }}
{ "name" : "Lisbon", "location" : { "type": "point", "coordinates": [-9.142685, 38.736946] }}

geo_shape类型的字段结构与geo_point不同。它在语法上称为 GeoJSON (http://en.wikipedia.org/wiki/GeoJSON)。它允许我们定义各种地理类型。现在是时候索引我们的数据了:

curl -XPOST localhost:9200/_bulk --data-binary @documents2.json

让我们总结一下我们在查询过程中可以使用的类型,至少是我们认为最有用的类型。

Point

当第一个元素是经度,第二个元素是纬度时, 表定义了一个点。这种形状的一个例子如下:

{
  "type": "point",
  "coordinates": [-0.1275, 51.507222]
}

Envelope

一个信封定义了一个由盒子左上角和右下角坐标给出的盒子。这种形状的一个例子如下:

{
  "type": "envelope",
  "coordinates": [[ -0.087890625, 51.50874245880332 ], [ 2.4169921875, 48.80686346108517 ]]
}

Polygon

多边形定义了一个点列表,这些点被连接以创建我们的多边形。 数组中的第一个点和最后一个点必须相同,这样形状才会闭合。这种形状的一个例子如下:

{
  "type": "polygon",
  "coordinates": [[
    [-5.756836, 49.991408],
    [-7.250977, 55.124723],
    [1.845703, 51.500194],
    [-5.756836, 49.991408]
  ]]
}

如果您仔细查看形状定义,您会发现补充级别的表格。多亏了这一点,您可以定义多个多边形。在这种情况下,第一个多边形定义基本形状,其余多边形是将从基本形状中排除的形状。

Multipolygon

multipolygon 形状 允许我们创建由多个多边形组成的形状。这种形状的一个例子如下:

{
  "type": "multipolygon",
  "coordinates": [
    [[
       [-5.756836, 49.991408],
       [-7.250977, 55.124723],
       [1.845703, 51.500194],
       [-5.756836, 49.991408]
    ]], [[ 
       [-0.087890625, 51.50874245880332],
       [2.4169921875, 48.80686346108517],
       [3.88916015625, 51.01375465718826],
       [-0.087890625, 51.50874245880332]
    ]] ]
}

multipolygon 形状包含多个多边形,并且属于与 polygon 类型相同的规则。因此,我们可以有多个多边形,除此之外,我们还可以包含多个排除 形状。

An example usage

现在我们有了 index 和 geo_shape 字段,我们可以检查哪些城市位于英国.允许我们执行此操作的查询如下所示:

curl -XGET localhost:9200/map2/_search?pretty -d '{
  "query" : {
    "bool" : {
      "must" : { "match_all": {}},
      "filter": {
        "geo_shape": {
          "location": {
            "shape": {
              "type": "polygon",
              "coordinates": [[
                [-5.756836, 49.991408], [-7.250977, 55.124723],
                [-3.955078, 59.352096], [1.845703, 51.500194],
                [-5.756836, 49.991408]
              ]]
            }
          }
        }
      }
    }
  }
}'

polygon 类型定义了英国的边界(以一种非常非常不精确的方式),而 Elasticsearch 的回应如下:

{
  "took" : 7,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 1.0,
    "hits" : [ {
      "_index" : "map2",
      "_type" : "poi",
      "_id" : "2",
      "_score" : 1.0,
      "_source" : {
        "name" : "London",
        "location" : {
          "type" : "point",
          "coordinates" : [ -0.1275, 51.507222 ]
        }
      }
    } ]
  }
}

据我们所知,答案是正确的。

Storing shapes in the index

通常,shape 定义很复杂,并且定义的区域不会经常更改(例如,英国的边界)。在这种情况下,在索引中定义形状并在查询中使用它们很方便。这是可能的,我们现在将讨论如何做到这一点。像往常一样,我们将从适当的映射开始,如下所示:

{
  "mappings" : {
    "country": {
      "properties": {
        "name": { "type": "string", "index": "not_analyzed" },
        "area": { "type": "geo_shape" }
      }
    }
  }
}

此映射类似于之前使用的映射。我们只更改了字段名称并将其保存在 mapping3.json 文件中。让我们通过运行以下命令来创建一个新索引:

curl -XPUT localhost:9200/countries -d @mapping3.json

我们将使用的示例数据 如下所示(存储在名为 documents3.json 的文件中):

{"index": { "_index": "countries", "_type": "country", "_id": 1 }}
{"name": "UK", "area": {"type": "polygon", "coordinates": [[ [-5.756836, 49.991408], [-7.250977, 55.124723], [-3.955078, 59.352096], [1.845703, 51.500194], [-5.756836, 49.991408] ]]}}
{"index": { "_index": "countries", "_type": "country", "_id": 2 }}
{"name": "France", "area": { "type":"polygon", "coordinates": [ [ [ 3.1640625, 42.09822241118974 ], [ -1.7578125, 43.32517767999296 ], [ -4.21875, 48.22467264956519 ], [ 2.4609375, 50.90303283111257 ], [ 7.998046875, 48.980216985374994 ], [ 7.470703125, 44.08758502824516 ], [ 3.1640625, 42.09822241118974 ] ] ] }}
{"index": { "_index": "countries", "_type": "country", "_id": 3 }}
{"name": "Spain", "area": { "type": "polygon", "coordinates": [ [ [ 3.33984375, 42.22851735620852 ], [ -1.845703125, 43.32517767999296 ], [ -9.404296875, 43.19716728250127 ], [ -6.6796875, 41.57436130598913 ], [ -7.3828125, 36.87962060502676 ], [ -2.109375, 36.52729481454624 ], [ 3.33984375, 42.22851735620852 ] ] ] }}

要索引数据,我们只需要运行以下命令:

curl -XPOST localhost:9200/_bulk --data-binary @documents3.json

正如您在数据中看到的,每个文档都包含一个 polygon 类型。多边形定义了给定国家的面积(同样,它远非准确)。如果您还记得,形状的第一个点需要与最后一个点相同,这样形状才能闭合。现在,让我们更改查询以包含索引中的形状。我们的新查询如下所示:

curl -XGET localhost:9200/map2/_search?pretty -d '{
  "query" : {
    "bool" : {
      "must" : { "match_all": {}},
      "filter": {
        "geo_shape": {
          "location": {
            "indexed_shape": {
              "index": "countries",
              "type": "country",
              "path": "area",
              "id": "1"
            }
          }
        }
      }
    }
  }
}'

比较这两个查询时,我们可以注意到 shape 对象更改为 indexed_shape。我们需要告诉 Elasticsearch 在哪里寻找这个形状。我们可以通过定义索引(index 属性,默认为 shape)、类型(type 属性)和路径(path 属性,默认为 shape) .缺少的一项是形状的 id 属性。在我们的例子中,这是 1。但是,如果您想索引更多形状,我们建议您使用名称作为标识符来索引形状。

Using suggesters


很久以前,从 Elasticsearch 0.90(2013 年 4 月 29 日发布)开始,我们就可以使用所谓的建议器了。我们可以将 suggester 定义为允许我们 更正用户的拼写错误并构建自动完成功能,牢记性能。本节专门介绍这些功能,并将帮助您了解它们。我们将讨论每种可用的建议器类型,并展示允许我们控制它们的最常见的属性。但是,请记住,本节并不是描述每个属性的综合指南。关于建议者的所有细节的描述是一个非常广泛的话题,超出了本书的范围。如果您想深入了解它们的功能,请参阅 Elasticsearch 官方文档 文档(https://www.elastic.co/guide/en/elasticsearch/reference/current/search-suggesters.html ) 或 Packt Publishing 出版的 Mastering Elasticsearch Second Edition 一书。

Available suggester types

自从最初将 Suggest API 引入 Elasticsearch 以来,这些都发生了变化。我们现在可以使用四种 类型的建议器:

  • term:建议器返回 对传递给它的每个单词的更正。对于不是短语的建议很有用,例如单词查询。

  • phrase:建议者处理短语,返回正确的短语。

  • completion:旨在提供快速高效的自动完成 结果的建议器。

  • context:扩展 Elasticsearch 的 Suggest API。允许我们在内存中处理部分 suggest 查询,因此在性能方面非常有效。

Including suggestions

现在让我们尝试获取 建议以及查询结果。例如,让我们使用 match_all 查询并尝试获取关于 serlock holnes 短语的建议,该短语有两个拼写错误的术语。为此,我们运行以下命令:

curl -XGET 'localhost:9200/library/_search?pretty' -d '{
 "query" : {
  "match_all" : {}
 },
 "suggest" : {
  "first_suggestion" : {
   "text" : "serlock holnes",
   "term" : {
    "field" : "_all"
   }
  }
 }
}'

如您所见,我们在查询中引入了一个新部分——suggest 部分。我们已经使用 text 属性指定了我们想要更正的文本。我们已经指定了我们想要使用的建议器(术语一)并配置它指定应该用于使用 field 属性构建建议的字段名称。 first_suggestion 是我们给建议者的名字;我们需要这样做,因为可以使用多个。这就是您发送建议请求的一般方式。

如果我们想为同一文本获得多个建议,我们可以将我们的建议嵌入到 suggest 对象中并放置 text 属性作为 建议 对象选项。例如,如果我们想为 title 字段和 serlock holnes 文本获取建议literal">_all 字段,我们运行以下命令:

curl -XGET 'localhost:9200/library/_search?pretty' -d '{
 "query" : {
  "match_all" : {}
 },
 "suggest" : {
  "text" : "serlock holnes",
  "first_suggestion" : {
   "term" : {
    "field" : "_all"
   }
  },
  "second_suggestion" : {
   "term" : {
    "field" : "title"
   }
  }
 }
}'

Suggester response

现在让我们看看我们发送的第一个查询的响应。如您所料,响应包括查询结果和建议:

{
  "took" : 10,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 4,
    "max_score" : 1.0,
    "hits" : [ ... ]
  },
  "suggest" : {
    "first_suggestion" : [ {
      "text" : "serlock",
      "offset" : 0,
      "length" : 7,
      "options" : [ {
        "text" : "sherlock",
        "score" : 0.85714287,
        "freq" : 1
      } ]
    }, {
      "text" : "holnes",
      "offset" : 8,
      "length" : 6,
      "options" : [ {
        "text" : "holmes",
        "score" : 0.8333333,
        "freq" : 1
      } ]
    } ]
  }
}

我们可以看到我们在响应中同时获得了搜索结果和建议(我们省略了结果以使示例更具可读性)。术语建议器为 text 参数中存在的每个术语返回一个可能的建议列表。对于每个术语,术语建议者返回一组可能的建议。查看为 serlock 术语返回的数据,我们可以看到原始单词(text 参数),它在text 参数(offset 参数),及其长度(length 参数)。

options 数组包含给定单词的建议,如果 Elasticsearch 没有找到任何建议,则该数组将为空。此数组中的每个条目都是一个建议,并由以下属性描述:

  • text建议的文本。

  • score:建议score;分数越高,建议越好。

  • freq:建议的频率。频率表示该词在我们运行建议查询的索引中的文档中出现的次数。

Term suggester

术语建议器基于字符串编辑距离工作。这意味着具有 最少需要更改、添加或删除以使建议看起来像原始单词的字符的建议是最好的.例如,让我们以 worlwork。要将 worl 术语更改为 work, 我们需要更改 lk的字母,所以表示距离1。当然,对提供给建议者的文本进行分析,然后选择要建议的术语。

Term suggester configuration options

常用的和 最常用的术语建议器选项可用于所有基于术语一的建议器实现。目前,这些是短语建议者,当然也是基本术语之一。可用的选项有:

  • text:我们想要获得建议的文本。为了使建议器工作,此参数是必需的。

  • field:我们需要提供的另一个必需的参数。 field 参数允许我们设置应该为哪个字段生成建议。

  • analyzer:分析器的名称,用于分析文本参数。如果未设置,Elasticsearch 将使用 field 参数提供的字段的分析器。

  • size:默认为 5 指定最大值text 参数中提供的每个术语允许返回的建议数。

  • suggest_mode: 控制将包含哪些建议以及将针对哪些条款返回建议。可能的选项有: missing – 默认行为,这意味着建议者只会为索引中不存在的术语提供建议; popular - 表示建议 仅在它们比提供的术语更频繁时才会返回;最后 always 表示每次都会返回建议。

  • sort:允许我们指定如何在 Elasticsearch 返回的结果中对建议进行排序。默认情况下,它设置为 score,它告诉 Elasticsearch 建议应该首先按建议分数排序,其次是建议文档频率,最后是术语。第二个可能的值是frequency,这意味着结果首先按文档频率排序,然后是分数,最后是词条。

Additional term suggester options

除了 前面的常用术语建议选项之外,Elasticsearch 还允许我们使用仅对术语建议本身有意义的其他选项。其中一些选项如下:

  • lowercase_terms:当设置为 true, 它告诉 Elasticsearch 将产生自 分析后的 text 字段。

  • max_edits:默认为 2并指定建议必须作为术语建议返回的最大编辑距离。 Elasticsearch 允许我们将此值设置为 12

  • prefix_len默认设置为1。如果我们在建议器性能方面遇到困难,增加此值将提高整体性能,因为需要处理的建议更少。

  • min_word_len:默认4并指定建议必须具有的最小字符数才能在建议列表中返回。

  • shard_size:默认为size指定的值code> 参数并允许我们设置应从每个分片读取的最大建议数。将此属性设置为高于 size 参数的值可能会导致更准确的文档频率,但会降低建议器的性能。

    Note

    提供的参数列表 不包含可用于 term 建议器的所有选项。请参阅官方 Elasticsearch 文档以获取 参考,位于 https://www.elastic.co/guide/en/elasticsearch/reference/current/search-suggesters-term.html

Phrase suggester

术语建议器提供了一种根据术语更正用户拼写错误的好方法,但它 不适用于短语。这就是为什么引入短语建议者的原因。它建立在术语建议器之上,但添加了额外的短语 计算逻辑。

让我们从一个如何使用短语建议器的示例开始。这次我们将省略查询中的 query 部分。我们通过运行以下命令来做到这一点:

curl -XGET 'localhost:9200/library/_search?pretty' -d '{
 "suggest" : {
  "text" : "sherlock holnes",
  "our_suggestion" : {
   "phrase" : { "field" : "_all" }
  }
 }
}'

正如您在前面的命令中看到的,它与我们在使用术语建议者时发送的几乎相同,但我们没有指定术语建议者类型,而是指定了短语类型。对上述命令的响应如下:

{
  "took" : 24,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 4,
    "max_score" : 1.0,
    "hits" : [ ... ]
  },
  "suggest" : {
    "our_suggestion" : [ {
      "text" : "sherlock holnes",
      "offset" : 0,
      "length" : 15,
      "options" : [ {
        "text" : "sherlock holmes",
        "score" : 0.12227806
      } ]
    } ]
  }
}

如您所见, 响应与术语建议者返回的响应非常相似,但是,它不是返回单个单词,而是已经组合在一起并且作为短语返回。

Configuration

因为短语Suggester是基于术语Suggester,所以它也可以使用它提供的一些配置选项。这些选项是:textsizeanalyzershard_size。除了提到的属性之外,短语Suggester 还公开了其他选项。其中一些选项是:

Completion suggester

完成提示器允许我们以非常高效的方式创建自动完成功能,因为存储了 复杂的 索引中的结构,而不是计算 在查询期间它们。我们需要通过使用称为完成的专用字段类型来为此准备 Elasticsearch。假设我们想要创建一个自动完成功能来允许我们显示图书作者。除了作者姓名,我们还想返回她/他写的书的标识符。我们首先通过运行以下命令来创建作者索引:

curl -XPOST 'localhost:9200/authors' -d '{
 "mappings" : {
  "author" : {
   "properties" : {
    "name" : { "type" : "string" },
    "ac" : {
     "type" : "completion",
     "payloads" : true,
    "analyzer" : "standard",
    "search_analyzer" : "standard"
    }
   }
  }
 }
}'

我们的索引将包含一个名为 author 的类型。每个文档都有两个字段:nameac 字段,这是我们将用于自动完成的字段。我们使用 completion 类型定义了 ac 字段。除此之外,我们还使用 standard 分析器来分析 索引和查询时间。最后一件事是有效负载——我们将与建议一起返回的额外的可选信息——在我们的例子中,它将是一个书籍标识符数组。

Indexing data

为了索引数据,我们需要 提供一些额外的信息以及我们通常在索引期间提供的信息。让我们看一下索引两个描述作者的文档的以下命令:

curl -XPOST 'localhost:9200/authors/author/1' -d '{
 "name" : "Fyodor Dostoevsky",
 "ac" : {
  "input" : [ "fyodor", "dostoevsky" ],
  "output" : "Fyodor Dostoevsky",
  "payload" : { "books" : [ "123456", "123457" ] }
 }
}'
curl -XPOST 'localhost:9200/authors/author/2' -d '{
 "name" : "Joseph Conrad",
 "ac" : {
  "input" : [ "joseph", "conrad" ],
  "output" : "Joseph Conrad",
  "payload" : { "books" : [ "121211" ] }
 }
}'

请注意 ac 字段的数据结构。我们提供了 inputoutputpayload 属性。可选的 payload 属性用于提供 将返回的附加信息。 input 属性用于提供输入信息,这些信息将用于构建建议器使用的补全。它将用于用户输入匹配。可选的 output 属性用于告诉建议者应该为文档返回哪些数据。

我们也可以按照我们习惯的方式省略附加参数部分和索引数据,就像下面的例子一样:

curl -XPOST 'localhost:9200/authors/author/1' -d '{
 "name" : "Fyodor Dostoevsky",
 "ac" : "Fyodor Dostoevsky"
}'

但是,由于完成提示器在后台使用 FST,我们将无法从 ac 字段的第二部分开始找到前面的文档。这就是为什么我们认为以我们首先显示的方式索引数据更方便,因为我们可以明确控制我们想要匹配的内容以及我们想要显示为输出的内容。

Querying indexed completion suggester data

如果我们要查找作者以 fyo 开头的 文档,我们运行以下命令:

curl -XGET 'localhost:9200/authors/_suggest?pretty' -d '{
 "authorsAutocomplete" : {
  "text" : "fyo",
  "completion" : {
   "field" : "ac"
  }
 }
}'

在查看结果之前,让我们讨论一下查询。如您所见,我们已将命令运行到 _suggest 端点,因为我们不想运行标准查询;我们只对自动完成结果感兴趣。查询很简单。我们将其名称设置为 authorsAutocomplete,我们设置我们想要完成的文本(text 属性),然后我们添加了带有配置的 completion 对象。上述命令的结果如下所示:

{
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "authorsAutocomplete" : [ {
    "text" : "fyo",
    "offset" : 0,
    "length" : 3,
    "options" : [ {
      "text" : "Fyodor Dostoevsky",
      "score" : 1.0,
      "payload" : {
        "books" : [ "123456", "123457" ]
      }
    } ]
  } ]
}

正如您在响应中看到的那样,我们得到了我们正在寻找的文档以及有效负载信息,如果它可用(对于前面的响应,它不可用)。

我们还可以 使用模糊搜索,这让我们能够容忍拼写错误。我们通过在查询中包含额外的 fuzzy 部分来做到这一点。例如,要在完成提示器中启用模糊匹配并将最大编辑距离设置为 2(这意味着最多允许两个错误),我们发送以下查询:

curl -XGET 'localhost:9200/authors/_suggest?pretty' -d '{
 "authorsAutocomplete" : {
  "text" : "fio",
  "completion" : {
   "field" : "ac",
   "fuzzy" : {
    "edit_distance" : 2
   }
  }
 }
}'

尽管我们犯了拼写错误,但我们仍然会得到与之前相同的结果。

Custom weights

默认情况下,使用 term 频率来确定前缀建议器返回的文档的权重。但是,这可能不是最好的解决方案。在这种情况下,通过为定义为 completion 的字段指定 weight 属性来定义建议的权重很有用。 weight 属性应设置为整数值。 weight 属性值越高,建议越重要。例如,如果我们想为示例中的第一个文档指定 权重,我们运行以下命令:

curl -XPOST 'localhost:9200/authors/author/1' -d '{
 "name" : "Fyodor Dostoevsky",
 "ac" : {
  "input" : [ "fyodor", "dostoevsky" ],
  "output" : "Fyodor Dostoevsky",
  "payload" : { "books" : [ "123456", "123457" ] },
  "weight" : 30
 }
}'

现在,如果我们运行示例查询,结果将如下所示:

{
  ...
  "authorsAutocomplete" : [ {
    "text" : "fyo",
    "offset" : 0,
    "length" : 3,
    "options" : [ {
      "text" : "Fyodor Dostoevsky",
      "score" : 30.0, 
      "payload":{
        "books":["123456","123457"]
      }
    } ]
  } ]
}

看看结果的分数是如何变化的。在我们最初的示例中,它是 1.0,现在它是 30.0。 这是因为我们将权重参数设置为 30 在索引期间。

Context suggester

上下文 suggester 是我们刚刚讨论的 Elasticsearch 2.1 和更早版本的 Elasticsearch Suggest API 的扩展。在描述 Elasticsearch 2.1 的完成提示器时,我们提到这个提示器允许我们完全在内存中处理与提示器相关的搜索。使用这个建议器,我们可以 为查询定义所谓的 context这会将建议限制为文档的子集。因为我们在映射中定义了上下文,所以它是在索引期间计算的,这使得查询时间计算更容易,对性能的要求也更低。

Note

请记住,本节与 Elasticsearch 2.1 相关。 Elasticsearch 2.2 中的上下文处理方式不同,并在讨论完成建议时进行了讨论。

Context types

Elasticsearch 2.1 支持两种类型的上下文:categorygeocategory 类型的上下文允许我们在索引期间将文档分配给一个或多个类别。稍后,在 查询期间,我们可以告诉 Elasticsearch 我们感兴趣的类别,Elasticsearch 会将建议限制在这些类别中。 geo 上下文允许我们将建议者返回的文档限制在给定位置或与某个点的一定距离内。上下文的好处是我们可以有多个上下文。例如,对于同一个文档,我们可以同时拥有 category 上下文和 geo 上下文。现在让我们看看我们需要做什么才能在建议中使用上下文。

Using context

使用 geocategory 上下文非常相似——它们只是参数不同。我们将在示例中使用更简单的 category 上下文向您展示如何 使用上下文,稍后我们将获得回到 geo 上下文并向您展示我们需要提供的内容。

使用上下文建议器的第一步是创建适当的映射。让我们回到作者映射,但这次让我们假设每个作者都可以被赋予一个或多个类别——她/他正在写的书籍的品牌。这将是我们的背景。使用上下文的映射如下所示:

curl -XPOST 'localhost:9200/authors_geo_context' -d '{
 "mappings" : {
  "author" : {
   "properties" : {
    "name" : { "type" : "string" },
    "ac" : {
     "type" : "completion",
     "analyzer" : "simple",
     "search_analyzer" : "simple",
     "context" : {
      "brand" : {
       "type" : "category",
       "default" : [ "none" ]
      }
     }
    }
   }
  }
 }
}'

我们在 ac 字段定义中引入了一个新部分:context。每个上下文都有一个名称,在我们的例子中是 brand,在这个对象内部我们提供配置。我们需要使用 type 属性来提供类型——我们现在将使用 category 上下文提示器。除此之外,我们还设置了 default 数组,它为我们提供了应该用作默认上下文的一个或多个值。如果我们愿意,我们还可以提供 path 属性,它将 Elasticsearch 指向文档中应该从中获取上下文值的字段。

我们现在可以通过修改我们之前使用的命令来索引单个作者,因为我们需要提供上下文:

curl -XPOST 'localhost:9200/authors_context/author/1' -d '{
 "name" : "Fyodor Dostoevsky",
 "ac" : {
  "input" : "Fyodor Dostoevsky",
  "context" : {
   "brand" : "drama"
  }
 }
}'

如您所见,ac 字段定义现在有点不同了;它是一个对象。 input 属性用于 提供自动完成的值, context 对象用于为映射中定义的每个上下文提供值。

最后,我们可以查询数据。您可以想象,我们将再次提供我们感兴趣的上下文。执行该操作的查询如下所示:

curl -XGET 'localhost:9200/authors_context/_suggest?pretty' -d '{
 "authorsAutocomplete" : {
  "text" : "fyo",
  "completion" : {
   "field" : "ac",
   "context" : {
    "brand" : "drama"
   }
  }
 }
}'

如您所见,我们在 completion 部分的查询中包含了 context 对象,并且我们已经设置了上下文我们有兴趣使用上下文名称。 Elasticsearch 返回的响应如下:

{
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "authorsAutocomplete" : [ {
    "text" : "fyo",
    "offset" : 0,
    "length" : 3,
    "options" : [ {
      "text" : "Fyodor Dostoevsky",
      "score" : 1.0
    } ]
  } ]
}

但是,如果我们将 brand 上下文更改为 comedy,例如,Elasticsearch 将不会返回任何结果,因为 我们没有具有这种背景的作者。让我们通过运行以下查询来测试它:

curl -XGET 'localhost:9200/authors_context/_suggest?pretty' -d '{
 "authorsAutocomplete" : {
  "text" : "fyo",
  "completion" : {
   "field" : "ac",
   "context" : {
    "brand" : "comedy"
   }
  }
 }
}'

这次 Elasticsearch 返回以下响应:

{
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "authorsAutocomplete" : [ {
    "text" : "fyo",
    "offset" : 0,
    "length" : 3,
    "options" : [ ]
  } ]
}

这是因为没有具有 brand 上下文的作者,并且 comedy 的值存在于 authors_context 索引。

Using the geo location context

geo 上下文在使用时类似于 category 上下文。但是,我们不是按术语过滤,而是使用地理点和距离进行过滤。当我们使用 geo 上下文时,我们需要提供 precision< /code>,它定义了计算的 geohash 的精度。我们提供的第二个属性是 neighbors 之一,可以设置为 true错误 。默认情况下,它设置为 true,这意味着相邻的地理哈希将包含在上下文中。

除此之外,类似于类别上下文,我们可以提供 path,它指定使用哪个字段作为地理点的查找,以及 default 属性,指定文档的默认地理点。

例如,假设我们要过滤作者的出生地。此类建议者的映射如下所示:

curl -XPOST 'localhost:9200/authors_geo_context' -d '{
 "mappings" : {
  "author" : {
   "properties" : {                
    "name" : { "type" : "string" },
    "ac" : {
     "type" : "completion",
     "analyzer" : "simple",
     "search_analyzer" : "simple",
     "context" : {
      "birth_location" : {
       "type" : "geo",
       "precision" : [ "1000km" ],
       "neighbors" : true,
       "default" : {
        "lat" : 0.0,
        "lon" : 0.0
       }
      }
     }
    }
   }
  }
 }
}'

现在我们可以索引文档并提供出生位置。对于我们的示例作者,它将如下所示(莫斯科市中心):

curl -XPOST 'localhost:9200/authors_geo_context/author/1' -d '{
 "name" : "Fyodor Dostoevsky",
 "ac" : {
  "input" : "Fyodor Dostoevsky",
  "context" : {
   "birth_location" : {
    "lat" : 55.75,
   "lon" : 37.61
   }
  }
 }
}'

如您所见,我们为作者提供了 birth_location 上下文。

现在在查询期间,我们需要提供我们感兴趣的上下文,我们可以(但我们没有义务)提供精度作为 映射。我们已将精度定义为 1000 公里,因此让我们找到所有出生于喀山(距莫斯科约 800 公里)的以 fyo 开头的作者。我们应该找到我们的示例作者。

执行此操作的查询如下所示:

curl -XGET 'localhost:9200/authors_geo_context/_suggest?pretty' -d '{
 "authorsAutocomplete" : {
  "text" : "fyo",
  "completion" : {
   "field" : "ac",
   "context" : {
    "birth_location" : {
     "lat" : 55.45,
     "lon" : 49.8
    }
   }
  }
 }
}'

Elasticsearch 返回的响应如下所示:

{
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "authorsAutocomplete" : [ {
    "text" : "fyo",
    "offset" : 0,
    "length" : 3,
    "options" : [ {
      "text" : "Fyodor Dostoevsky",
      "score" : 1.0
    } ]
  } ]
}

但是,如果我们运行 相同的查询但指向北极,我们将不会得到任何结果:

curl -XGET 'localhost:9200/authors_geo_context/_suggest?pretty' -d '{
 "authorsAutocomplete" : {
  "text" : "fyo",
  "completion" : {
   "field" : "ac",
   "context" : {
    "birth_location" : {
     "lat" : 0.0,
     "lon" : 0.0
    }
   }
  }
 }
}'

以下是本例中来自 Elasticsearch 的 响应:

{
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "authorsAutocomplete" : [ {
    "text" : "fyo",
    "offset" : 0,
    "length" : 3,
    "options" : [ ]
  } ]
}

The Scroll API


假设我们有一个包含数百万个文档的索引。我们已经知道如何构建我们的查询等等。但是,当尝试获取大量文档时,您会看到 随着结果的页面越来越远,查询速度变慢并最终超时或导致内存问题。

原因是全文搜索引擎,尤其是那些分布式的,不能很好地处理分页。当然,获得几百页的结果对于 Elasticsearch 来说不是问题,但是对于遍历所有索引文档或通过大型结果集,一个 专门已引入 API。

Problem definition

当 Elasticsearch 生成响应时,它必须确定形成结果的文档的顺序。如果我们在首页,这不是什么大问题。 Elasticsearch 只是找到文档集并收集第一个文档;比方说,20 个文件。但是如果我们在第 10 页,Elasticsearch 必须从第 1 页到第 10 页获取所有文档,然后丢弃在第 1 到 9 页的文档。如果我们有一个分布式环境,这就更复杂了,因为我们不知道结果会来自哪些节点。因此,每个节点都需要构建响应并将其保存在内存中一段时间​​。问题不是 Elasticsearch 特有的;在数据库系统中可以找到类似的情况,例如,通常在每个使用所谓的优先级队列的系统中。

Scrolling to the rescue

解决方案很简单。由于 Elasticsearch 必须对每个请求进行一些操作(确定前几页的文档),我们可以要求 Elasticsearch 为 后续查询存储这些信息。缺点是由于资源有限,我们无法永远存储这些信息。 Elasticsearch 假设我们可以声明我们需要这些信息在多长时间内可用。让我们看看它在实践中是如何工作的。

首先,我们像往常一样查询 Elasticsearch。但是,除了所有已知参数之外,我们还添加了一个参数:包含我们想要使用滚动的信息的参数以及我们建议 Elasticsearch 应该保留有关结果的信息的时间。我们可以通过发送如下查询来做到这一点:

curl 'localhost:9200/library/_search?pretty&scroll=5m' -d '{
  "size" : 1,
  "query" : {
    "match_all" : { }
  }
}'

此查询的内容无关紧要。重要的是 Elasticsearch 如何修改响应。查看 Elasticsearch 返回的响应的以下前几行:

{
  "_scroll_id" : "cXVlcnlUaGVuRmV0Y2g7NTsxNjo1RDNrYnlfb1JTeU1sX20yS0NRSUZ3OzE3OjVEM2tieV9vUlN5TWxfbTJLQ1FJRnc7MTg6NUQza2J5X29SU3lNbF9tMktDUUlGdzsxOTo1RDNrYnlfb1JTeU1sX20yS0NRSUZ3OzIwOjVEM2tieV9vUlN5TWxfbTJLQ1FJRnc7MDs=",
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 4,
    ...

新部分是 _scroll_id 部分。这是我们将在后面的查询中使用的句柄。 Elasticsearch 为此有一个特殊的端点:_search/scroll 端点。让我们看下面的例子:

curl -XGET 'localhost:9200/_search/scroll?pretty' -d '{
  "scroll" : "5m",
  "scroll_id" : "cXVlcnlUaGVuRmV0Y2g7NTsyNjo1RDNrYnlfb1JTeU1sX20yS0NRSUZ3OzI3OjVEM2tieV9vUlN5TWxfbTJLQ1FJRnc7Mjg6NUQza2J5X29SU3lNbF9tMktDUUlGdzsyOTo1RDNrYnlfb1JTeU1sX20yS0NRSUZ3OzMwOjVEM2tieV9vUlN5TWxfbTJLQ1FJRnc7MDs="
}'

现在,每次使用 scroll_id 调用此端点都会返回下一页结果。请记住,此句柄仅在定义的不活动时间内有效。

当然,这种方案并不理想,在对各种结果的随机页面有很多请求或者请求之间的时间很难确定的情况下,也不是很合适。但是,您可以成功地将其用于想要获得更大结果集的用例,例如在多个系统之间传输数据。

Summary


在我们刚刚完成的章节中,我们了解了 Elasticsearch 的一些功能,这些功能我们可能不会每天使用,或者至少不是我们每个人都会使用它们。我们讨论了 percolator——一种倒置的搜索功能,它允许我们索引查询并找到与它们匹配的文档。我们了解了 Elasticsearch 的空间功能,并使用建议器来纠正用户拼写错误并构建高效的自动完成功能。我们还使用 Scroll API 从我们的 Elasticsearch 索引中高效地获取大量结果。

在下一章中,我们将重点介绍集群及其配置。我们将讨论节点发现、网关和恢复模块——它们负责什么以及如何配置它们以满足我们的需求。我们将使用模板和动态模板,并了解如何安装扩展 Elasticsearch 开箱即用功能的插件。我们将了解 Elasticsearch 缓存的缓存是什么,以及如何有效地配置它们以充分利用它们。最后,我们将使用更新设置 API 来更新实时和运行集群上的 Elasticsearch 配置。