Elasticsearch入门

发布于 2019-08-26  405 次阅读


启动并运行Elasticsearch

索引一些文档

启动并运行群集后,您就可以为某些数据编制索引了。 Elasticsearch有各种各样的摄取选项,但最终它们都做同样的事情:将JSON文档放入Elasticsearch索引。

您可以使用简单的PUT请求直接执行此操作,该请求指定要添加文档的索引,唯一的文档ID以及请求正文中的一个或多个键值对。

PUT /customer/_doc/1
{
  "name": "John Doe"
}

此请求会自动创建客户索引(如果尚不存在),添加ID为1的新文档,并存储名称字段并为其编制索引。

由于这是一个新文档,因此响应显示操作的结果是创建了文档的第1版:

{
  "_index" : "customer",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 2,
    "failed" : 0
  },
  "_seq_no" : 26,
  "_primary_term" : 4
}

可以从群集中的任何节点立即获取新文档。 您可以使用指定其文档ID的GET请求来检索它:

GET /customer/_doc/1

响应表明找到了具有指定ID的文档,并显示已编制索引的原始源字段。

{
  "_index" : "customer",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 1,
  "_seq_no" : 26,
  "_primary_term" : 4,
  "found" : true,
  "_source" : {
    "name": "John Doe"
  }
}

使用bulk来索引文档

如果要索引大量文档,可以使用批量API批量提交。 使用批量到批量文档操作比单独提交请求要快得多,因为它可以最大限度地减少网络往返。

最佳批处理大小取决于多种因素:文档大小和复杂性,索引和搜索负载以及群集可用的资源。 一个好的起点是批量为1,000到5,000个文档,总有效载荷在5MB到15MB之间。 从那里,你可以尝试找到最佳点。

要将一些数据导入Elasticsearch,您可以开始搜索和分析:

  • 下载accounts.json示例数据集。 此随机生成的数据集中的文档表示具有以下信息的用户帐户:

    {
        "account_number": 0,
        "balance": 16623,
        "firstname": "Bradshaw",
        "lastname": "Mckenzie",
        "age": 29,
        "gender": "F",
        "address": "244 Columbus Place",
        "employer": "Euron",
        "email": "bradshawmckenzie@euron.com",
        "city": "Hobucken",
        "state": "CO"
    }
    
  • 使用以下_bulk请求将帐户数据索引到银行索引中:

    curl -H "Content-Type: application/json" -XPOST "localhost:9200/bank/_bulk?pretty&refresh" --data-binary "@accounts.json"
    curl "localhost:9200/_cat/indices?v"
    

    响应表明已成功索引1,000个文档。

    health status index uuid                   pri rep docs.count docs.deleted store.size pri.store.size
    yellow open   bank  l7sSYV2cQXmu6_4rJWVIww   5   1       1000            0    128.6kb        128.6kb
    

开始搜索

现在让我们从一些简单的搜索开始吧。 运行搜索有两种基本方法:一种是通过REST请求URI发送搜索参数,另一种是通过REST请求体发送搜索参数。 请求体方法允许您更具表现力,并以更可读的JSON格式定义搜索。 我们将尝试一个请求URI方法的示例,但是对于本教程的其余部分,我们将专门使用请求体方法。

可以从_search端点访问用于搜索的REST API。 此示例返回银行索引中的所有文档:

GET /bank/_search?q=*&sort=account_number:asc&pretty

让我们首先剖析搜索调用。 我们在银行索引中搜索(_search endpoint),q = *参数指示Elasticsearch匹配索引中的所有文档。 sort = account_number:asc参数指示使用升序中的每个文档的account_number字段对结果进行排序。 格式化的参数再次告诉Elasticsearch返回格式化的JSON结果。

{
  "took" : 63,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
        "value": 1000,
        "relation": "eq"
    },
    "max_score" : null,
    "hits" : [ {
      "_index" : "bank",
      "_type" : "_doc",
      "_id" : "0",
      "sort": [0],
      "_score" : null,
      "_source" : {"account_number":0,"balance":16623,"firstname":"Bradshaw","lastname":"Mckenzie","age":29,"gender":"F","address":"244 Columbus Place","employer":"Euron","email":"bradshawmckenzie@euron.com","city":"Hobucken","state":"CO"}
    }, {
      "_index" : "bank",
      "_type" : "_doc",
      "_id" : "1",
      "sort": [1],
      "_score" : null,
      "_source" : {"account_number":1,"balance":39225,"firstname":"Amber","lastname":"Duke","age":32,"gender":"M","address":"880 Holmes Lane","employer":"Pyrami","email":"amberduke@pyrami.com","city":"Brogan","state":"IL"}
    }, ...
    ]
  }
}

至于响应,我们看到以下部分:

  • took - Elasticsearch执行搜索的时间(以毫秒为单位)

  • timed_out - 告诉我们搜索是否超时

  • _shards - 告诉我们搜索了多少个分片,以及搜索成功/失败分片的数量

  • hits - 搜索结果

  • hits.total - 包含与我们的搜索条件匹配的文档总数的信息的对象

    • hits.total.value- 总命中数的值(必须在上下文中解释hits.total.relation)。
    • hits.total.relation- 是否hits.total.value是确切的命中计数,在这种情况下它等于"eq"或总命中数的下限(大于或等于),在这种情况下它等于gte
  • hits.hits - 实际的搜索结果数组(默认为前10个文档)

  • hits.sort - 为每个结果排序键的排序值(如果按分数排序则丢失)

  • hits._scoremax_score- 暂时忽略这些字段

精度hits.total由请求参数控制track_total_hits,当设置为true时,请求将准确跟踪总命中("relation": "eq")。默认为10,000 这意味着总命中数被准确地跟踪到10,000文档。您可以通过track_total_hits显式设置为true 来强制进行准确计数。有关详细信息,请参阅请求正文文档。

以下是使用替代请求正文方法的上述完全相同的搜索:

GET /bank/_search
{
  "query": { "match_all": {} },
  "sort": [
    { "account_number": "asc" }
  ]
}

这里的不同之处在于,我们不是在URI中传递q = *,而是向_search API提供JSON样式的查询请求体。 我们将在下一节讨论这个JSON查询。

重要的是要理解,一旦您获得了搜索结果,Elasticsearch就完全完成了请求,并且不会在结果中维护任何类型的服务器端资源或打开游标。 这与SQL等许多其他平台形成鲜明对比,其中您最初可能会预先获得查询结果的部分子集,然后如果要获取(或翻页)其余部分,则必须不断返回服务器使用某种有状态服务器端游标的结果。

查询语言介绍

Elasticsearch提供了一种JSON样式的特定于域的语言,可用于执行查询。 这被称为查询DSL。 查询语言非常全面,乍一看可能令人生畏,但实际学习它的最佳方法是从一些基本示例开始。

回到上一个例子,我们执行了这个查询:

GET /bank/_search
{
  "query": { "match_all": {} }
}

解析上面的内容,该query部分告诉我们查询定义是什么,match_all部分只是我们想要运行的查询类型。该match_all查询仅仅是在指定索引的所有文件进行搜索。

除了query参数,我们还可以传递其他参数来影响搜索结果。在上面我们传入的部分的示例中 sort,我们传入size

GET /bank/_search
{
  "query": { "match_all": {} },
  "size": 1
}

请注意,如果size未指定,则默认为10。

此示例执行a match_all并返回文档10到19:

GET /bank/_search
{
  "query": { "match_all": {} },
  "from": 10,
  "size": 10
}

from(从0开始)参数规定了从启动该文件的索引和size参数指定了多少文件,返回从参数开始的。在实现搜索结果的分页时,此功能非常有用。请注意,如果from未指定,则默认为0。

此示例执行a match_all并按帐户余额降序对结果进行排序,并返回前10个(默认大小)文档。

GET /bank/_search
{
  "query": { "match_all": {} },
  "sort": { "balance": { "order": "desc" } }
}

现在我们已经看到了一些基本的搜索参数,让我们再深入研究一下Query DSL。我们先来看一下返回的文档字段。默认情况下,完整的JSON文档作为所有搜索的一部分返回。这被称为源(_source搜索命中中的字段)。如果我们不希望返回整个源文档,我们只能请求返回源中的几个字段。

此示例显示如何从搜索中返回两个字段account_numberbalance(内部_source):

GET /bank/_search
{
  "query": { "match_all": {} },
  "_source": ["account_number", "balance"]
}

请注意,上面的示例只是简化了_source字段。它仍将只返回一个名为_source的字段。但在其中的字段中仅包含account_numberbalance

如果您有SQL使用经验,则上面的概念与SQL SELECT FROM字段列表有些相似。

现在让我们转到查询部分。以前,我们已经看到match_all查询如何用于匹配所有文档。现在让我们介绍一个名为match查询的新查询,它可以被认为是一个基本的字段搜索查询(即针对特定字段或字段集进行的搜索)。

此示例返回编号为20的帐户:

GET /bank/_search
{
  "query": { "match": { "account_number": 20 } }
}

此示例返回地址中包含术语“mill”的所有帐户:

GET /bank/_search
{
  "query": { "match": { "address": "mill" } }
}

此示例返回地址中包含术语“mill”或“lane”的所有帐户:

GET /bank/_search
{
  "query": { "match": { "address": "mill lane" } }
}

此示例是matchmatch_phrase)的变体,它返回地址中包含短语“mill lane”的所有帐户:

GET /bank/_search
{
  "query": { "match_phrase": { "address": "mill lane" } }
}

我们现在介绍一下这个bool查询。该bool查询允许我们使用布尔逻辑将较小的查询组成更大的查询。

此示例组成两个match查询并返回地址中包含“mill”和“lane”的所有帐户:

GET /bank/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "address": "mill" } },
        { "match": { "address": "lane" } }
      ]
    }
  }
}

在上面的示例中,该bool must子句指定必须为true才能将文档视为匹配的所有查询。

相反,此示例组成两个match查询并返回地址中包含“mill”或“lane”的所有帐户:

GET /bank/_search
{
  "query": {
    "bool": {
      "should": [
        { "match": { "address": "mill" } },
        { "match": { "address": "lane" } }
      ]
    }
  }
}

在上面的示例中,该bool should子句指定了一个查询列表,其中任何一个都必须为true才能使文档被视为匹配。

此示例组成两个match查询并返回地址中既不包含“mill”也不包含“lane”的所有帐户:

GET /bank/_search
{
  "query": {
    "bool": {
      "must_not": [
        { "match": { "address": "mill" } },
        { "match": { "address": "lane" } }
      ]
    }
  }
}

在上面的示例中,该bool must_not子句指定了一个查询列表,对于要被视为匹配的文档,这些查询都不能为true。

我们可以在查询中同时组合mustshouldmust_not子句bool。此外,我们可以bool在任何这些bool子句中组合查询来模仿任何复杂的多级布尔逻辑。

此示例返回任何40岁但未居住在ID中的人的所有帐户:

GET /bank/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "age": "40" } }
      ],
      "must_not": [
        { "match": { "state": "ID" } }
      ]
    }
  }
}

执行过滤器

在上一节中,我们跳过了一个称为文档分数的小细节(_score搜索结果中的字段)。分数是一个数值,它是文档与我们指定的搜索查询匹配程度的相对度量。分数越高,文档越相关,分数越低,文档的相关性越低。

但是查询并不总是需要产生分数,特别是当它们仅用于“过滤”文档集时。Elasticsearch检测这些情况并自动优化查询执行,以便不计算无用的分数。

我们在上一节中介绍的bool查询还支持一些filter子句,这些子句允许我们使用查询来限制将与其他子句匹配的文档,而不会更改计算分数的方式。作为示例,让我们介绍一下range查询,它允许我们按一系列值过滤文档。这通常用于数字或日期过滤。

此示例使用bool查询返回所有余额介于20000和30000之间的帐户。换句话说,我们希望找到余额大于或等于20000且小于或等于30000的帐户。

GET /bank/_search
{
  "query": {
    "bool": {
      "must": { "match_all": {} },
      "filter": {
        "range": {
          "balance": {
            "gte": 20000,
            "lte": 30000
          }
        }
      }
    }
  }
}

解析上面的内容,bool查询包含match_all查询(查询部分)和range查询(过滤部分)。我们可以将任何其他查询替换为查询和过滤器部分。在上面的情况下,范围查询非常有意义,因为落入范围的文档都“相同”匹配,即,没有文档比另一文档更相关。

除了match_allmatchbool,和range查询,有很多可用的其他查询类型的,在这里我们不会深入他们。由于我们已经基本了解它们的工作原理,因此将这些知识应用于学习和试验其他查询类型应该不会太困难。

使用聚合分析结果

聚合提供了从数据中分组和提取统计信息的功能。考虑聚合的最简单方法是将其大致等同于SQL GROUP BY和SQL聚合函数。在Elasticsearch中,您可以执行返回匹配的搜索,同时在一个响应中返回与命中相关的聚合结果。这是非常强大和高效的,因为您可以运行查询和多个聚合,并一次性获取两个(或任一)操作的结果,避免使用简洁和简化的API进行网络往返。

首先,此示例按状态对所有帐户进行分组,然后返回按计数降序排序的前10个(默认)状态(也是默认值):

GET /bank/_search
{
  "size": 0,
  "aggs": {
    "group_by_state": {
      "terms": {
        "field": "state.keyword"
      }
    }
  }
}

在SQL中,上述聚合在概念上类似于:

SELECT state, COUNT(*) FROM bank GROUP BY state ORDER BY COUNT(*) DESC LIMIT 10;

响应(部分显示):

{
  "took": 29,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped" : 0,
    "failed": 0
  },
  "hits" : {
     "total" : {
        "value": 1000,
        "relation": "eq"
     },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "group_by_state" : {
      "doc_count_error_upper_bound": 20,
      "sum_other_doc_count": 770,
      "buckets" : [ {
        "key" : "ID",
        "doc_count" : 27
      }, {
        "key" : "TX",
        "doc_count" : 27
      }, {
        "key" : "AL",
        "doc_count" : 25
      }, {
        "key" : "MD",
        "doc_count" : 25
      }, {
        "key" : "TN",
        "doc_count" : 23
      }, {
        "key" : "MA",
        "doc_count" : 21
      }, {
        "key" : "NC",
        "doc_count" : 21
      }, {
        "key" : "ND",
        "doc_count" : 21
      }, {
        "key" : "ME",
        "doc_count" : 20
      }, {
        "key" : "MO",
        "doc_count" : 20
      } ]
    }
  }
}

我们可以看到ID(爱达荷州)有27个账户,其次是TX(德克萨斯州)的27个账户,其次是AL(阿拉巴马州)的25个账户,依此类推。

请注意,我们设置size=0为不显示搜索匹配,因为我们只想在响应中看到聚合结果。

在前一个聚合的基础上,此示例按州计算平均帐户余额(同样仅针对按降序排序的前10个州):

GET /bank/_search
{
  "size": 0,
  "aggs": {
    "group_by_state": {
      "terms": {
        "field": "state.keyword"
      },
      "aggs": {
        "average_balance": {
          "avg": {
            "field": "balance"
          }
        }
      }
    }
  }
}

请注意我们如何嵌套average_balance聚合内的group_by_state聚合。这是所有聚合的常见模式。您可以在聚合中任意嵌套聚合,以从数据中提取所需的轮转摘要。

在前一个聚合的基础上,我们现在按降序排列平均余额:

GET /bank/_search
{
  "size": 0,
  "aggs": {
    "group_by_state": {
      "terms": {
        "field": "state.keyword",
        "order": {
          "average_balance": "desc"
        }
      },
      "aggs": {
        "average_balance": {
          "avg": {
            "field": "balance"
          }
        }
      }
    }
  }
}

此示例演示了我们如何按年龄段(20-29岁,30-39岁和40-49岁)进行分组,然后按性别进行分组,最后得到每个年龄段的平均帐户余额:

GET /bank/_search
{
  "size": 0,
  "aggs": {
    "group_by_age": {
      "range": {
        "field": "age",
        "ranges": [
          {
            "from": 20,
            "to": 30
          },
          {
            "from": 30,
            "to": 40
          },
          {
            "from": 40,
            "to": 50
          }
        ]
      },
      "aggs": {
        "group_by_gender": {
          "terms": {
            "field": "gender.keyword"
          },
          "aggs": {
            "average_balance": {
              "avg": {
                "field": "balance"
              }
            }
          }
        }
      }
    }
  }
}

还有许多其他聚合功能,我们在此不再详述。如果您想进行进一步的实验,聚合参考指南是一个很好的起点。

 


面向ACG编程