Elasticsearch 提供了近实时的数据操做和搜索功能。默认状况下, 您能够指望从索引/更新/删除数据的时间到一个秒延迟 (刷新间隔), 直到显示在搜索结果中的时间。这是与 SQL 相似的其余平台的重要区别, 其中数据在事务完成后当即可用。sql
索引单个文档。让咱们再次记住该命令:api
PUT /customer/_doc/1?pretty
{
"name": "John Doe"
}
复制代码
一样, 上述将将指定的文档索引为客户索引, ID 为1。若是咱们再用不一样 (或相同) 的文档再次执行上述命令, Elasticsearch 将替换 (即 reindex) 一个新文档, 上面有一个 ID 为1的文件:数组
PUT /customer/_doc/1?pretty
{
"name": "Jane Doe"
}
复制代码
上述更改的文件名称, 其 ID 为1从 "无名氏" 到 "无名氏"。另外一方面, 若是咱们使用不一样的 ID, 则会对新文档进行索引, 而且索引中已经存在的现有文档将保持不变。服务器
PUT /customer/_doc/2?pretty
{
"name": "Jane Doe"
}
复制代码
上面的索引是一个 ID 为2的新文档。网络
索引时, ID 部分是可选的。若是未指定, Elasticsearch 将生成随机 ID, 而后使用它对文档进行索引。架构
实际 ID Elasticsearch 生成 (或在前面的示例中显式指定的任何内容) 做为索引 API 调用的一部分返回。函数
此示例演示如何索引没有显式 ID 的文档:优化
POST /customer/_doc?pretty
{
"name": "Jane Doe"
}
复制代码
请注意, 在上述状况下, 咱们使用的是POST而不是PUT, 由于咱们没有指定 ID。spa
除了可以索引和替换文档以外, 咱们还能够更新文档。注意,每次进行更新时, Elasticsearch 都会删除旧文档, 而后用一次快照将更新应用于该文档, 对其进行索引。rest
本示例演示如何经过将名称字段更改成 "无名氏" 来更新之前的文档 (ID 为 1):
POST /customer/_doc/1/_update?pretty
{
"doc": { "name": "Jane Doe" }
}
复制代码
本示例演示如何经过将 "名称" 字段更改成 "无名氏", 并同时向其添加 "年龄" 字段来更新咱们之前的文档 (ID 1):
POST /customer/_doc/1/_update?pretty
{
"doc": { "name": "Jane Doe", "age": 20 }
}
复制代码
删除文档至关简单。此示例演示如何删除 ID 为2的客户:
DELETE /customer/_doc/2?pretty
复制代码
除了可以索引、更新和删除单个文档以外, Elasticsearch 还提供了使用bulk API在批处理中执行上述任何操做的能力。此功能很重要, 由于它提供了一个很是有效的机制, 尽量快地完成多个操做, 尽量少使用网络往返。
例如, 如下调用在一个批量操做中索引两个文档 (id 1-无名氏和 id 2-无名氏):
POST /customer/_doc/_bulk?pretty
{"index":{"_id":"1"}}
{"name": "John Doe" }
{"index":{"_id":"2"}}
{"name": "Jane Doe" }
复制代码
本示例更新第一个文档 (id 为 1), 而后在一个批量操做中删除第二个文档 (id 为 2):
POST /customer/_doc/_bulk?pretty
{"update":{"_id":"1"}}
{"doc": { "name": "John Doe becomes Jane Doe" } }
{"delete":{"_id":"2"}}
复制代码
注意, 对于删除操做, 在它以后没有相应的源文档, 由于删除只要求删除文档的 ID。
因为其中一个操做失败, 批量 API 不会失败。若是单个操做因任何缘由而失败, 它将继续处理其后面的其他操做。当批量 API 返回时, 它将为每一个操做提供一个状态 (与发送的顺序相同), 以便您能够检查特定操做是否失败。
如今, 咱们已经看到了基本知识, 让咱们尝试一个更现实的数据集。我已经准备了一个关于客户银行账户信息的虚拟 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"
}
复制代码
如今让咱们从一些简单的搜索开始。运行搜索有两种基本方法: 一种是经过rest 请求 URI发送搜索参数, 另外一种是经过rest 请求正文发送。请求正文方法容许您更具表现力, 也能够用更可读的 JSON 格式定义搜索。
咱们将尝试一个请求 URI 方法的示例, 但对于本教程的其他部分, 咱们将专门使用请求正文方法。
用于搜索的 REST API 可从端点访问。本示例返回银行索引中的全部文档:_search
GET /bank/_search?q=*&sort=account_number:asc&pretty
复制代码
让咱们先解剖一下上面的查询语句。咱们在bank
索引中搜索 (端点), q=*
参数指示 Elasticsearch 匹配索引中的全部文档。sort=account_number:asc
该参数指示使用每一个文档的字段升序对结果进行排序。pretty
该参数, 只是告诉 Elasticsearch 返回漂亮的打印 JSON 结果。
响应 (部分显示):
{
"took" : 63,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 1000,
"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.hits–实际的搜索结果数组 (默认为前10个文档)
hits.sort-为结果排序键 (若是按分数排序则丢失)
下面是使用备选请求正文方法的相同的精确搜索:
GET /bank/_search
{
"query": { "match_all": {} },
"sort": [
{ "account_number": "asc" }
]
}
复制代码
这里的区别在于, 咱们不是在 URI 中传递, 而是向 API 提供 JSON 样式的查询请求正文。
注意, 当你获得搜索结果, Elasticsearch 已经请求结束。 与许多其余平台 (如 SQL) 不一样, SQL最初可能会得到查询结果的部分子集, 而后要使用某种方法(如翻页) 继续返回到服务器再次查询获取其他的结果。
复制代码
Elasticsearch 提供了一个 JSON 样式的域特定语言, 您可使用它来执行查询。这称为查询 DSL。查询语言很是全面, 乍一看可能很吓人, 但真正了解它的最好方法是从几个基本示例开始。
回到最后一个示例, 咱们执行了如下查询:
GET /bank/_search
{
"query": { "match_all": {} }
}
复制代码
咱们还能够经过其余参数来影响搜索结果。在上面的部分中, 咱们经过size
设置查询结果数量:
GET /bank/_search
{
"query": { "match_all": {} },
"size": 1
}
复制代码
请注意, 若是size
未指定, 则默认为10。
本示例执行并返回文档10到 19:
GET /bank/_search
{
"query": { "match_all": {} },
"from": 10,
"size": 10
}
复制代码
本示例按账户余额降序排列结果:
GET /bank/_search
{
"query": { "match_all": {} },
"sort": { "balance": { "order": "desc" } }
}
复制代码
既然咱们已经看到了一些基本的搜索参数, 让咱们在查询 DSL 中多挖掘一些。
让咱们先来看看返回的文档字段。默认状况下, 完整的 JSON 文档做为全部搜索的一部分返回。这称为_source
源 (搜索命中中的字段)。若是咱们不但愿返回整个源文档, 咱们就有能力请求返回源中的几个字段。
此示例演示如何从搜索中返回两个字段:
GET /bank/_search
{
"query": { "match_all": {} },
"_source": ["account_number", "balance"]
}
复制代码
相似 select account_number,balance from table;
之前, 咱们已经看到了如何使用查询来匹配全部文档。如今让咱们介绍一个名为 " 匹配查询" 的新查询, 它能够被看做是一个基本的搜索查询 (即针对特定字段或字段集进行的搜索)。
本示例返回编号为20的账户:
GET /bank/_search
{
"query": { "match": { "account_number": 20 } }
}
复制代码
本示例返回address
中包含 mill
一词的全部账户:
GET /bank/_search
{
"query": { "match": { "address": "mill" } }
}
复制代码
本示例返回address
中包含 mill
或 lane
术语的全部账户:
GET /bank/_search
{
"query": { "match": { "address": "mill lane" } }
}
复制代码
此示例是 match
的变体, 它返回地址中包含 mill lane
短语的全部账户:
GET /bank/_search
{
"query": { "match_phrase": { "address": "mill lane" } }
}
复制代码
如今让咱们介绍一下 bool查询。bool查询容许咱们使用布尔逻辑将较小的查询组成更大的查询。
本示例构成两个查询, 并返回address
中包含 mill
和 lane
的全部账户:
GET /bank/_search
{
"query": {
"bool": {
"must": [
{ "match": { "address": "mill" } },
{ "match": { "address": "lane" } }
]
}
}
}
复制代码
在上面的示例中, 子句指定bool must
要视为匹配的文档必须为 true 的全部查询。
本示例构成两个查询, 并返回address
中包含 mill
或 lane
的全部账户:
GET /bank/_search
{
"query": {
"bool": {
"should": [
{ "match": { "address": "mill" } },
{ "match": { "address": "lane" } }
]
}
}
}
复制代码
在上面的示例中, 子句bool should
指定一个查询列表, 其中有一个都是 true, 才能将文档视为匹配项。
本示例构成两个查询, 并返回address
中不包含 mill
和 lane
的全部账户:
GET /bank/_search
{
"query": {
"bool": {
"must_not": [
{ "match": { "address": "mill" } },
{ "match": { "address": "lane" } }
]
}
}
}
复制代码
本示例返回40岁的人的全部账户, 但不居住在 ID (阿霍) 中:
GET /bank/_search
{
"query": {
"bool": {
"must": [
{ "match": { "age": "40" } }
],
"must_not": [
{ "match": { "state": "ID" } }
]
}
}
}
复制代码
在上一节中, 咱们跳过一个名为_score
"文档分数" (搜索结果中的字段) 的小细节。
分数是一个数值, 它是文档与咱们指定的搜索查询匹配程度的相对度量值。
分数越高, 文档越相关, 分数越低, 文档越不相关。
可是查询并不老是须要产生分数, 特别是当它们仅用于 "筛选" 文档集时。
Elasticsearch 检测这些状况, 并自动优化查询执行, 以不计算无用的分数。
例如, 让咱们介绍范围查询, 它容许咱们按一系列值筛选文档。这一般用于数字或日期筛选。
本示例使用 bool 查询返回包含20000和30000之间的余额的全部账户。换言之, 咱们但愿找到一个余额大于或等于20000且小于或等于30000的账户。
GET /bank/_search
{
"query": {
"bool": {
"must": { "match_all": {} },
"filter": {
"range": {
"balance": {
"gte": 20000,
"lte": 30000
}
}
}
}
}
}
复制代码
解剖上面, 布尔查询包含查询 (查询部分) 和查询 (筛选器部分)。在上述状况下, 范围查询是彻底无心义的, 由于只要在这个范围内的文件,他们的分数应该相同,没有谁比谁更相关。因此,咱们能够不用关心他们的文档分数,从而使用filter
筛选文档。
聚合提供了从数据中分组和提取统计信息的能力。考虑聚合的最简单方法是大体将它等同于 sql group和 sql 聚合函数。在 Elasticsearch 中, 您有能力执行返回命中的搜索, 同时返回聚合结果, 并在一个响应中与命中所有分开。这是很是强大和高效的, 在这个意义上, 您能够运行查询和多个聚合, 并得到两个 (或两个以上) 操做的结果, 以免网络往返。
首先, 本示例按状态对全部账户进行分组, 而后返回按降序 (也为默认值) 排序的前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" : 1000,
"max_score" : 0.0,
"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个账户, 其次是27账户 (TX
得克萨斯州), 其次是25账户 (AL
阿拉巴马州), 等等。
请注意, 咱们设置为不显示搜索命中size=0
, 由于咱们只但愿看到聚合结果在响应中。
本示例计算平均账户余额 (仅针对按降序排序的前10个状态):
GET /bank/_search
{
"size": 0,
"aggs": {
"group_by_state": {
"terms": {
"field": "state.keyword"
},
"aggs": {
"average_balance": {
"avg": {
"field": "balance"
}
}
}
}
}
}
复制代码
在上一个聚合的基础上, 如今让咱们按降序排序平均余额:
GET /bank/_search
{
"size": 0,
"aggs": {
"group_by_state": {
"terms": {
"field": "state.keyword",
"order": {
"average_balance": "desc"
}
},
"aggs": {
"average_balance": {
"avg": {
"field": "balance"
}
}
}
}
}
}
复制代码
本示例演示如何按年龄(年龄20-2九、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"
}
}
}
}
}
}
}
}
复制代码
Elasticsearch 是一个简单而复杂的产品。迄今为止, 咱们已经了解了它的基本知识、如何查看它的内部以及如何使用一些 REST api 来处理它。