关于分布式锁的整理

Posted 捋捋

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于分布式锁的整理相关的知识,希望对你有一定的参考价值。

分布式锁

分布式锁一般有三种
1.数据库的乐观锁
2.基于redis的分布式锁
3.基于zookeeper的分布式锁

一.基于redis的分布式锁实现:

可靠性
1.互斥性。任意时刻,只有一个系统持有锁
2.不会发生死锁。即使某个客户端持有锁的期间崩溃没有主动解锁,也能保证其他客户端后续可以加锁
3.具有容错性。只要大部分redis的节点正常运行,客户端就可以正常加锁
4.解铃还须系铃人。加锁和解锁必须是同一个客户端

1.代码实现:

1.1组件依赖

首先我们要通过Maven引入Jedis开源组件,在pom.xml文件加入下面的代码:

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>

1.2加锁代码

 private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";

    /**
     * 尝试获取分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @param expireTime 超期时间
     * @return 是否获取成功
     */
    public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) 

        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);

        if (LOCK_SUCCESS.equals(result)) 
            return true;
        
        return false;

    

可以看到,我们加锁就一行代码,这个set()方法一共有五个形参:
第一个为key,我们使用key来当锁,因为key是唯一的。
第二个为value,我们传的是requestId,很多童鞋可能不明白,有key作为锁不就够了吗,为什么还要用到value?原因就是我们在上面讲到可靠性时,分布式锁要满足第四个条件解铃还须系铃人,通过给value赋值为requestId,我们就知道这把锁是哪个请求加的了,在解锁的时候就可以有依据。requestId可以使用UUID.randomUUID().toString()方法生成。
第三个为nxxx,这个参数我们填的是NX,意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作;
第四个为expx,这个参数我们传的是PX,意思是我们要给这个key加一个过期的设置,具体时间由第五个参数决定。
第五个为time,与第四个参数相呼应,代表key的过期时间。

2.3解决死锁问题

为了解决这个死锁的问题需要给 Key 设置有效期了。设置的方式有 2 种:

第一种就是在 Set 完 Key 之后,直接设置 Key 的有效期 “expire key timeout” ,为 Key 设置一个超时时间,单位为 Second,超过这个时间锁会自动释放,避免死锁。

这种方式相当于,把锁持有的有效期,交给了 Redis 去控制。如果时间到了,你还没有给我删除 Key,那 Redis 就直接给你删了,其他服务器就可以继续去 Setnx 获取锁。

第二种方式,就是把删除 Key 权利交给其他的服务器,那这个时候就需要用到 Value 值了,比如服务器 1,设置了 Value 也就是 Timeout 为当前时间 +1 秒 。
这个时候服务器 2 通过 Get 发现时间已经超过系统当前时间了,那就说明服务器 1 没有释放锁,服务器 1 可能出问题了,服务器 2 就开始执行删除 Key 操作,并且继续执行 Setnx 操作。

但是这块有一个问题,也就是不光你服务器 2 可能会发现服务器 1 超时了,服务器 3 也可能会发现,如果刚好服务器 2 Setnx 操作完成,服务器 3 就接着删除,是不是服务器 3 也可以 Setnx 成功了?那就等于是服务器 2 和服务器 3 都拿到锁了,那就问题大了。

这个时候需要用到“GETSET key value”命令了。这个命令的意思就是获取当前 Key 的值,并且设置新的值。

假设服务器 2 发现 Key 过期了,开始调用 getset 命令,然后用获取的时间判断是否过期,如果获取的时间仍然是过期的,那就说明拿到锁了。

如果没有,则说明在服务 2 执行 getset 之前,服务器 3 可能也发现锁过期了,并且在服务器 2 之前执行了 getset 操作,重新设置了过期时间。

那么服务器 2 就需要放弃后续的操作,继续等待服务器 3 释放锁或者去监测 Key 的有效期是否过期。

这块其实有一个小问题是,服务器 3 已经修改了有效期,拿到锁之后,服务器 2 也修改了有效期,但是没能拿到锁。

但是这个有效期的时间已经被在服务器 3 的基础上又增加一些,但是这种影响其实还是很小的,几乎可以忽略不计。

二.基于zookeeper的分布式锁实现:

就是同一个目录下文件名称不能重复,同样 ZooKeeper 也是这样的。

在 ZooKeeper 所有的节点,也就是文件夹称作 Znode,而且这个 Znode 节点是可以存储数据的。

我们可以通过“ create /zkjjj nice”来创建一个节点,这个命令就表示,在根目录下创建一个 zkjjj 的节点,值是 nice。

同样这里的值,和我在前面说的 Redis 中的一样,没什么意义,你随便给。

另外 ZooKeeper 可以创建 4 种类型的节点,分别是:
持久性节点
持久性顺序节点
临时性节点
临时性顺序节点

首先说下持久性节点和临时性节点的区别:

持久性节点表示只要你创建了这个节点,那不管你 ZooKeeper 的客户端是否断开连接,ZooKeeper 的服务端都会记录这个节点。临时性节点刚好相反,一旦你 ZooKeeper 客户端断开了连接,那 ZooKeeper 服务端就不再保存这个节点。顺便也说下顺序性节点,顺序性节点是指,在创建节点的时候,ZooKeeper 会自动给节点编号比如 0000001,0000002 这种的。
Zookeeper 有一个监听机制,客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变、被删除、子目录节点增加删除)等,Zookeeper 会通知客户端。

在 Zookeeper 中如何加锁?

下面我们继续结合我们上面的分红包场景,描述下在 Zookeeper 中如何加锁。

假设服务器 1,创建了一个节点 /zkjjj,成功了,那服务器 1 就获取了锁,服务器 2 再去创建相同的锁,就会失败,这个时候就只能监听这个节点的变化。

等到服务器 1 处理完业务,删除了节点后,他就会得到通知,然后去创建同样的节点,获取锁处理业务,再删除节点,后续的 100 台服务器与之类似。

注意这里的 100 台服务器并不是挨个去执行上面的创建节点的操作,而是并发的,当服务器 1 创建成功,那么剩下的 99 个就都会注册监听这个节点,等通知,以此类推。

但是大家有没有注意到,这里还是有问题的,还是会有死锁的情况存在,对不对?

当服务器 1 创建了节点后挂了,没能删除,那其他 99 台服务器就会一直等通知,那就完蛋了。

这个时候就需要用到临时性节点了,我们前面说过了,临时性节点的特点是客户端一旦断开,就会丢失。

也就是当服务器 1 创建了节点后,如果挂了,那这个节点会自动被删除,这样后续的其他服务器,就可以继续去创建节点,获取锁了。

但是我们可能还需要注意到一点,就是惊群效应:举一个很简单的例子,当你往一群鸽子中间扔一块食物,虽然最终只有一个鸽子抢到食物,但所有鸽子都会被惊动来争夺,没有抢到…

就是当服务器 1 节点有变化,会通知其余的 99 个服务器,但是最终只有 1 个服务器会创建成功,这样 98 还是需要等待监听,那么为了处理这种情况,就需要用到临时顺序性节点。

大致意思就是,之前是所有 99 个服务器都监听一个节点,现在就是每一个服务器监听自己前面的一个节点。

假设 100 个服务器同时发来请求,这个时候会在 /zkjjj 节点下创建 100 个临时顺序性节点 /zkjjj/000000001,/zkjjj/000000002,一直到 /zkjjj/000000100,这个编号就等于是已经给他们设置了获取锁的先后顺序了。

当 001 节点处理完毕,删除节点后,002 收到通知,去获取锁,开始执行,执行完毕,删除节点,通知 003~以此类推。

以上是关于关于分布式锁的整理的主要内容,如果未能解决你的问题,请参考以下文章

Redis分布式锁的实现原理

Redis分布式锁的实现原理

分布式套路之万字详解Redis/Redission分布式锁原理

关于数据库事务隔离级别锁的理解与整理(转)

zookeeper实现分布式锁

zookeeper实现分布式锁