• ElasticSearch教程——并发问题与锁机制


    ElasticSearch汇总请查看:ElasticSearch教程——汇总篇

    并发冲突
    举个例子,比如在电商的场景下,假设我们有个程序,其工作流程为:

    1.读取商品信息(包含库存,以牙膏为例);

    2.用户下单购买;

    3.更新商品库存(库存减一);

    如果该程序是多线程的,那么总有一个线程是先得到的,假设我们牙膏库存一开始有100件,此时线程A先得到线程将牙膏的库存设置为99件,然后线程B再将牙膏设置为99件,这个时候就已经错了。

    上面所述问题就是ES中的并发冲突问题,会导致数据不准确。

     并发解决方案
    在ES中如何解决这类并发冲突问题?

    ——通过_version版本号的方式进行乐观锁并发控制

    在es内部第次一创建document的时候,它的_version默认会是1,之后进行的删除和修改的操作_version都会增加1。可以看到删除一个document之后,再进行同一个id的document添加操作,版本号是加1而不是初始化为1,从而可以说明document并不是正真地被物理删除,它的一些版本号信息一样会存在,而是会在某个时刻一起被清除。

    在es后台,有很多类似于replica同步的请求,这些请求都是异步多线程的,对于多个修改请求是乱序的,因此会使用_version乐观锁来控制这种并发的请求处理。当后续的修改请求先到达,对应修改成功之后_version会加1,然后检测到之前的修改到达会直接丢弃掉该请求;而当后续的修改请求按照正常顺序到达则会正常修改然后_version在前一次修改后的基础上加1(此时_version可能就是3,会基于之前修改后的状态)。

    es提供了一个外部版本号的乐观控制方案来替代内部的_version。例如:

    ?version=1&version_type=external

    和内在的_version的区别在于。对于内在_version=1,只有在后续请求满足?_version=1的时候才能够更新成功;对于外部_version=1,只有在后续请求满足?_version>1才能够修改成功,即必须大于对应的版本才可以进行修改。

    replica同步图示如下图:

     说明
    乐观锁和悲观锁都是指对待并发控制的两种思想,共享锁(S锁,也叫读锁)、排他锁(X锁,又称写锁)、行锁、表锁、全局锁、文档锁等是具体的锁的实现,且都属于悲观锁,乐观锁没有锁。

     

    乐观锁
    乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。

    在ES中乐观锁主要通过版本号等数据来进行判定该数据是否有被修改过,如果发现版本version与自己不相同,那就说明数据是已经被修改过的,那么它会重新去es中读取最新的数据版本,然后再进行数据上的操作。

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

    PUT /test_index/test_type/7
    {
      "test_field": "test test"
    }

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

    GET test_index/test_type/7
     
    返回:
    {
      "_index": "test_index",
      "_type": "test_type",
      "_id": "7",
      "_version": 1,
      "found": true,
      "_source": {
        "test_field": "test test"
      }
    }

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

    同时带上数据的版本号,确保说,es中的数据的版本号,跟客户端中的数据的版本号是相同的,才能修改

    PUT /test_index/test_type/7?version=1 
    {
      "test_field": "test client 1"
    }
     
    返回:
    {
      "_index": "test_index",
      "_type": "test_type",
      "_id": "7",
      "_version": 2,
      "result": "updated",
      "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
      },
      "created": false
    }

    (4)另外一个客户端,尝试基于version=1的数据去进行修改,同样带上version版本号,进行乐观锁的并发控制

    PUT /test_index/test_type/7?version=1 
    {
      "test_field": "test client 2"
    }
     
    返回:
    {
      "error": {
        "root_cause": [
          {
            "type": "version_conflict_engine_exception",
            "reason": "[test_type][7]: version conflict, current version [2] is different than the one provided [1]",
            "index_uuid": "6m0G7yx7R1KECWWGnfH1sw",
            "shard": "3",
            "index": "test_index"
          }
        ],
        "type": "version_conflict_engine_exception",
        "reason": "[test_type][7]: version conflict, current version [2] is different than the one provided [1]",
        "index_uuid": "6m0G7yx7R1KECWWGnfH1sw",
        "shard": "3",
        "index": "test_index"
      },
      "status": 409
    }

    (5)在乐观锁成功阻止并发问题之后,尝试正确的完成更新

    GET /test_index/test_type/7
     
    返回:
    {
      "_index": "test_index",
      "_type": "test_type",
      "_id": "7",
      "_version": 2,
      "found": true,
      "_source": {
        "test_field": "test client 1"
      }
    }

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

    PUT /test_index/test_type/7?version=2 
    {
      "test_field": "test client 2"
    }
     
    返回:
    {
      "_index": "test_index",
      "_type": "test_type",
      "_id": "7",
      "_version": 3,
      "result": "updated",
      "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
      },
      "created": false
    }

    悲观锁
    悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,上锁之后就只有一个线程可以操作这条数据了,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

     

    乐观锁和悲观锁对比
    悲观锁
    优点:方便,直接加锁,对应用程序来说比较透明,不需要额外的操作;

    缺点:并发能力低,同一时间只能有一条线程操作数据;

    乐观锁
    优点:并发能力高,不给数据加锁,可大量线程并发操作;

    缺点:麻烦,每次更新的时候,都要先比对版本号,然后可能需要重新加载数据,再次修改,再次更改……这个过程可能需要重复多次



    ————————————————
    版权声明:本文为CSDN博主「东天里的冬天」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/gwd1154978352/article/details/82852801

  • 相关阅读:
    互评
    201671010438 王奕晗英文文本词频统计
    201671010438王奕晗 实验二词频统计
    201671010438王奕晗 实验三 作业互评与改进
    通读《构建之法》所提出的问题
    个人学习总结博客(201671010440 王雨竹)
    互评
    201671010440王雨竹+《英文文本统计分析》
    201671010440 王雨竹 词频统计软件项目报告
    201671010440 王雨竹 实验三 作业互评与改进
  • 原文地址:https://www.cnblogs.com/it-deepinmind/p/14527913.html
Copyright © 2020-2023  润新知