一、为什么要使用radssion?
单个服务内可以利用JVM提供的相关锁如ReentrantLock,synchronized关键字等,保证多线程执行任务的安全性。但在分布式场景下,多个服务存在多个JVM中,就需要借助分布式锁,实现不同服务间的并发任务控制。
需要考虑的问题:1.锁可重入;2.可重试;3.超时释放问题;4.集群环境下主从一致性问题
二、 radssion使用
依赖引入(此处为java项目,如果为springboot项目引入直接引入相关starter即可):
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.14.0</version>
</dependency>
配置及对象获取
private static Config config= new Config();
static {
config.useSingleServer().setAddress("redis://127.0.0.1:6379").setPassword("redis123");
}
public static RedissonClient getClient(){
return Redisson.create(config);
}
public static void main(String[] args) {
RedissonClient client = Redisson.create(config);
RLock lock = client.getLock("test");
try {
lock.tryLock(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
client.shutdown();
}
}
三、源码阅读
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();
//此方法后面详细解答,此处如果获取锁成功则返回null,否则返回当前锁对象过期的时间
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();
//ttl为锁过期时间 time为等待时间,如果锁过期时间短于等待时间则使用锁过期时间否则使用等待时间继续获取锁
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));
}
tryAcquireOnceAsync被tryAcquire调用
private RFuture<Boolean> tryAcquireOnceAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
//设置释放时间使用释放时间去获取锁
if (leaseTime != -1) {
return tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
}
//没有设置释放时间使用默认释放时间去获取锁
RFuture<Boolean> ttlRemainingFuture = tryLockInnerAsync(waitTime, internalLockLeaseTime,
TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
if (e != null) {
return;
}
// lock acquired
if (ttlRemaining) {
//获取到锁以后延长锁的过期时间
scheduleExpirationRenewal(threadId);
}
});
return ttlRemainingFuture;
}
tryLockInnerAsync属于tryAcquire
<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,
//不存在这个key放置过期并返回null,否则返回过期时间'pttl', KEYS[1]
//此处使用脚本是为了保证整个操作的原子性
"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));
}