通过对前面内容的学习,我相信你的机器上已经成功安装上了 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 执行以下示例可以删除索引:
# 删除索引 booksDELETE 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 中指定 indexGET /_mget{"docs": [{ "_index": "books", "_id": "1" },{ "_index": "books", "_id": "2" }]}# 2:直接指定 indexGET /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 使用如下:
# 删除文档 2DELETE 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 的使用还有点生疏的话,可以参考官方文档多实操几次。
好了,今天的内容到此为止。欢迎你在留言区与我分享你的想法,我们一起交流、一起进步。
