通过对前面内容的学习,我相信你的机器上已经成功安装上了 ES,并且对 ES 有个大概的了解了。现在是不是迫不及待想尝试一下 ES 提供的功能呢?其实 ES 官方提供了各种各样的功能接口,而我们今天先来学习操作文档的基本接口。如果需要了解更多的接口使用方式,可以参考官方文档。
文档基本操作接口有:
- 新建文档,提供了索引文档(Index doc)和创建文档(create doc)两种方式。
- 通过文档 ID 获取文档,通过 Get API 来获取文档内容。
- 批量获取文档,使用 MGET API 来批量获取文档。
- 更新文档,使用 Update API 来更新文档。
- 删除文档,使用 Delete API 来删除文档。
- 批量操作文档,使用 Bulk API 来批量操作文档。
本文除了介绍上述的基本文档操作接口外,还会简单介绍索引的创建、删除操作。今天我们就以在线书店业务为例子来介绍上面的 API 吧。
一、索引管理
现阶段在线书店的业务很简单,我们的模型只需要记录书本的 ID、名字、作者、简介即可。所以我们可以定义如下 Mapping,并且创建索引:
# 创建 books 索引
PUT books
{
"mappings": {
"properties": {
"book_id": {
"type": "keyword"
},
"name": {
"type": "text"
},
"author": {
"type": "keyword"
},
"intro": {
"type": "text"
}
}
},
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
}
}
# 返回结果
{
"acknowledged" : true,
"shards_acknowledged" : true,
"index" : "books"
}
在 Kibana 中运行上述示例,可以创建 books 索引。books 索引包含 book_id(书本 ID)、name(书本名字)、author(作者)、intro(简介)四个字段,可以满足我们现阶段的需求。其中 books 索引还有 3 个分片和 1 个副本备份。
如果你之前创建过 books 索引的话,这里再次创建会报错,所以你需要先将之前的索引删除,然后再重新创建。在 Kibana 执行以下示例可以删除索引:
# 删除索引 books
DELETE books
# 返回结果
{
"acknowledged" : true
}
如上所示,acknowledged 为 true,则删除成功,如果返回的 http 状态码为 404,则说明这个索引本身不存在。
ok,在准备好索引后,我们开始学习文档基本操作的 API。
二、新建文档
这天书店进了一批电子书,管理员需要把这些书录入到 ES 中。ES 提供了两种创建文档的方式,一种是使用 Index API 索引文档,一种是使用 Create API 创建文档。
1. 使用 Index API 索引文档
# 使用 Index API 索引文档
PUT books/_doc/1
{
"book_id": "4ee82462",
"name": "深入Linux内核架构",
"author": "Wolfgang Mauerer",
"intro": "内容全面深入,领略linux内核的无限风光。"
}
# 结果
{
"_index" : "books",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 2,
"failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 1
}
如上示例,可以看到索引一个文档是比较简单的,将 Mapping 中对应的字段做成 Json Object 对应的 Key 即可,并且上面的例子中我们指定文档 ID 为 1。需要说明的是,在 ES 7.0 版本后 type 统一为 _doc。
如果我们索引的文档已经存在,会发生什么情况?你可以在不改变文档 ID 的情况下多次执行上面的索引文档的语句,可以发现系统并不会报错,而是将返回结果中 “_version” 字段的值自加。
其实在索引一个文档的时候,如果文档 ID 已经存在,会先删除旧文档,然后再写入新文档的内容,并且增加文档版本号。
2. 使用 Create API 创建文档
使用 Create API 创建文档有两种写法:PUT 和 POST 方式,其示例分别如下:
# 使用 PUT 的方式创建文档
PUT books/_create/2
{
"book_id": "4ee82463",
"name": "时间简史",
"author": "史蒂芬霍金",
"intro": "探索时间和空间核心秘密的引人入胜的故事。"
}
# PUT 方式返回的结果
{
"_index" : "books",
"_type" : "_doc",
"_id" : "2",
"_version" : 1
......
}
# 使用 POST 的方式,不需要指定文档 ID, 系统自动生成
POST books/_doc
{
"book_id": "4ee82464",
"name": "时间简史(插画版)",
"author": "史蒂芬霍金",
"intro": "用精美的插画带你探索时间和空间的核心秘密"
}
# POST 方式返回的结果
{
"_index" : "books",
"_type" : "_doc",
"_id" : "LfwVtH0BxOuNtEd4yM4F",
"_version" : 1
......
}
如上示例,使用 PUT 的方式创建文档需要指定文档的 ID,如果文档 ID 已经存在,则返回 http 状态码为 409 的错误。而使用 POST 的方式创建文档时候,则不需要指定文档 ID,系统会自动创建,在返回结果中可以看到,新创建的文档的 ID 为 ‘LfwVtH0BxOuNtEd4yM4F’。
序号 | 语句 | 特性描述 |
---|---|---|
1 | PUT books/_doc/1 | 使用 Index API 索引文档,如果文档存在,会先删除然后再写入,即有覆盖原内容的功能。 |
2 | PUT books/_create/2 | Create API 中使用 PUT 的方式创建文档,需要指定文档 ID。如果文档已经存在,则返回 http 409 错误。 |
3 | POST books/_doc | Create API 中使用 POST 的方式,不需要指定文档 ID, 系统自动生成。 |
上表是新建文档时 3 种写法的总结,如果你有更新文档内容的需求,应该使用第一种方式。如果写入文档时有唯一性校验需求的话,应该使用第二种方式。如果需要系统为你创建文档 ID,应该使用第三种方式。相对于第一种方式来说,第三种方式写入的效率会更高,因为不需要在库里查询文档是否已经存在,并且进行后续的删除工作。
三、获取文档
当用户需要通过书本 ID 获取书本信息的时候,可以使用 ES 提供的 GET API 来获取文档内容。获取文档有 2 种情况,一种是只获取一个文档内容,另一种是同时获取多个文档的内容。
1. 使用 GET API 获取单个文档
通过书本的 ID 获取书本的信息时可以使用 GET API 来实现,其示例如下:
# 使用 GET API 获取单个文档的内容
GET books/_doc/1
# 结果
{
"_index" : "books",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"_seq_no" : 0,
"_primary_term" : 1,
"found" : true,
"_source" : {
"book_id" : "4ee82462",
"name" : "深入Linux内核架构",
"author" : "Wolfgang Mauerer",
"intro" : "内容全面深入,领略linux内核的无限风光。"
}
}
如上示例,GET API 是非常简单的,使用时只需要指定文档 ID 即可。文档的原生内容保存在 “_source” 字段中,其他字段是这个文档的元数据。如果成功,返回的是 http 状态码为 200,如果文档不存在则 http 状态码为 404,并且 found 字段为 false。
GET API 提供了多个参数,更多的信息可以参考官方文档,下面是几个比较常用的:
参数 | 简介 |
---|---|
preference | 默认的情况下,GET API 会从多个副本中随机挑选一个,设置 preference 参数可以控制 GET 请求被路由到哪个分片上执行。 |
realtime | 控制 GET 请求是实时的还是准实时的,默认为 true。 |
refresh | 是否在执行 GET 操作前执行 refresh(后续我们有介绍),默认为 false。 |
routing | 自定义 routing key。 |
stored_fields | 返回在 Mapping 中 store 设置为 true 的字段,而不是 _source。默认为 false。 |
_source | 指定是否返回 _source 的字段,或者设置某些需要返回的字段。 |
_source_excludes | 不返回哪些字段,逗号分割的字符串列表。如果 _source 设置为 false,此参数会被忽略。 |
_source_includes | 返回哪些字段,逗号分割的字符串列表。如果 _source 设置为 false,此参数会被忽略。 |
version | 指定版本号,如果获取的文档的版本号与指定的不一样,返回 http 409。 |
2. 使用 MGET API 获取多个文档
当我们需要通过多个文档 ID 同时获取它们的信息时,如果使用 GET API 的话,需要发起多个请求,这样做效率比较低下。可以使用 ES 提供的 MGET API 来解决这个需求,MGET API 的请求格式有 3 种,其示例如下:
# 1:在 body 中指定 index
GET /_mget
{
"docs": [
{ "_index": "books", "_id": "1" },
{ "_index": "books", "_id": "2" }
]
}
# 2:直接指定 index
GET /books/_doc/_mget
{
"docs": [
{ "_id": "1" },
{ "_id": "2" }
]
}
# 3:也可以简写为一下例子
GET /books/_mget
{
"ids" : ["1", "2"]
}
# 结果
{
"docs" : [
{
"found" : true,
"_source" : {
"book_id" : "4ee82462",
"name" : "深入Linux内核架构"
......
}
},
{
"found" : true,
"_source" : {
"book_id" : "4ee82463",
"name" : "时间简史",
......
}
}
]
}
如上示例,如果在 body 中指定 index,可以获取多个索引中的文档数据,比较灵活。而使用直接指定 index 的方式只能获取指定索引中的文档。更多的 MGET API 使用例子可以参考官方文档。同样,如果对应的文档找不到,found 字段为 false。
四、更新文档
有时候发现一些书籍的信息有误,需要进行修改,ES 提供了 Update API 来更新文档信息,我们可以通过这个接口来更新书本的信息。
更新一个文档,需要指定文档的 ID 和需要更新的字段与其对应的值。Update API 使用如下:
# 更新文档
POST books/_update/2
{
"doc": {
"name":"时间简史(视频版)",
"intro": "探索时间和空间核心秘密的引人入胜的视频故事。"
}
}
#结果
{
"_index" : "books",
"_type" : "_doc",
"_id" : "2",
"_version" : 3,
"result" : "updated",
......
}
上述示例更新了文档 2 的 name 和 intro 字段。如返回结果所示,版本号会增加,”result” 字段为 updated。
上面提到过,索引文档的方式也有更新数据的效果,那它跟文档更新接口有啥区别呢?其实索引文档的更新效果是先删除数据,然后再写入新数据。所以索引文档的方式会覆盖旧的数据,使其无法实现只更新某些字段的需求。
除了使用指定 ID 的方式来更新数据外,还可以用 update_by_query 的方式:
POST books/_update_by_query
{
"query": {
"term": {
"book_id": {
"value": "4ee82462"
}
}
},
"script": {
"source": "ctx._source.name='深入Linux内核架构1'",
"lang": "painless"
}
}
如上示例,我们将 book_id 为 ‘4ee82462’ 的文档的 name 字段进行了更新。我们可以使用 DSL 中 ‘script’ 字段来指定一段脚本。脚本中 ‘ctx._source’ 可以拿到匹配的文档的数据,所以 ctx._source.name=’深入Linux内核架构1’ 的意思是将匹配文档的 ‘name’ 字段的数据设置为 ‘深入Linux内核架构1’。
需要注意的是,如果 query 中匹配的文档数量巨大,那么这个接口在 kibana 中执行的时候可能会发生超时的错误,当然你可以在请求中使用 timeout 参数,但是这个不是解决问题的有效方案。我建议你使用异步的方式进行处理,并且控制需要更新的数据量,例如按日期分批来进行更新。使用异步方式的实例如下:
POST books/_update_by_query?wait_for_completion=false
{
"query": {
"term": {
"book_id": {
"value": "4ee82462"
}
}
},
"script": {
"source": "ctx._source.name='深入Linux内核架构1'",
"lang": "painless"
}
}
结果:
{
"task" : "R8Zd-p4GRXmBovdOQaOyqA:29677918"
}
如上示例,异步的方式返回了一个 task id,然后可以用这个 task id 查询任务的执行情况:
GET /_tasks/R8Zd-p4GRXmBovdOQaOyqA:29677918
_update_by_query 的接口参数有很多,更详细的使用示例请参考官方文档。
五、删除文档
当书店不再拥有某本书的版权时,我们需要对其进行删除(现实业务一般是用下架,这里只是举例子啦)。ES 提供了 Delete API 来删除一个文档,删除一个文档是非常简单的,只需要指定索引和文档 ID 即可。Delete API 使用如下:
# 删除文档 2
DELETE books/_doc/2
# 结果
{
"_index" : "books",
"_type" : "_doc",
"_id" : "2",
"_version" : 4,
"result" : "deleted",
......
}
如上示例,如果文档存在则删除成功,”result” 字段为 “deleted”。如果文档本身不存在,则返回 http 的状态码为 404。
除了指定文档 ID 进行文档删除外,我们还可以使用 Delete By Query API 进行查询删除。
# 使用 Delete By Query API 删除文档
POST /books/_delete_by_query
{
"query": {
"term": {
"book_id": "4ee82462"
}
}
}
# 结果
{
"total" : 1,
"deleted" : 1,
......
"failures" : [ ]
}
如上示例的返回结果中,”deleted” 字段为 1,即删除了 1 个文档。至于查询的内容是什么意思,我们在后续的内容中将会有详细介绍。现在你只需要知道,”term” 字段下面填写文档的对应的字段和需要查询的值即可,本示例中我们查询了书本 ID 为 “4ee82462” 的文档。
六、批量操作文档
当我们需要写入多个文档的时候,如果每写一个文档就发起一个请求的话,多少有点浪费。这个时候我们可以使用 Bulk API 来批量处理文档。
Bulk API 支持在一次调用中操作不同的索引,使用时可以在 Body 中指定索引也可以在 URI 中指定索引。而且还可以同时支持 4 中类型的操作:
- Index
- Create
- Update
- Delete
Bulk API 的格式是用换行符分隔 JSON 的结构,第一行指定操作类型和元数据(索引、文档id等),紧接着的一行是这个操作的内容(文档数据,如果有的话。像简单的删除就没有。),其格式如下:
POST _bulk
# 第一行指定操作类型和元数据(索引、文档id等)
{ "index" : { "_index" : "books", "_id" : "1" } }
# 紧接着的一行是这个操作的内容(文档数据,如果有的话。像简单的删除就没有)
{ "book_id": "4ee82462","name": "深入Linux内核架构", ......}
下面示例是在 Bulk API 中同时使用多种操作类型的例子:
# 在 Bulk API 中同时使用多种操作类型的实例
POST _bulk
{ "index" : { "_index" : "books", "_id" : "1" } }
{ "book_id": "4ee82462","name": "深入Linux内核架构","author": "Wolfgang Mauerer","intro": "内容全面深入,领略linux内核的无限风光。" }
{ "delete" : { "_index" : "books", "_id" : "2" } }
{ "create" : { "_index" : "books", "_id" : "3" } }
{ "book_id": "4ee82464","name": "深入Linux内核架构第三版","author": "Wolfgang Mauerer","intro": "内容全面深入,再次领略linux内核的无限风光。" }
{ "update" : {"_index" : "books", "_id" : "4"} } # 指定操作类型、索引、文档 id
{ "doc" : {"intro" : "书本的内容非常好,值得一看"} } # 指定文档内容
# 结果
{
"items" : [
{
"index" : {
"_id" : "1",
"result" : "created",
......
}
},
{
"delete" : {
"_id" : "2",
"result" : "deleted",
......
}
},
{
"create" : {
"_id" : "3",
"result" : "created",
......
}
},
{
"update" : {
"_id" : "4",
"status" : 404,
......
}
}
]
}
因为一个请求中有多个操作,所以返回结果中会对每个操作有相应的执行结果。如果其中一条操作失败,是不会影响其他操作的执行。
更详细 Bulk API 使用方式,可以参考官方文档。
七、总结
今天为你介绍了文档的基础操作,包括新建文档、获取文档、更新文档、删除文档、批量操作文档,这些内容都比较简单,相信你都已经掌握了。下面就来总结一下今天的内容吧。
索引的创建比较简单,我们要先定义好业务需要的 JSON,然后构建 Mapping,最后创建索引。
新建文档可以有多种方式,使用 Index API 可以索引一个文档,如果文档已经存在,则先删除文档,然后再写入新的数据。另外还可以使用 Create API 来创建文档,使用 PUT 的方式创建文档时需要指定文档 ID,如果文档已经存在则写入失败。而使用 POST 的方式则不需要指定文档 ID, 系统自动生成。
已经写入的文档可以通过 GET API 来获取,如果需要同时获取多个文档内容,可以使用 MGET API 来实现。
当我们需要更新、删除文档的时候,可以使用 Update API 和 Delete API。最后我们介绍了 Bulk API 是如何批量操作文档的,Bulk API 支持 4 种类型的操作在一个请求中完成,可以提高了执行效率。
最后,如果大家对这些 API 的使用还有点生疏的话,可以参考官方文档多实操几次。
好了,今天的内容到此为止。欢迎你在留言区与我分享你的想法,我们一起交流、一起进步。