zoukankan      html  css  js  c++  java
  • 分布式---分布式锁

    1.分布式锁

      在单机环境下,可以使用语言内置锁来实现线程之间的同步。但是在分布式场景下,需要同步的进程可能位于不同的节点上,那么就需要使用分布式锁

    分布式锁需要满足的要求:

      排他性:在同一时间只有一个客户端能获取到锁,其他客户端无法同时获取

      避免死锁:这把锁在一段有限时间之后,一定会被释放(正常释放,或者异常释放)

      高可用:获取或释放锁的机制必须高可用且性能佳

      目前主流的分布式锁实现方式有三种:基于数据库实现基于Redis实现基于ZooKeeper实现,无论哪种方式都不完美,要根据业务的实际场景来选择。

    基于数据库实现

      基于数据库来做分布式锁的话,通常有两种做法:基于数据库的乐观锁,基于数据库的悲观锁

    乐观锁实现

      乐观锁机制其实就是在数据库表中引入一个版本号(version)字段来实现的。

      当我们从数据库中读取数据的时候,同时将这个版本号字段也读取出来,如果要对读出来的数据进行更新后写回数据库,则需要将版本号(version)加1,同时将新的数据与新的version更新到数据表中,且必须在更新的时候,查看版本号是不是之前的版本号,如果是,则正常更新,如果不是,则更新失败,说明在这个过程中有其他的进程去更新过数据了。

    如下图例子所示:


    )

    通过上面这个例子可以看出,使用乐观锁机制,必须得满足:

      (1)锁服务要有递增的版本号(version)

      (2)再次更新数据的时候都必须先判断版本号对不对,然后再写入新的版本号

    悲观锁也叫排他锁,在MySQL中是基于for update来实现加锁的

    //锁定的方法-伪代码
    public boolean lock(){
        connection.setAutoCommit(false);
        for(){
            result=
                select * from user where
                id=100 for update;
            if (result){
                //结果不为空,说明获取到了锁
                return true;
            }
            //没有获取到锁
            sleep(1000);
        }
        return false;
    }
    //释放锁-伪代码
    connection.commit();
    

      上面的示例中,user表中,id是主键,通过for update操作,数据库在查询的时候就会给这条记录加上排他锁。(需要注意的是,在InnoDB中只有字段加了索引的,才会是行级锁,否则是表级锁,所以这个id字段要加索引)

      当这条记录加上排他锁后,其他线程是无法操作这条记录的。

      那么,这样的话,我们就认为获得了排它锁的这个进程是拥有了分布式锁,然后就可以执行我们想要做的业务逻辑,当逻辑完成后,再调用上述释放锁的语句即可。

    存在以下几个问题:

    • 没有失效时间,解锁失败的话其他进程就无法再获得该锁。
    • 只能是非阻塞锁,插入失败就直接报错了,无法重试。
    • 不可重入,已经获得锁的进程也必须重新获取锁。

    基于Redis实现

    Redis的SETNX指令

      使用SETNX(set if not exist)指令插入一个键值对,如果key已经存在,那么返回false,否则插入成功并返回True。

      SETNX指令和数据库的唯一索引类似,保证了只存在一个Key键值对,那么可以用一个key的键值对是否存在来判断是否处于锁定状态

      EXPIRE指令可以为一个键值对设置一个过期时间,从而避免了数据库唯一索引实现方式中释放锁失败的问题。

    Redis的RedLock算法

      使用多个Redis实例来实现分布式锁,这是为了保证在单节点故障时仍然可以使用

    • 尝试从N个互相独立Redis实例获取锁;
    • 计算获取锁消耗的时间,只有当这个时间小于锁的过期时间,并且从大多数(N/2+1)实例上获取了锁,那么就认为获取锁成功了
    • 如果获取锁失败,就到每个实例上释放锁

    基于Zookeeper有序节点实现

    1.Zookeeper抽象模型

      Zookeeper提供了一种树形结构的命名空间,/app1/p_1节点的父节点为/app1。

    2.节点类型

    • 永久节点:不会因为会话结束或者超时而消失;
    • 临时节点:如果会话结束或者超时就会结束;
    • 有序节点:会在节点名的后面加一个数字后缀,并且是有序的,例如生成的有序节点为/lock/node-0000000,它的下一个有序节点则为/lock/node-0000001,以此类推。

    3.监听器

      为一个节点注册监听器,在节点状态发生改变的时候,会给客户端发送消息

    4.分布式锁的实现

    • 创建一个锁目录/lock;
    • 当一个客户端需要获取时,在/lock下创建临时的且有序的子节点;
    • 客户端获取/lcok下的子节点列表。判断自己创建的子节点是否为当前子节点列表中序号最小的子节点,如果是则认为获得锁;否则监听自己的前一个子节点,获得子节点的变更通知后重复此步骤直至获得锁。
    • 执行业务代码,完成后,删除对应的子节点。

    5.会话超时

      如果一个已经获得锁的会话超时了,因为创建的是临时节点,所以该会话对应的临时节点会被删除,其他会话就可以获得锁了。可以看到Zookeeper分布式锁不会出现数据库唯一索引实现的分布式锁释放失败问题。

  • 相关阅读:
    读入输出优化模板
    HDU-2647 Reward(拓扑排序)
    HDU-2647 Reward(拓扑排序)
    HDU-2647 Reward(拓扑排序)
    HDU-2647 Reward(拓扑排序)
    Using KafkaBolt to write to a kafka topic
    Using KafkaBolt to write to a kafka topic
    Using KafkaBolt to write to a kafka topic
    Using KafkaBolt to write to a kafka topic
    getElementById() 获取指定ID的第一个元素
  • 原文地址:https://www.cnblogs.com/yjxyy/p/11139036.html
Copyright © 2011-2022 走看看