本节的主要内容:ES的乐观锁并发控制原理以及模拟过程

1、ES的乐观锁并发控制

1.1、悲观锁与乐观锁

悲观锁的优点是:方便,直接加锁,对应用程序来说透明,不需要做额外的操作;缺点,并发能力很低,同一时间只能一条线程操作数据

乐观锁的优点是:并发能力很高,不给数据加锁,大量线程并发操作;缺点,麻烦,每次更新的时候,都要先对比版本号,然后可能需要重新加载数据,再次修改,再写;这个过程,可能要重复好几次。

1.2、Elasticsearch内部如何基于_version进行乐观锁并发控制

es内部的多线程异步并发修改时,是基于自己的version版本号进行乐观锁并发控制的

第一次创建一个document的时候,它的version内部版本号就是1;以后,每次对这个document执行修改或者删除操作,都会对这个version版本号自动加1;哪怕是删除,也会对这条数据的版本号加1

举例说明:

某document的初始值 version=1,field=test1,模拟两个线程同时进行修改
先修改的field=test2 ,此时获取的version=1
后修改的field=test3,此时获取的version= 1,后修改先到,更新field=test3,此时version= 2
先修改的后到时,此时会比较一下version号,是否相等,如果不相等的话,那么就直接将field=test2这条数据给扔掉,这样的话就不会让旧的数据,覆盖掉新的数据

2、ES内部并发控制

所有的REST命令都可以在 Kibana 7.6.1上正常运行

(1)先构造一条数据出来

POST /test_index/_create/7
{
  "test_field": "test test"
}

(2)模拟两个客户端,都获取到了同一条数据

GET test_index/_doc/7

返回的结果

{
  "_index" : "test_index",
  "_type" : "_doc",
  "_id" : "7",
  "_version" : 3,
  "_seq_no" : 2,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "test_field" : "test test"
  }
}

(3)其中一个客户端,先更新了一下这个数据

注意

一些老的版本es使用version,但是新版本不支持了,会提示我们用if_seq_noif_primary_term

先查操作之前当前数据的seq_no,primary_term值比如各自是2和1,在更新的时候url带上if_seq_no=2&if_primary_term=1,指明只有在这个值的情况下才进行更新,否则返回错误

PUT test_index/_doc/7?if_seq_no=2&if_primary_term=1
{
  "test_field": "test client 1"
}

如果重复执行就会报 version_conflict_engine_exception异常

此时就需要基于最新的数据和版本号去进行修改,修改后,带上最新的版本号,可能这个步骤会需要反复执行好几次,才能成功,特别是在多线程并发更新同一条数据很频繁的情况下

//使用最新的_seq_no和_primary_term就可以正常更新了
PUT test_index/_doc/7?if_seq_no=3&if_primary_term=1
{
  "test_field": "test client 1"
}

3、ES基于 external version 进行并发控制

使用外部版本(使用其他数据库作为主要数据存储)

当version_type=external时,只有当你提供的version比es中的_version大的时候,才能完成修改

当document 的_version=1,只有当version>1&version_type=external,才能成功,

比如说使用version=7&version_type=external更新document成功后,该document的_version会设置为7,后续的更新需要基于最新的版本号发起更新

PUT test_index/_doc/7?version=7&version_type=external
{
  "title":"iphone",
  "count":130
}