Elasticsearch-10-并发控制方案

并发场景

假设现在是矮子电商场景下,用户需要去购买商品,程序的工作流程是:

  1. 读取商品信息,比如商品名称价格等.
  2. 用户下单去购买商品
  3. 更新商品库存
    es的工作流程:
  4. 先查询document数据,商品信息等,显示到页面,同时在内存中缓存该document的数据
  5. 当网页发生购买以后,直接基于内存中的数据进行计算和操作
  6. 将计算后的结果返回到es中

现在有两个线程同时去执行这个流程,比如购买一台电脑库存是100,线程A 线程B并发执行,两个线程查询到的库存都是100,这个时候用户去下单购买,线程A 将商品库存减一是99件,线程B也将库存减一也是99件,然后A先更新es的中的商品库存,更新为99,此时线程B也去更新商品库存为99,这个时候一共是发生了两次购买操作,正常结果是库存应该减少两件,但其实只减少了一件,这就导致了并发冲突,使得数据结果不正确.

有些场景下,在不管数据正不正确的情况下这样操作是无所谓,比如我们只管将数据写入es,不管数据是什么样的,或者是说算错了也没关系的这些情况,但是一般情况下我们是需要做并发控制的防止数据错乱

并发控制方案

悲观锁并发控制方案

还是上面的这个场景,使用悲观锁控制并发,就是线程A去读取商品信息,读取的时候给这个商品信息加锁,然后进行一系列的操作,比如减少库存等,将库存减一后,再更新到商品信息中去,然后释放锁,在这个过程中,线程B如果去请求这个商品数据是请求不到的,在线程A释放锁之前,B是获取不到的,当A释放锁之后,这个时候商品库存已经变化了,B拿到的数据就是A已经操作完成后的数据,同时线程B也会对数据加锁,操作完成之后再释放.

悲观锁的并发控制方案,就是在各种情况下都上锁,上锁之后,就只有一个线程可以操作这条数据了,当然,不同场景下上的锁也不同,比如行级锁 表级锁 读锁 写锁等.

乐观锁并发控制方案

es就是使用的乐观锁,使用乐观锁控制并发的时候,并不会对数据进行加锁,而是通过版本号控制(version),这点和zookeeper相似,还是上面的场景,线程A B 同时去请求商品信息,然后拿到的都是一样的,比如库存是100, 这时候比如数据的version=1, 假设线程A先把库存-1然后更新到es中,这时候es会拿版本号和线程A中的数据的版本号进行对比,这时候version都是1写入成功,库存更新为99 es中数据的版本号变更为2,这时候线程B也去进行更新,发现es中数据的版本号是2,但是线程B的版本号还是1,版本号不同的情况下,线程B重新请求es的数据 然后再将库存-1,版本号也变为2,然后再去更新es中的数据,这个时候库存变为98 版本号变为3.

es内部基于乐观锁控制并发的原理

假设现在有两个节点一个shard一个replica,里面有一条document 数据是test1 version也是1
集群

现在有两个请求同时去修改document数据,我们期待的结果是1先修改为test2,然后2再修改为test3
修改document

但是这个两个请求时并发的所以第2个请求可能先到达es先进性修改, 如果没有乐观锁并发控制的话,第2个请求先到达将内容修改为test3,第1个请求后到,再去修改为test2,这个时候数据就变成了test,跟我们预期的结果就不一样了,因为按照顺序来说是先修改为test2 再修改为test3

同理shard去将数据同步到replica的时候也会多线程异步执行的,如果没有乐观锁的控制,数据也可能发生错误
但是在有乐观锁的情况下,基于version去控制,如果说第二个请求先到先修改了数据 version已经加1了比如变为2,replica先同步了第二个请求的数据,第一个请求再去同步的时候去比对一下版本号,version还是1,那么es就会直接把这条数据扔掉,这个时候数据就会保持一个正确的状态

基于external version进行乐观锁并发控制

es提供了一个feature,就是说你可以不用他提供的内部版本号来进行并发控制,可以基于你自己维护的一个版本号来进行并发控制.
举个例子,假如你的数据在mysql里面也有一份,然后你的应用本身就维护了一个版本号,无论是自己生成的或者是程序控制的.这个时候进行乐观锁并发控制的时候可能并不是想要用内部的_version来进行控制,而是用你自己维护的那个版本号来控制.

基于内部的_version进行控制时,只需要在请求后面加 _version=版本号即可,基于自己的version控制的话需要在请求后面加?_version=版本号&version_type=external
这两种版本控制的唯一区别就在于 基于内部_version控制时,只有当你提供的version和es中的version一毛一样的时候才能修改,只要不一样就报错,而基于自己的版本号控制时,只有当你提供的version比es中的_version大的时候才能完成修改

两种锁的优缺点
  1. 悲观锁
  • 优点:方便,直接加锁,对应用程序来说是透明的,不需要做额外的操作
  • 缺点:并发能力很低,同一时间只能有一个线程访问操作数据
  1. 乐观锁
  • 优点:并发能力高,不给数据加锁,可以有大量线程并发操作
  • 缺点:麻烦,每次更新的时候都要先去比对版本号,然后版本号不一致时,可能需要重新加载数据,再次修改然后再写,而且这个过程可能要重复好几次