关于Redis分布式锁实现的那些事

了解过Redis的人可能都知道Redis是单线程的,也就是对于同一个Redis实例,同一时刻只会处理一个请求。因为对于分布式架构不同机器共享同一资源,我们可以借助Redis实现分布式锁。

使用Redis的SETNX和GETSET的配合使用则可实现分布式锁。

命令简介

  • setnx(key, value):给指定的key设置value,当且仅当key不存在时,key会被设置,返回了1,否则不做任何动作,返回0
  • getset(key, value):给指定key设置新的value,同时返回旧的value

使用SETNX实现分布式锁:

多个线程执行以下操作:

long currentTime = System.currentTimeMillis();
String lockTime = String.valueOf(currentTime + Lock_Timeout + 1);//锁的有效时间
Long result = jedisClient.setnx(key, lockTime);

如果 result == 1,说明该进程获得锁,setnx 将 key 的值设置为锁的超时时间(当前时间 + 锁的有效时间 + 1)。 
如果 result == 0,说明其他进程已经获得了锁,进程可以在一个循环中不断地尝试 SETNX 操作,以获得锁。

考虑一种情况,如果进程获得锁后,断开了与 Redis 的连接(可能是进程挂掉,或者网络中断),如果没有有效的释放锁的机制,那么其他进程都会处于一直等待的状态,即出现“死锁”。对此,我们可以通过 getset 获取锁的一个最新的被改变的时间,与当前的时间进行比较,进而校验锁的一个最新的状态。

//获取锁
private boolean innerGetLock(String key){
    long currentTime = System.currentTimeMillis();
    String lockTime = String.valueOf(currentTime + Lock_Timeout + 1);//锁的有效时间
    Long result = jedisClient.setnx(key, lockTime);
    if (result == 1) {
        return true;
    } else {
        if (currentTime > Long.valueOf(jedisClient.get(key))){
            //当前时间已超过锁的有效时间,则判断是否有其他进程已获取到锁
            String preLockTimeDuration = jedisClient.getSet(key, lockTime);
            if (currentTime > Long.valueOf(preLockTimeDuration)) {
                return true;
            }
        }
        return false;
    }
}

有很多文章说明了使用SETNX实现分布式锁,不过事实上对于SETNX,Redis是先拿数据,后校验,还是需要分成两步执行,因此并不能保证一致性(CAP定理中的C)。对于该问题的解决,一种方法是在代码中做同步操作,例如synchrinized,另一种,则不适用SETNX,可使用SET(key, value, NX, EX, time) 实现,同样只有当key不存在才进行SET操作,保证了一致性,设置过期时间也可以避免死锁。

猜你喜欢

转载自blog.csdn.net/hsf15768615284/article/details/85010674