先说重点
结论
- redission分布式锁加锁方式是redis 的 hash数据结构(别再傻傻的说是setnx了)。其中,key是锁名称,value的field1是 redission cliendId +线程id, field2是加锁次数。每次重入时加锁次数++,释放时–,等于0时释放锁
- 加锁失败后重试并不是不是简单的while循环,而且订阅到redis锁被释放后才会重试的
- 加锁和解锁都是采用的lua脚本,因为lua脚本可以保证原子性,不会插入redis 的其他指令
- 如果加锁时不加过期时间,redis 会自动设置过期时间为30s,并开启一次自动续期的任务,10s后执行,执行的时候会再次续期30s(改为30s而不是加30s),并再次开启一次自动续期的任务)(时间轮算法)
源码分析
分布式锁的入口tryLock
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
// ttl为锁的剩余时间 如果为空 说明获取到锁了
if (ttl == null) {
return true;
}
// time 剩余等待是时间 如果剩余等待时间小于0 即获取锁失败
time -= System.currentTimeMillis() - current;
if (time <= 0) {
acquireFailed(waitTime, unit, threadId);
return false;
}
// 下面是锁的重试 这里的重试也不是直接重试 而是通过订阅的方式,即如果在剩余等待时间内订阅到释放锁的通知才会重试
current = System.currentTimeMillis();
CompletableFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);
try {
subscribeFuture.get(time, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
if (!subscribeFuture.completeExceptionally(new RedisTimeoutException(
"Unable to acquire subscription lock after " + time + "ms. " +
"Try to increase 'subscriptionsPerConnection' and/or 'subscriptionConnectionPoolSize' parameters."))) {
subscribeFuture.whenComplete((res, ex) -> {
if (ex == null) {
unsubscribe(res, threadId);
}
});
}
acquireFailed(waitTime, unit, threadId);
return false;
} catch (ExecutionException e) {
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) {
commandExecutor.getNow(subscribeFuture).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
} else {
commandExecutor.getNow(subscribeFuture).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
}
time -= System.currentTimeMillis() - currentTime;
if (time <= 0) {
acquireFailed(waitTime, unit, threadId);
return false;
}
}
} finally {
unsubscribe(commandExecutor.getNow(subscribeFuture), threadId);
}
// return get(tryLockAsync(waitTime, leaseTime, unit));
}
实际尝试获取锁 tryAcquire
private Long tryAcquire(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
return get(tryAcquireAsync(waitTime, leaseTime, unit, threadId));
}
tryAcquireAsync
private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
RFuture<Long> ttlRemainingFuture;
// leaseTime 为设置的过期时间 大于0 说明设置了有效的时间
if (leaseTime > 0) {
ttlRemainingFuture = tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
} else {
// 否则就默认设置过期时间为30s
ttlRemainingFuture = tryLockInnerAsync(waitTime, internalLockLeaseTime,
TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
}
// 开启续期
CompletionStage<Long> f = ttlRemainingFuture.thenApply(ttlRemaining -> {
// lock acquired ttlRemaining为剩余时间 为空说明获取到的锁
if (ttlRemaining == null) {
// 如果设置的过期时间 则过期时间就是剩余的时间
if (leaseTime > 0) {
internalLockLeaseTime = unit.toMillis(leaseTime);
} else {
// 如果没有设置过期时间 就自动续期
scheduleExpirationRenewal(threadId);
}
}
return ttlRemaining;
});
return new CompletableFutureWrapper<>(f);
}
tryLockInnerAsync
<T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
return evalWriteAsync(getRawName(), LongCodec.INSTANCE, command,
// 此处为lua脚本,redission加锁是通过hash数据结构进行的 key 为锁的名称 vaule 的field为线程id value为加锁次数
// KEYS[1]为hash的key 表示锁名称
// ARGV[1]为 hash的value对应的field 线程id
// ARGV[2]为hash的value对应的value 加锁次数
"if (redis.call('exists', KEYS[1]) == 0) then " +
// 如果锁不存在 则执行加锁 并设置加锁次数为1 设置过期时间 返回null
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
// 如果锁存在,且对应的线程id 也存在 则说明是此线程持有锁
// 执行加锁次数自增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(getRawName()), unit.toMillis(leaseTime), getLockName(threadId));
}
//
scheduleExpirationRenewal
protected void scheduleExpirationRenewal(long threadId) {
ExpirationEntry entry = new ExpirationEntry();
ExpirationEntry oldEntry = EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry);
// 如果锁存在 则加入改线程id 由于之前已经加了续期任务 不必再执行renewExpiration
if (oldEntry != null) {
oldEntry.addThreadId(threadId);
} else {
// 锁不存在 除了加入线程id 需要开启续期
entry.addThreadId(threadId);
try {
renewExpiration();
} finally {
if (Thread.currentThread().isInterrupted()) {
cancelExpirationRenewal(threadId);
}
}
}
}
renewExpiration
private void renewExpiration() {
ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());
if (ee == null) {
return;
}
Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());
if (ent == null) {
return;
}
// 取的永远是第一个线程id
Long threadId = ent.getFirstThreadId();
if (threadId == null) {
return;
}
// 续期
CompletionStage<Boolean> future = renewExpirationAsync(threadId);
// 开启一个10s后执行的任务 该任务是调用自己 继续续期 下次继续开启任务
future.whenComplete((res, e) -> {
if (e != null) {
log.error("Can't update lock " + getRawName() + " expiration", e);
EXPIRATION_RENEWAL_MAP.remove(getEntryName());
return;
}
if (res) {
// reschedule itself
renewExpiration();
} else {
cancelExpirationRenewal(null);
}
});
}
}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
ee.setTimeout(task);
}
renewExpiration
// 每次续期增加30s
protected CompletionStage<Boolean> renewExpirationAsync(long threadId) {
return evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return 1; " +
"end; " +
"return 0;",
Collections.singletonList(getRawName()),
internalLockLeaseTime, getLockName(threadId));
}