
图示的冲突过程,其实就是es的并发冲突问题,会导致数据不准确
当并发操作es的线程越多,或者读取一份数据,供用户查询和操作的时间越长,在这段时间里,如果数据被其他用户修改,那么我们拿到的就是旧数据,基于旧数据去操作,就会导致错误的结果
1、悲观锁与乐观锁两种并发控制方案
悲观锁(Pessimistic Lock),,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁
乐观锁(Optimistic Lock), 每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。
悲观锁的优点是:方便,直接加锁,对应用程序来说,透明,不需要做额外的操作;缺点,并发能力很低,同一时间只能一条线程操作数据
乐观锁的优点是:并发能力很高,不给数据加锁,大量线程并发操作;缺点,麻烦,每次更新的时候,都要先对比版本号,然后可能需要重新加载数据,再次修改,再写;这个过程,可能要重复好几次。
2、内部如何基于_version进行乐观锁并发控制
(1)_version元数据
PUT /test_index/test_type/5
{
"test_field": "test test"
}
{
"_index": "test_index",
"_type": "test_type",
"_id": "5",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"created": true
}
第一次创建一个document的时候,它的_version内部版本号就是1;以后,每次对这个document执行修改或者删除操作,都会对这个_version版本号自动加1;哪怕是删除,也会对这条数据的版本号加1
DELETE /test_index/test_type/5
{
"found": true,
"_index": "test_index",
"_type": "test_type",
"_id": "5",
"_version": 3,
"result": "deleted",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
}
}
在删除一个document之后,可以从一个侧面证明,它不是立即物理删除掉的,因为它的一些版本号等信息还是保留着的。先删除一条document,再重新创建这条document,其实会在delete version基础之上,再把version号加1
(2)图解内部如何基于_version进行乐观锁并发控制
(3)基于external version进行乐观锁并发控制
语法:
?version=1&version_type=external
version_type=external,唯一的区别在于。
_version,只有当你提供的version与es中的_version一模一样的时候,才可以进行修改,只要不一样,就报错
version_type=external,只有当你提供的version比es中的_version大的时候,才能完成修改。
PUT /test_index/test_type/8?version=2&version_type=external
{
"test_field": "test test1"
}
{
"error": {
"root_cause": [
{
"type": "version_conflict_engine_exception",
"reason": "[test_type][8]: version conflict, current version [2] is higher or equal to the one provided [2]",
"index_uuid": "toqtg_FpS-e8bCUkqRr2-Q",
"shard": "1",
"index": "test_index"
}
],
"type": "version_conflict_engine_exception",
"reason": "[test_type][8]: version conflict, current version [2] is higher or equal to the one provided [2]",
"index_uuid": "toqtg_FpS-e8bCUkqRr2-Q",
"shard": "1",
"index": "test_index"
},
"status": 409
}
重新基于最新的版本号发起更新
GET /test_index/test_type/8
{
"_index": "test_index",
"_type": "test_type",
"_id": "8",
"_version": 3,
"found": true,
"_source": {
"test_field": "test test1"
}
}
PUT /test_index/test_type/8?version=3&version_type=external
{
"test_field": "test test1"
}
{
"_index": "test_index",
"_type": "test_type",
"_id": "8",
"_version": 3,
"result": "updated",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 2,
"_primary_term": 1
}