ElasticSearch的并发控制问题
1.存储系统并发控制的基本解决思路
-
采用悲观锁
这种方式,是说我确定一定会有较大或者非常大的可能产生并发,那么我就对数据资源加锁,防止并发操作时产生的数据冲突,从而对一类数据进行顺序执行。
-
采用乐观锁
这是方式,是我假定并发不会发生或者很少发生,不会阻塞正在尝试的操作,如果数据在读写过程中被修改,更新就会失败,然后由应用程序来处理决定是否要进行冲突的解决以及解决的方式,例如重试、或者就默认更是失败。
2.ElasticSearch采用的并发控制
-
ElasticSearch采用的是第二种,乐观锁并发控制
-
ElasticSearch的乐观锁并发控制
- ElasticSearch的文档更新方式:我们知道,ElasticSearch的每个文档都是不可变的,如果要更新文档,其实是把源文档删除,同时增加一个全新的文档,并且把文档的version字段+1;
- 内部版本控制:采用_seq_no+_primary_term的方式进行并发控制;
- 使用外部版本控制:采用version+external的方式进行并发控制,注意这里是说不用es自己维护version,而是由外部程序来自己维护verison(这个version只能变大,不能变小哦);
-
正常的数据更新过程:应该是先查询出文档,然后再执行更新操作;
-
ElasticSearch乐观锁并发控制的实例
- 先创建一个文档
PUT accout/_doc/1 { "user_id": 2, "account_amout": 30 }
结果:
{ "_index" : "accout", "_type" : "_doc", "_id" : "1", "_version" : 1, "result" : "created", "_shards" : { "total" : 2, "successful" : 2, "failed" : 0 }, "_seq_no" : 1, "_primary_term" : 1 }
我们看到它返回了_seq_no和_primary_term。
我们查询这个文档:
GET accout/_doc/1
返回结果:
{ "_index" : "accout", "_type" : "_doc", "_id" : "1", "_version" : 1, "_seq_no" : 1, "_primary_term" : 1, "found" : true, "_source" : { "user_id" : 2, "account_amout" : 30 } }
我们看到,同样也返回了_seq_no和_primary_term。
2.内部版本控制的更新方式:
PUT accout/_doc/1?if_primary_term=1&if_seq_no=1 { "user_id":"2", "account_amout":31 }
返回:
{ "_index" : "accout", "_type" : "_doc", "_id" : "1", "_version" : 2, "result" : "updated", "_shards" : { "total" : 2, "successful" : 2, "failed" : 0 }, "_seq_no" : 2, "_primary_term" : 1 }
查询结果:
GET accout/_doc/1
{ "_index" : "accout", "_type" : "_doc", "_id" : "1", "_version" : 2, "_seq_no" : 2, "_primary_term" : 1, "found" : true, "_source" : { "user_id" : "2", "account_amout" : 31 } }
如果此时有另外的客户端也做更新,也拿到了 “_seq_no” : 2,"_primary_term" : 1,此时去更新:
PUT accout/_doc/1?if_primary_term=1&if_seq_no=1 { "user_id":"2", "account_amout":34 }
执行会报错:
{ "error" : { "root_cause" : [ { "type" : "version_conflict_engine_exception", "reason" : "[1]: version conflict, required seqNo [1], primary term [1]. current document has seqNo [2] and primary term [1]", "index_uuid" : "CkgluTniSO2lUONksRFJEA", "shard" : "0", "index" : "accout" } ], "type" : "version_conflict_engine_exception", "reason" : "[1]: version conflict, required seqNo [1], primary term [1]. current document has seqNo [2] and primary term [1]", "index_uuid" : "CkgluTniSO2lUONksRFJEA", "shard" : "0", "index" : "accout" }, "status" : 409 }
3.使用外部版本控制案例
我们先查询一下文档:
GET accout/_doc/1
结果:
{ "_index" : "accout", "_type" : "_doc", "_id" : "1", "_version" : 2, "_seq_no" : 1, "_primary_term" : 1, "found" : true, "_source" : { "user_id" : "2", "account_amout" : 34 } }
我们看到version=2,
然后执行更新操作,将余额改为36,
PUT accout/_doc/1?version=3&version_type=external { "user_id":"2", "account_amout":36 }
结果:
{ "_index" : "accout", "_type" : "_doc", "_id" : "1", "_version" : 3, "result" : "updated", "_shards" : { "total" : 2, "successful" : 2, "failed" : 0 }, "_seq_no" : 2, "_primary_term" : 1 }
如果此时有另外的客户端也做更新,也拿到了 _version:2,此时用version:3去更新:
PUT accout/_doc/1?version=3&version_type=external { "user_id":"2", "account_amout":38 }
结果:
{
"error" : {
"root_cause" : [
{
"type" : "version_conflict_engine_exception",
"reason" : "[1]: version conflict, current version [3] is higher or equal to the one provided [3]",
"index_uuid" : "Y6IWjMJwQsmqi91VWzwaxA",
"shard" : "0",
"index" : "accout"
}
],
"type" : "version_conflict_engine_exception",
"reason" : "[1]: version conflict, current version [3] is higher or equal to the one provided [3]",
"index_uuid" : "Y6IWjMJwQsmqi91VWzwaxA",
"shard" : "0",
"index" : "accout"
},
"status" : 409
}
出错 了。