# 数据操作

# 集群健康

运行 _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
}

即使文档不存在( Foundfalse ), _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 的执行搜索时间 单位 ms
time_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 允许在单个步骤中进行多次 createindexupdatedelete 请求。 如果你需要索引一个数据流比如日志事件,它可以排队和索引数百或数千批次。

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 动作不能有请求体,它后面跟着的是另外一个操作。
最后一个换行符不要落下。

-->