并发控制的必要性
假设两个请求同时更新某个文档,如果缺乏有效的并发控制,会导致更改的数据丢失。
悲观并发控制
假定有同事写共享变量的请求,会对资源加锁,例如数据库行锁。
乐观并发控制
- 假定冲突是不会发生的,不会阻止正在尝试的操作
- 如果数据在读写中被更改,读写将失败
- 应用程序决定如何解决重复,例如重试机制,使用新的数据,或者将错误的数据报告给用户
- ES采用的是乐观并发控制
ES的乐观并发控制
ES中的文档是不可变更的,如果更新一个文档,会将旧文档标记删除,同时添加一个全新的文档,并且文档的version
会+1
内部版本控制
使用if_seq_no
+ if_primary_term
做并发控制。
数据准备
写入文档,会返回_seq_no
和primary_term
。
PUT /user/_doc/222
{
"name":"neelon"
}
//响应
{
"_index": "user",
"_type": "_doc",
"_id": "222",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 29,
"_primary_term": 1
}
更新操作
- 根据文档当前的_seq_no和_primary_term去更新文档,更新成功文档的_seq_no和_primary_term会增加;
- 如果此时有别的请求用增加前的_seq_no和_primary_term尝试更新文档则会失败
- 通过这种机制,保证并发写的正确性
也就是说,每次更新前需要通过查询,预先获取到文档的_seq_no和_primary_term的值。
PUT /user/_doc/1?if_seq_no=29&if_primary_term=1
外部版本控制
比如应用的数据存储再MySQL中,ES值是作为一个数据同步用以支持搜索。
使用version + version type控制并发写
- MySQL中的记录也有version字段
- 写入成功,并且文档的_version变成30001
- 如果再次更新,version还是30000,更新失败。带有version更大,则更新成功。