一、索引
1、使用自己的ID
例如我们的索引叫做“website”
,类型叫做“blog”
,我们选择的ID是“123”
,那么这个索引请求就像这样:
PUT /website/blog/123 { "title": "My first blog entry", "text": "Just trying this out...", "date": "2014/01/01" }
2、自增ID
URL现在只包含_index
和_type
两个字段:
POST /website/blog/ { "title": "My second blog entry", "text": "Still trying this out...", "date": "2014/01/01" }
二、获取
1、检索文档
GET /website/blog/123?pretty
pretty
在任意的查询字符串中增加 pretty
参数,类似于上面的例子。会让Elasticsearch美化输出(pretty-print)JSON响应以便更加容易阅读。_source
字段不会被美化,它的样子与我们输入的一致。
2、检索文档的一部分
GET /website/blog/123?_source=title,text
_source
字段现在只包含我们请求的字段,而且过滤了date
字段:
{ "_index" : "website", "_type" : "blog", "_id" : "123", "_version" : 1, "exists" : 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" }
三、检查文档是否存在
HEAD /website/blog/123
Elasticsearch将会返回 200 OK
状态如果你的文档存在:
200 - OK
如果不存在返回 404 Not Found:
HEAD /website/blog/124
404 - Not Found
当然,这只表示你在查询的那一刻文档不存在,但并不表示几毫秒后依旧不存在。另一个进程在这期间可能创建新文档。
四、更新整个文档
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" : 3, "result" : "updated", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 3, "_primary_term" : 1 }
在内部,Elasticsearch已经标记旧文档为删除并添加了一个完整的新文档。旧版本文档不会立即消失,但你也不能去访问它。Elasticsearch会在你继续索引更多数据时清理被删除的文档。
五、创建一个新文档
1、最简单的方式是使用POST
方法让Elasticsearch自动生成唯一 _id
POST /website/blog/ { "title": "My first blog entry", "text": "I am starting to get the hang of this...", "date": "2014/01/02" }
2、使用op_type
查询参数
PUT /website/blog/125?op_type=create { "title": "My first blog entry", "text": "I am starting to get the hang of this...", "date": "2014/01/02" }
3、在URL后加/_create
做为端点
PUT /website/blog/124/_create { "title": "My first blog entry", "text": "I am starting to get the hang of this...", "date": "2014/01/02" }
六、删除文档
删除文档的语法模式与之前基本一致,只不过要使用DELETE
方法:
DELETE /website/blog/124
如果文档被找到,Elasticsearch将返回200 OK
状态码和以下响应体。注意_version
数字已经增加了。
{ "_index" : "website", "_type" : "blog", "_id" : "125", "_version" : 2, "result" : "deleted", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 3, "_primary_term" : 1 }
如果文档未找到,我们将得到一个404 Not Found
状态码,响应体是这样的:
{ "_index" : "website", "_type" : "blog", "_id" : "127", "_version" : 1, "result" : "not_found", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 0, "_primary_term" : 1 }
尽管文档不存在——"found"
的值是false
——_version
依旧增加了。这是内部记录的一部分,它确保在多节点间不同操作可以有正确的顺序。
七、版本控制
例如,创建一个包含外部版本号5
的新博客,我们可以这样做:
PUT /website/blog/2?version=5&version_type=external { "title": "My first external blog entry", "text": "Starting to get the hang of this..." }
在响应中,我们能看到当前的_version
号码是5
:
{ "_index" : "website", "_type" : "blog", "_id" : "2", "_version" : 5, "result" : "created", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 0, "_primary_term" : 1 }
现在我们更新这个文档,指定一个新version
号码为10
:
PUT /website/blog/2?version=10&version_type=external { "title": "My first external blog entry", "text": "This is a piece of cake..." }
请求成功:
{ "_index" : "website", "_type" : "blog", "_id" : "2", "_version" : 10, "result" : "updated", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 1, "_primary_term" : 1 }
八、局部更新
示例:为博客添加一个tags
字段和一个views
字段。
POST /website/blog/1/_update { "doc" : { "tags" : [ "testing" ], "views": 0 } }
请求成功:
{ "_index" : "website", "_type" : "blog", "_id" : "1", "_version" : 3, "result" : "updated", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 6, "_primary_term" : 1 }
检索文档文档显示被更新的_source
字段:
{ "_index" : "website", "_type" : "blog", "_id" : "1", "_score" : 1.0, "_source" : { "title" : "My first blog entry", "text" : "Starting to get the hang of this...", "views" : 0, "tags" : [ "testing" ] } }
使用脚本局部更新
使用Groovy脚本:
默认的脚本语言是Groovy,一个快速且功能丰富的脚本语言,语法类似于Javascript。它在一个沙盒(sandbox)中运行,以防止恶意用户毁坏Elasticsearch或攻击服务器。
脚本能够使用update
API改变_source
字段的内容,它在脚本内部以ctx._source
表示。
示例:使用脚本增加博客的views
数量。
POST /website/blog/1/_update { "script" : "ctx._source.views+=1" }
九、Mget
1、检索多个文档
POST /_mget { "docs" : [ { "_index" : "website", "_type" : "blog", "_id" : 2 }, { "_index" : "website", "_type" : "pageviews", "_id" : 123, "_source": "views" } ] }
如果你想检索的文档在同一个_index
中(甚至在同一个_type
中),你就可以在URL中定义一个默认的/_index
或者/_index/_type
。
POST /website/blog/_mget { "docs" : [ { "_id" : 2 }, { "_type" : "pageviews", "_id" : 1 } ] }
事实上,如果所有文档具有相同_index
和_type
,你可以通过简单的ids
数组来代替完整的docs
数组:
POST /website/blog/_mget { "ids" : [ "2", "1" ] }
注意到我们请求的第二个文档并不存在。我们定义了类型为blog
,但是ID为1
的文档类型为pageviews
。这个不存在的文档会在响应体中被告知。
{ "docs" : [ { "_index" : "website", "_type" : "blog", "_id" : "2", "_version" : 10, "found" : true, "_source" : { "title": "My first external blog entry", "text": "This is a piece of cake..." } }, { "_index" : "website", "_type" : "blog", "_id" : "1", "found" : false <1> } ] }
- <1> 这个文档不存在
事实上第二个文档不存在并不影响第一个文档的检索。每个文档的检索和报告都是独立的。
十、更新时的批量操作
就像 mget
允许我们一次性检索多个文档一样,bulk
API允许我们使用单一请求来实现多个文档的 create
、index
、update
或delete
。这对索引类似于日志活动这样的数据流非常有用,它们可以以成百上千的数据为一个批次按序进行索引。
bulk请求体如下:
{ action: { metadata }}
{ request body }
{ action: { metadata }}
{ request body }
...
这种格式类似于用"
"
符号连接起来的一行一行的JSON文档流(stream)。两个重要的点需要注意:
-
每行必须以
" "
符号结尾,包括最后一行。这些都是作为每行有效的分离而做的标记。 -
每一行的数据不能包含未被转义的换行符,它们会干扰分析——这意味着JSON不能被美化打印。
action/metadata 这一行定义了文档行为(what action)发生在哪个文档(which document)之上。
行为(action)必须是以下几种:
行为 | 解释 |
---|---|
create |
当文档不存在时创建之。详见《创建文档》 |
index |
创建新文档或替换已有文档。见《索引文档》和《更新文档》 |
update |
局部更新文档。见《局部更新》 |
delete |
删除一个文档。见《删除文档》 |
在索引、创建、更新或删除时必须指定文档的_index
、_type
、_id
这些元数据(metadata)。
例如删除请求看起来像这样:
{ "delete": { "_index": "website", "_type": "blog", "_id": "123" }}
这些还被 update
操作所必需,而且请求体的组成应该与update
API(doc
, upsert
, script
等等)一致。删除操作不需要请求体(request body)。
{ "create": { "_index": "website", "_type": "blog", "_id": "123" }} { "title": "My first blog post" }
如果没有定义_id
,ID将会被自动创建:
{ "index": { "_index": "website", "_type": "blog" }} { "title": "My second blog post" }
为了将这些放在一起,bulk
请求表单是这样的:
POST /_bulk { "delete": { "_index": "website", "_type": "blog", "_id": "123" }} <1> { "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"} } <2>
- <1> 注意
delete
行为(action)没有请求体,它紧接着另一个行为(action)。
- <2> 记得最后一个换行符。
Elasticsearch响应包含一个items
数组,它罗列了每一个请求的结果,结果的顺序与我们请求的顺序相同:
{ "took" : 103, "errors" : false, <1> "items" : [ { "delete" : { "_index" : "website", "_type" : "blog", "_id" : "123", "_version" : 5, "result" : "deleted", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 5, "_primary_term" : 1, "status" : 200 } }, { "create" : { "_index" : "website", "_type" : "blog", "_id" : "123", "_version" : 6, "result" : "created", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 6, "_primary_term" : 1, "status" : 201 } }, { "index" : { "_index" : "website", "_type" : "blog", "_id" : "cP66bGkBUi3i0pMApm5N", "_version" : 1, "result" : "created", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 8, "_primary_term" : 1, "status" : 201 } }, { "update" : { "_index" : "website", "_type" : "blog", "_id" : "123", "_version" : 7, "result" : "updated", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 7, "_primary_term" : 1, "status" : 200 } } ] }
- <1> 所有子请求都成功完成。
每个子请求都被独立的执行,所以一个子请求的错误并不影响其它请求。如果任何一个请求失败,顶层的error
标记将被设置为true
,然后错误的细节将在相应的请求中被报告:
POST /_bulk { "create": { "_index": "website", "_type": "blog", "_id": "123" }} { "title": "Cannot create - it already exists" } { "index": { "_index": "website", "_type": "blog", "_id": "123" }} { "title": "But we can update it" }
响应中我们将看到create
文档123
失败了,因为文档已经存在,但是后来的在123
上执行的index
请求成功了:
{ "took" : 42, "errors" : true, <1> "items" : [ { "create" : { "_index" : "website", "_type" : "blog", "_id" : "123", "status" : 409, <2> "error" : { "type" : "version_conflict_engine_exception", <3> "reason" : "[blog][123]: version conflict, document already exists (current version [7])", "index_uuid" : "Uc01DQRhSqq_REs9HsSYPA", "shard" : "0", "index" : "website" } } }, { "index" : { "_index" : "website", "_type" : "blog", "_id" : "123", "_version" : 8, "result" : "updated", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 8, "_primary_term" : 1, "status" : 200 <4> } } ] }
- <1> 一个或多个请求失败。
- <2> 这个请求的HTTP状态码被报告为
409 CONFLICT
。
- <3> 错误消息说明了什么请求错误。
- <4> 第二个请求成功了,状态码是
200 OK
。
这些说明 bulk
请求不是原子操作——它们不能实现事务。每个请求操作时分开的,所以每个请求的成功与否不干扰其它操作。
不要重复
你可能在同一个index
下的同一个type
里批量索引日志数据。为每个文档指定相同的元数据是多余的。就像mget
API,bulk
请求也可以在URL中使用/_index
或/_index/_type
:
POST /website/_bulk { "index": { "_type": "log" }} { "event": "User logged in" }
你依旧可以覆盖元数据行的_index
和_type
,在没有覆盖时它会使用URL中的值作为默认值:
POST /website/log/_bulk { "index": {}} { "event": "User logged in" } { "index": { "_type": "blog" }} { "title": "Overriding the default type" }