Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 等。

redis分布式锁三板斧,获取锁、删除锁、锁超时

redis分布式的常规实现

Redis是最常见的实现分布式锁的方法之一,而很多人都了解使用了redis分布式锁使用redis的

SET key value [EX seconds] [PX milliseconds] [NX|XX]

指令。

对于删除操作,由于先判断key是否存在,然后执行del操作,为避免删除其他线程生成的锁,因此需要执行使用lua脚本执行,保证原子性,脚本如下

-- lua删除锁:-- KEYS和ARGV分别是以集合方式传入的参数 key为锁名称,argv为每个锁的唯一表示if redis.call('get', KEYS[1]) == ARGV[1]     then       -- 执行删除操作        return redis.call('del', KEYS[1])     else       -- 不成功,返回0        return 0 end

超时时间怎么设置

上面实现的过程中,我们对于设置key的超时时间的设置怎么处理?一旦我们设置太短,业务代码耗时过长,则会被超时释放;如果设置的太长,万一线程获得锁后线程异常或死亡,未能正常释放锁,容易导致业务积压。

有人说我们可以预先计算业务耗时,设置一个最长的,其实这个看似很合理,但实际上,作为一款分布式锁的基础服务,本应与业务脱离,需要保证未来业务场景,同时简化使用方式。

其实,redisson就针对这样的问题提供了解决方案,那就是watch dog(看门狗),下面我们看下redisson的源码。

watch dog即通过开启一个线程进行key的续期操作。

先看redisson的demo使用,如下

// 初始化Config config = new Config();// 由于本地使用单机模式,其他模式也config.useSingleServer().setAddress("127.0.0.1:6379");RedissonClient redisson = Redisson.create(config);// 获取锁RLock lock = redisson.getLock("lockkey");lock.lock(30, TimeUnit.SECONDS);

那么我们直接从redisson.getLock去往下跟踪代码,为了简化,我们直接看到最后的watch dog的代码(一步一步跟源码,肯定会看到这段代码)

private  RFuture tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) {        if (leaseTime != -1L) {            // 获取锁            return this.tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);        } else {            // watchdog            RFuture ttlRemainingFuture = this.tryLockInnerAsync(this.commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);            ttlRemainingFuture.onComplete((ttlRemaining, e) -> {                if (e == null) {                    if (ttlRemaining == null) {                        this.scheduleExpirationRenewal(threadId);                    }                }            });            return ttlRemainingFuture;        }    }

此时我们看核心代码tryLockInnerAsync

RFuture tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand command) {        this.internalLockLeaseTime = unit.toMillis(leaseTime);        return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, command,         //如果锁不存在,创建锁,并返回空        "if (redis.call('exists', KEYS[1]) == 0) then             redis.call('hset', KEYS[1], ARGV[2], 1);             redis.call('pexpire', KEYS[1], ARGV[1]);             return nil;          end;          //如果锁存在,且value匹配,说明是当前线程持有的锁,对key增量加1(重入次数+1),返回空         if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then             redis.call('hincrby', KEYS[1], ARGV[2], 1);             redis.call('pexpire', KEYS[1], ARGV[1]);             return nil;          end;          //申请锁失败,返回锁的剩余时间         return redis.call('pttl', KEYS[1]);", Collections.singletonList(this.getName()), new Object[]{this.internalLockLeaseTime, this.getLockName(threadId)});    }

上面可以看到如果有锁会返回nil,然后会看到最上面的代码,会判断,如果为null,会进行scheduleExpirationRenewal方法调用,如下

ttlRemainingFuture.onComplete((ttlRemaining, e) -> {                if (e == null) {                    if (ttlRemaining == null) {                        this.scheduleExpirationRenewal(threadId);                    }                }            });

scheduleExpirationRenewal方法内容如下:

protected RFuture renewExpirationAsync(long threadId) {        return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,        // 如果有锁则续期 30s(源码中初始化 this.lockWatchdogTimeout = 30000L)       "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then              redis.call('pexpire', KEYS[1], ARGV[1]);              return 1;         end;         return 0;", Collections.singletonList(this.getName()), new Object[]{this.internalLockLeaseTime, this.getLockName(threadId)});    }

此时我们终于看到了续期操作,了解了watch dog的原理。