Redis实现分布式锁

109 阅读3分钟

为什么需要分布式锁?

系统是单机版的情况下,是在同一个JVM虚拟机内,使用synchronized或者Lock接口,就可以保证高并发下同一时间只有一个客户端可以对共享资源进行操作。

在分布式系统中,高并发下有多个客户端需要获取锁时,此时不是在同一个JVM虚拟机内,我们就需要分布式锁。这个分布式锁是保存在一个共享存储系统中,可以被多个客户端共享访问和获取。

我们使用redis作为这个共享存储系统,redis属性分布式系统,这时候需要保证:

  • 这些锁操作的原子性
  • redis作为共享存储系统保存了锁变量,考虑这个共享存储系统的可靠性

基于单个redis节点实现分布式锁

加锁

SET lock_key unique_value NX PX 10000

其中,unique_value是客户端的唯⼀标识,可以⽤⼀个随机⽣成的字符串来表⽰,PX 10000则表⽰lock_key会在10s后过期,以免客户端在这期间发⽣异常⽽⽆法释放锁。

释放锁
//释放锁 ⽐较unique_value是否相等,避免误释放
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
总结
  • 以上就是使⽤SET命令和Lua脚本在Redis单节点上实现分布式锁。
  • 有了唯一标识可以保证锁不会被别人释放
  • 有了过期时间就不会发生客户端异常无法主动释放,导致死锁
  • 使用lua脚本 和 set命令+nx px 保证了锁释放操作的原⼦性

问题

  • 锁过期时间不好评估怎么办?可能自己业务执行时间超过了过期时间给提前释放了,有没有一种自动续费的机制?
  • redis实例故障而导致锁无法工作的问题

基于多个Redis节点实现高可靠的分布式锁

Redisson是一个Java语言实现的Redis SDK客户端,在使用分布式锁时,它就采用了「自动续期」的方案来避免锁过期,这个守护线程我们一般也把它叫做「看门狗」线程。

所以,Redisson 提供了 watch dog 自动延时机制,提供了一个监控锁的看门狗,它的作用是在 Redisson 实例被关闭前,不断的延长锁的有效期。如果一个拿到锁的线程一直没有完成逻辑,那么看门狗会帮助线程不断的延长锁超时时间,锁不会因为超时而被释放。

为了避免Redis实例故障而导致的锁无法工作的问题Redis的,开发者Antirez提出了分布式锁算法Redlock。

Redlock算法的基本思路:

  1. 客户端在多个 Redis 实例上申请加锁

  2. 客户端从超过半数(⼤于等于 N/2+1)的Redis实例上成功则加锁成功

  3. 大多数节点加锁的总耗时,要小于锁设置的过期时间

  4. 释放锁,要向全部节点发起释放锁请求

    @Slf4j @Service @ConditionalOnClass(RedissonClient.class) public class RedLock implements Lock {

    private final String RED_LOCK_KEY_PREFIX = "redLock_";
    
    @Autowired
    private RedissonClient client;
    
    @Override
    public boolean tryLock(String key) {
        return tryLock(key, 3);
    }
    
    @Override
    public boolean tryLock(String key, long expireTime) {
        RLock lock = client.getLock(key);
        EhcContext.getContext().put(RED_LOCK_KEY_PREFIX + key, lock);
        try {
            return lock.tryLock(expireTime, TimeUnit.SECONDS);
        } catch (Exception e) {
            log.error("获取锁[{}]失败", RED_LOCK_KEY_PREFIX + key, e);
            return false;
        }
    }
    
    @Override
    public boolean unlock(String key) {
        try {
            RLock lock = EhcContext.getContext().get(RED_LOCK_KEY_PREFIX + key);
            lock.unlock();
            return true;
        } catch (Exception e) {
            log.error("释放锁[{}]失败", RED_LOCK_KEY_PREFIX + key, e);
            return false;
        }
    }
    

总结

因为我们项目是分布式系统,以及使用了redis-cluster集群部署,所以我们使用了RedLock来实现锁操作。

相关文章可参考: