# 数据操作
# 集群健康
运行 _cat
API
curl -X GET "localhost:9200/_cat/health?v" |
返回内容:
epoch timestamp cluster status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent | |
1475247709 17:01:49 elasticsearch green 1 1 0 0 0 0 0 0 - 100.0% |
status
字段指示着当前集群在总体上是否工作正常。它的三种颜色含义如下:green
: 所有的主分片和副本分片都正常运行。yellow
: 所有的主分片都正常运行,但不是所有的副本分片都正常运行。red
: 有主分片没能正常运行。
还可以获取集群中节点列表
curl -X GET "localhost:9200/_cat/nodes?v&pretty" |
ip heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name | |
127.0.0.1 10 5 5 4.46 mdi * PB2SGZY |
有个名为 PB2SGZY
的节点,是集群中的单个节点。
列出所有 index
curl -X GET "localhost:9200/_cat/indices?v&pretty" |
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size | |
// 第二行是空,说明当前没有 index |
# 文档
就是一个存储的内容,类似于 document DB /mongoBD
一条信息的存储是以文档进行存储的。一次存一条文档。
_index
:文档在哪存放_type
:文档表示的对象类别(弃用)_id
:文档唯一标识
# 创建索引
PUT /customer?pretty | |
GET /_cat/indices?v |
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size | |
yellow open customer 95SQ4TSUT7mWBT7VNHH67A 5 1 0 0 260b 260b |
回应的结果显示,现在又一个名为 customer
的 index,有 5 个主分片和 1 个副本(默认值),其中有 0 个文档。
health 是黄色,表明没有备份或者其他什么问题。因为这个新建的 index 只有一个节点在运行,在另外一个节点加入 es 集群之前,这个集群是没有备份的,如果加入了第二个节点,副本一分配,这个节点的健康状态就会变成绿色。
# 新建文档
新建文档,使用自己的 id
PUT /{index}/{type}/{id} | |
{ | |
"field": "value", | |
... | |
} |
如果不输入 {id},会新建一个
如果 index 不存在,es 会自动创建一个 index。
GET /customer/_doc/1?pretty |
{ | |
"_index" : "customer", | |
"_type" : "_doc", | |
"_id" : "1", | |
"_version" : 1, | |
"found" : true, | |
"_source" : { "name": "John Doe" } | |
} |
当我们索引一个文档,怎么确认我们正在创建一个完全新的文档,而不是覆盖现有的呢?
请记住, _index 、 _type 和 _id 的组合可以唯一标识一个文档。
PUT /website/blog/123?op_type=create | |
{ ... } | |
PUT /website/blog/123/_create | |
{ ... } |
如果创建新文档的请求成功执行,Elasticsearch 会返回元数据和一个 201 Created
的 HTTP 响应码。
另一方面,如果具有相同的 _index
、 _type
和 _id
的文档已经存在,Elasticsearch 将会返回 409 Conflict
响应码,以及如下的错误信息:
{ | |
"error": { | |
"root_cause": [ | |
{ | |
"type": "document_already_exists_exception", | |
"reason": "[blog][123]: document already exists", | |
"shard": "0", | |
"index": "website" | |
} | |
], | |
"type": "document_already_exists_exception", | |
"reason": "[blog][123]: document already exists", | |
"shard": "0", | |
"index": "website" | |
}, | |
"status": 409 | |
} |
# 删除文档
删除索引
DELETE /customer?pretty |
和删除文档一样
DELETE /website/blog/123 |
如果找到该文档,Elasticsearch 将要返回一个 200 ok
的 HTTP 响应码,和一个类似以下结构的响应体。注意,字段 _version
值已经增加:
{ | |
"found" : true, | |
"_index" : "website", | |
"_type" : "blog", | |
"_id" : "123", | |
"_version" : 3 | |
} |
如果文档没有找到,我们将得到 404 Not Found
的响应码和类似这样的响应体:
{ | |
"found" : false, | |
"_index" : "website", | |
"_type" : "blog", | |
"_id" : "123", | |
"_version" : 4 | |
} |
即使文档不存在( Found
是 false
), _version
值仍然会增加。这是 Elasticsearch 内部记录本的一部分,用来确保这些改变在跨多节点时以正确的顺序执行。
# 更新文档
在 Elasticsearch 里文档是 不可改变 的,不能修改它们。相反,如果想要更新现有的文档,需要 重建索引 或者进行替换,比如删掉一个再新建一个。
PUT /website/blog/123
{
"title": "My first blog entry",
"text": "I am starting to get the hang of this...",
"date": "2014/01/02"
}
在响应体中,我们能看到 Elasticsearch 已经增加了 _version
字段值:
{ | |
"_index" : "website", | |
"_type" : "blog", | |
"_id" : "123", | |
"_version" : 2, | |
"created": false | |
} |
created
标志设置成 false
,是因为相同的索引、类型和 ID 的文档已经存在。
在内部,Elasticsearch 已将旧文档标记为已删除,并增加一个全新的文档。 尽管你不能再对旧版本的文档进行访问,但它并不会立即消失。当继续索引更多的数据,Elasticsearch 会在后台清理这些已删除文档。
也可以使用简单的脚本执行更新,例如将年龄 + 5
POST /customer/_doc/1/_update?pretty | |
{ | |
"script" : "ctx._source.age += 5" | |
} |
# 简单查询文档
为了从 Elasticsearch 中检索出文档,我们仍然使用相同的 _index
, _id
,但是 HTTP 谓词更改为 GET
:
参数方式请求
GET /bank/_search?q=*&sort=account_number:asc&pretty |
q=\*
参数指示 es 匹配索引中的所有文档sort=account_number:asc
尝试知名对 account_number 升序排序,pretty
表示返回便于识别的 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
: es 的执行搜索时间 单位 mstime_out
: 是否超时_shards
: 搜索了多少个分片,搜索成功和失败的分片数。hits
: 搜索结果hits.total
: 搜索结果的数量hits.hits
: 实际搜索结果数组hits.sort
: 结果的排序键
不同于上面的参数方式请求,这是请求体方式请求,就是把查询的内容放入 json body 中。
GET /bank/_search | |
{ | |
"query": { "match_all": {} }, | |
"sort": [ | |
{ "account_number": "asc" } | |
] | |
} |
请求体方式请求
GET /bank/_search | |
{ | |
"query": { "match_all": {} }, | |
"sort": { "balance": { "order": "desc" } } | |
"from": 10, | |
"size": 5 | |
} |
match_all
: 查询索引内部所有文档。sort
: 排序from、size
: 类似于 mysql limit 10,5
如果 size 未指定,默认是 10,
返回一部分文档
默认情况下, GET
请求会返回整个文档,这个文档正如存储在 _source
字段中的一样。
GET /website/blog/123?_source=title,text |
{ | |
"_index" : "website", | |
"_type" : "blog", | |
"_id" : "123", | |
"_version" : 1, | |
"found" : true, | |
"_source" : { | |
"title": "My first blog entry" , | |
"text": "Just trying this out..." | |
} | |
} |
如果只要 source 字段,不要元数据
GET /website/blog/123/_source |
{ | |
"title": "My first blog entry", | |
"text": "Just trying this out...", | |
"date": "2014/01/01" | |
} |
bool 查询
GET /bank/_search | |
{ | |
"query": { | |
"bool": { | |
"must": [ | |
{ "match": { "address": "mill" } }, | |
{ "match": { "address": "lane" } } | |
] | |
} | |
} | |
} |
must
子语句里面的所有条件都必须满足才是匹配的数据。
相反的,还有一个 must_not
GET /bank/_search | |
{ | |
"query": { | |
"bool": { | |
"must_not": [ | |
{ "match": { "address": "mill" } }, | |
{ "match": { "address": "lane" } } | |
] | |
} | |
} | |
} |
must_not
内的语句必须全不满足才是匹配的数据。
bool
里允许混合条件,也就是同时使用 must 和 must_not、should
GET /bank/_search | |
{ | |
"query": { | |
"bool": { | |
"must": [ | |
{ "match": { "age": "40" } } | |
], | |
"must_not": [ | |
{ "match": { "state": "ID" } } | |
] | |
} | |
} | |
} |
bool
里还有 filter
用于过滤文档
//eg 选取余额大于 20000,小于 30000 的文档 | |
GET /bank/_search | |
{ | |
"query": { | |
"bool": { | |
"must": { "match_all": {} }, | |
"filter": { | |
"range": { | |
"balance": { | |
"gte": 20000, | |
"lte": 30000 | |
} | |
} | |
} | |
} | |
} | |
} |
聚合 (group by)
对数据进行分组和统计信息,就跟 mysql 一样,近似于 sql group by
GET /bank/_search | |
{ | |
"size": 0, | |
"aggs": { | |
"group_by_state": { | |
"terms": { | |
"field": "state.keyword" | |
} | |
} | |
} | |
} |
等同于
SELECT state, COUNT(*) FROM bank GROUP BY state ORDER BY COUNT(*) DESC LIMIT 10; |
response
{ | |
"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 | |
} ] | |
} | |
} | |
} |
对余额进行排序
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" | |
} | |
} | |
} | |
} | |
} | |
} |
检查文档是否存在
curl -i -XHEAD http://localhost:9200/website/blog/123 |
如果文档存在, Elasticsearch 将返回一个 200 ok
的状态码:
HTTP/1.1 200 OK | |
Content-Type: text/plain; charset=UTF-8 | |
Content-Length: 0 |
若文档不存在, Elasticsearch 将返回一个 404 Not Found
的状态码:
curl -i -XHEAD http://localhost:9200/website/blog/124 |
HTTP/1.1 404 Not Found | |
Content-Type: text/plain; charset=UTF-8 | |
Content-Length: 0 |
当然,一个文档仅仅是在检查的时候不存在,并不意味着一毫秒之后它也不存在:也许同时正好另一个进程就创建了该文档。
# 批量查询文档
将多个请求合并成一个,避免单独处理每个请求花费的网络延时和开销。 如果你需要从 Elasticsearch 检索很多文档,那么使用 multi-get 或者 mget
API 来将这些检索请求放在一个请求中,将比逐个文档请求更快地检索到全部文档。
mget
API 要求有一个 docs
数组作为参数,每个元素包含需要检索文档的元数据, 包括 _index
、 _type
和 _id
。如果你想检索一个或者多个特定的字段,那么你可以通过 _source
参数来指定这些字段的名字:
GET /_mget | |
{ | |
"docs" : [ | |
{ | |
"_index" : "website", | |
"_type" : "blog", | |
"_id" : 2 | |
}, | |
{ | |
"_index" : "website", | |
"_type" : "pageviews", | |
"_id" : 1, | |
"_source": "views" | |
} | |
] | |
} |
该响应体也包含一个 docs
数组, 对于每一个在请求中指定的文档,这个数组中都包含有一个对应的响应,且顺序与请求中的顺序相同。
{ | |
"docs" : [ | |
{ | |
"_index" : "website", | |
"_id" : "2", | |
"_type" : "blog", | |
"found" : true, | |
"_source" : { | |
"text" : "This is a piece of cake...", | |
"title" : "My first external blog entry" | |
}, | |
"_version" : 10 | |
}, | |
{ | |
"_index" : "website", | |
"_id" : "1", | |
"_type" : "pageviews", | |
"found" : true, | |
"_version" : 2, | |
"_source" : { | |
"views" : 2 | |
} | |
} | |
] | |
} |
如果想检索的数据都在相同的 _index
中(甚至相同的 _type
中),则可以在 URL 中指定默认的 /_index
或者默认的 /_index/_type
。
你仍然可以通过单独请求覆盖这些值:
GET /website/blog/_mget | |
{ | |
"docs" : [ | |
{ "_id" : 2 }, | |
{ "_type" : "pageviews", "_id" : 1 } | |
] | |
} |
如果所有文档的 _index
和 _type
都是相同的,你可以只传一个 ids
数组,而不是整个 docs
数组:
GET /website/blog/_mget | |
{ | |
"ids" : [ "2", "1" ] | |
} |
即使有某个文档没有找到,上述请求的 HTTP 状态码仍然是 200
。事实上,即使请求 没有 找到任何文档,它的状态码依然是 200
-- 因为 mget
请求本身已经成功执行。 为了确定某个文档查找是成功或者失败,你需要检查 found
标记。
# 批量操作
与 mget
可以使我们一次取回多个文档同样的方式, bulk
API 允许在单个步骤中进行多次 create
、 index
、 update
或 delete
请求。 如果你需要索引一个数据流比如日志事件,它可以排队和索引数百或数千批次。
bulk
与其他的请求体格式稍有不同,如下所示:
{ action: { metadata }}\n | |
{ request body }\n | |
{ action: { metadata }}\n | |
{ request body }\n | |
... |
这种格式类似一个有效的单行 JSON 文档 流 ,它通过换行符 ( \n
) 连接到一起。注意两个要点:
每行一定要以换行符 ( \n
) 结尾, 包括最后一行 。这些换行符被用作一个标记,可以有效分隔行。
这些行不能包含未转义的换行符,因为他们将会对解析造成干扰。这意味着这个 JSON 不 能使用 pretty 参数打印。
POST /_bulk | |
{ "delete": { "_index": "website", "_type": "blog", "_id": "123" }} | |
{ "create": { "_index": "website", "_type": "blog", "_id": "123" }} | |
{ "title": "My first blog post" } | |
{ "index": { "_index": "website", "_type": "blog" }} | |
{ "title": "My second blog post" } | |
{ "update": { "_index": "website", "_type": "blog", "_id": "123", "_retry_on_conflict" : 3} } | |
{ "doc" : {"title" : "My updated blog post"} } |
delete
动作不能有请求体,它后面跟着的是另外一个操作。
最后一个换行符不要落下。