分布式---分布式锁

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分布式锁不会出现数据库唯一索引实现的分布式锁释放失败问题。

posted @ 2019-07-05 16:19  yjxyy  阅读(202)  评论(0编辑  收藏  举报