boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException;
waitTime表示获取锁的最大等待时长,在第一次获取锁失败后不会立即返回,而是在等待时间内不断的去尝试获取锁。如果超过最大等待时长都没有成功获取到锁就会返回false。所以传入waitTime的值就变成了一个可重试的锁了。
leaseTime表示锁自动失效,释放锁的一个时间。不传值,默认值为-1。
unit是时间单位。
锁重试机制
必须要传入参数waitTime
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
@Override public boolean tryLock(long waitTime, TimeUnit unit) throws InterruptedException { return tryLock(waitTime, -1, unit); }
RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(waitTime, commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
private long lockWatchdogTimeout = 30 * 1000;
这个方法中,参数time就是上面介绍的waitTime。
当leaseTime锁过期时间不传时,默认值为-1,lockWatchdogTimeout看门狗的超时时间为30秒。
<T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) { internalLockLeaseTime = unit.toMillis(leaseTime); return evalWriteAsync(getName(), LongCodec.INSTANCE, command, "if (redis.call('exists', KEYS[1]) == 0) then " + "redis.call('hincrby', KEYS[1], ARGV[2], 1); " + "redis.call('pexpire', KEYS[1], ARGV[1]); " + "return nil; " + "end; " + "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(getName()), internalLockLeaseTime, getLockName(threadId)); }
上面的lua脚本
1.当判断锁不存在说明是第一次获取锁,则添加锁并计数加一,并记录锁标识,hash结构,获取锁成功,返回null。
2.当判断锁存在,并且是同一个线程获取锁,则直接计数加一,体现锁的可重入性,获取锁成功,返回null。
如果获取锁失败则返回锁的剩余有效期。
@Override public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException { long time = unit.toMillis(waitTime); long current = System.currentTimeMillis(); long threadId = Thread.currentThread().getId(); Long ttl = tryAcquire(waitTime, leaseTime, unit, threadId); // lock acquired if (ttl == null) { return true; } time -= System.currentTimeMillis() - current; if (time <= 0) { acquireFailed(waitTime, unit, threadId); return false; } current = System.currentTimeMillis(); RFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId); if (!subscribeFuture.await(time, TimeUnit.MILLISECONDS)) { if (!subscribeFuture.cancel(false)) { subscribeFuture.onComplete((res, e) -> { if (e == null) { unsubscribe(subscribeFuture, threadId); } }); } acquireFailed(waitTime, unit, threadId); return false; } try { time -= System.currentTimeMillis() - current; if (time <= 0) { acquireFailed(waitTime, unit, threadId); return false; } while (true) { long currentTime = System.currentTimeMillis(); ttl = tryAcquire(waitTime, leaseTime, unit, threadId); // lock acquired if (ttl == null) { return true; } time -= System.currentTimeMillis() - currentTime; if (time <= 0) { acquireFailed(waitTime, unit, threadId); return false; } // waiting for message currentTime = System.currentTimeMillis(); if (ttl >= 0 && ttl < time) { subscribeFuture.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS); } else { subscribeFuture.getNow().getLatch().tryAcquire(time, TimeUnit.MILLISECONDS); } time -= System.currentTimeMillis() - currentTime; if (time <= 0) { acquireFailed(waitTime, unit, threadId); return false; } } } finally { unsubscribe(subscribeFuture, threadId); } // return get(tryLockAsync(waitTime, leaseTime, unit)); }
time -= System.currentTimeMillis() - current;
剩余锁的等待时间 = 锁的最大等待时长 - 获取锁过程中消耗的时间
如果刚才获取锁失败,不会立即又去尝试获取锁,而是去订阅其他线程释放锁的信号。如果其他线程释放了锁,会发一个信号,这个信号就会被接收到。
使用到了信号量和消息订阅机制。
在unlock释放锁的源码中的lua脚本:
protected RFuture<Boolean> unlockInnerAsync(long threadId) { return evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " + "return nil;" + "end; " + "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " + "if (counter > 0) then " + "redis.call('pexpire', KEYS[1], ARGV[2]); " + "return 0; " + "else " + "redis.call('del', KEYS[1]); " + "redis.call('publish', KEYS[2], ARGV[1]); " + "return 1; " + "end; " + "return nil;", Arrays.asList(getName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId)); }
看出锁被释放的时候,会发布一条消息出去。
等待释放锁的信号,等待的时间 < 剩余锁的等待时间
如果超时,则取消消息订阅,获取锁失败。
只有不设置锁的过期时间,也就默认值为-1才会走看门狗的逻辑,默认锁超时时间是30秒的情况下,等到超时时间三分之一的时候,也就是10秒钟的时候会重新去刷新锁的有效期。当锁释放的时候,会去取消刷新锁的有效期。

redis主从同步延迟引起的锁失效问题

解决办法:
不存在主从节点,都是同级节点。

联锁
配置多个独立的redis, 获取锁的时候,每个redis都会去获取一遍锁,每个锁都获取到了才叫获取到锁。



