7. 关于ES数据读写那点事儿
7. 关于ES数据读写那点事儿
赵洲洋【ElasticSearch系列连载】7. 关于ES数据读写那点事儿
1 对文档建索引
1.1 自定义文档ID
如果数据本身有自己的唯一标记,那么在建立索引时可以使用id来指定文档的id。
如下,使用curl在your_index索引下写入一个id=1001的文档。
1 | curl -H "Content-Type: application/json" -XPOST 'http://es00:9200/your_index/_doc/1001' -d ' |
返回如下
1 | { |
可以看到除了index, type和id字段,还有一个version字段。
在ES中每个文档都有一个自己的version编号,每当文档发生变化时,version就会增长。
1.2 使用自增ID
如果文档没有唯一标识,也可以让ES帮你自动生成文档ID,对应请求的方式也要发生变化:使用POST方法。
1 | curl -H "Content-Type: application/json" -XPOST 'http://es00:9200/your_index/_doc' -d' |
会自动生成一个id,使用base64编码的UUID。
1 | { |
2 获取文档
使用GET请求获取id为1001的文档。
1 | http://es00:9200/your_index/_doc/1001?pretty |
返回的数据体在_source字段中。
如果你查找的文档不存在,found字段会变为false(同时返回的HTTP状态码为404)。
1 | { |
3 只获取文档的部分字段
正常情况下GET请求会将文档的所有字段都进行返回。但如果你只需要部分字段的话,可以通过在URL中使用_source参数来控制需要返回的字段。
1 | http://es00:9200/your_index/_doc/1001?pretty&_source=field |
_source中只包含了需要的字段部分。
1 | { |
如果只是想要获取数据体而不需要其他辅助信息,那么使用_source即可。
1 | http://es00:9200/your_index/_doc/1001/_source |
返回如下,直接是数据体
1 | { |
4 文档更新
首先,文档数据在ES中是不可修改的。所以当我们需要对一个已有的文档进行更新时,会自动进行如下的系列操作:
- 获取要更新的文档
- 根据修改请求对其在临时存储中进行修改
- 删掉要更新的文档
- 重建文档
即:先删除,再新建
请求方式和新建文档一样。
1 | curl -H "Content-Type: application/json" -XPOST 'http://es00:9200/your_index/_doc/1001' -d ' |
返回如下
1 | { |
5 创建全新文档
如果我们只希望在文档不存在时新建文档,而不是无条件覆盖的话,可以在请求时添加_create,这样的话,如果这个文档id已经存在则不会新建成功。
比如请求如下,仍然新建id为1001的文档。
1 | curl -H "Content-Type: application/json" -XPOST 'http://es00:9200/your_index/_doc/1001/_create' -d ' |
返回如下,409
1 | { |
6 删除文档
进行如下DELETE请求即可进行id=1001的文档删除操作。
1 | curl -XDELETE 'http://es00:9200/your_index/_doc/1001' |
如果文档存在,返回如下(deleted)
1 | { |
如果文档不存在,返回如下(not_found, 404)
1 | { |
7 处理冲突
如上文所述,如果我们要更新一个文档,需要先读取到原始的文档,对其进行临时修改,然后重新新建这个文档。
即:如果两个人同时获取 & 修改文档,谁最后修改,谁的就生效。
大部分情况下都不影响:
- 比如数据是从关系型数据库同步过来的,无所谓冲突
- 比如虽然两个人都是修改,但就是按照先后顺序就好了,业务上可以承受另一个人的修改不生效
但是有时候就会影响,比如售卖一个东西,如图
两个人同时购买时,两个网站此时获取到的库存都是100,购买时两个网站都试图将100减一变成99,一起更新,最后的库存变成99(应该是98)。
不难发现,当更新变动的频率越快,以及读取数据到更新数据的周期越长时,就越容易出现上面问题。
在大数据的领域,通常有两种方式保障并发更新的正确性。
悲观并发控制
这个是关系型数据库广泛使用的,假设冲突修改会经常发生,所以会很严格的控制对数据资源的访问来防止冲突的发生。比如:在读取数据时会将目标数据上锁,确保只有这个线程可以修改这条数据,期间其他线程无法对这条数据进行操作
乐观并发控制
也是ES使用的并发控制模式。这种模式假设冲突不经常发生,所以在尝试数据修改时不会加锁、不会阻碍其他线程对数据的访问。但是,如果在读取到更新期间发现数据有过更新记录,则可以阻止这次的更新操作并报错。剩下的交给调用方去判断:是继续使用新的数据再来一次更新
8 文档版本控制
ES是一个分布式系统,所以当一个文档发生变化时,ES需要将这个文档的变化推送到各个节点中去,这个过程是并发的,所以ES需要有一个机制保证一个文档的旧版本不会覆盖新版本。通过在更新数据时添加long类型的version字段可以保障这一点:如果ES发现此次更新数据携带的version低于目前数据的version则将会拒绝。
1 | curl -H "Content-Type: application/json" -XPOST 'http://es00:9200/your_index/_doc/1001?version=1&version_type=external' -d ' |
9 对文档进行局部更新
通过使用_update方法即可对文档的部分字段进行局部更新,但是我们要知道,这个局部更新也没有违背”ES中的数据都是只读“的原则,也是进行了如下系列的操作:
- 获取要更新的文档
- 根据修改请求对其在临时存储中进行修改
- 删掉要更新的文档
- 重建文档
1 | curl -H "Content-Type: application/json" -XPOST 'http://es00:9200/your_index/_doc/1001/_update' -d ' |
10 使用脚本更新文档
我们可以使用ES自带的脚本语言Groovy进行文档的更新。
1 新建一个条数据
1 | curl -H "Content-Type: application/json" -XPOST 'http://es00:9200/your_index/_doc/1001' -d ' |
2 count + 20
1 | curl -H "Content-Type: application/json" -XPOST 'http://es00:9200/your_index/_doc/1001/_update' -d ' |
3 upsert
如果担心更新的目标没有对应字段(比如上面的count),可以使用upsert指定默认值。
1 | curl -H "Content-Type: application/json" -XPOST 'http://es00:9200/your_index/_doc/1002/_update' -d ' |
11 更新冲突重试
通过指定retry_on_conflict字段,即可在更新失败时进行自动重试,减少丢数据的风险。
1 | curl -H "Content-Type: application/json" -XPOST 'http://es00:9200/your_index/_doc/1002/_update?retry_on_conflict=5' -d ' |
12 批量获取不同类型的文档数据
当你需要同时获取不同索引的数据时,可以使用mget方法,比如:
1 | curl -H "Content-Type: application/json" -XGET 'http://es00:9200/_mget' -d ' |
批量执行建议
通常一次请求文档数量在1000-5000个,请求体大概控制在5-15MB。