使用redisson做分布式锁源码分析
分布式锁加锁原则:
1、互斥性(同一时刻只能有一个线程持有锁)
2、防死锁(锁必须有过期时间,避免服务崩溃后出现死锁)
3、防误删(线程只能释放自己持有的锁)
为什么要使用redisson做分布式锁,而不是使用redisTemplate的原始命令:
1、原子性:使用 redisTemplate 的原始命令时,加锁和设置过期时间是两个操作,这就可能出现以下情况:
a、加锁成功,此时服务宕机,那么就会出现死锁(redisson内部使用lua脚本,实现加锁和设置过期时间为一个原子性操作,避免出现死锁的风险)
2、锁自动续期:线程t1使用redisTemplate 手动设置锁过期时间时,业务逻辑执行时间超过锁过期时间,锁就会释放,此时其他线程t2抢占到锁,当前线程t1执行完业务释放锁,就会出现释放线程t2持有的锁(redisson内部当线程加锁成功后,就会执行锁续命逻辑(看门狗机制)定期检查当前线程加的锁是否还存在,存在则把锁过期时间重置(默认30秒),确保锁能持续持有直到业务逻辑执行完毕)
redisson加锁原理如下:

redisson依赖版本
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.6.5</version> </dependency>
测试代码
@RestController public class TestRedissonLock { @Autowired private RedissonClient redissonClient; private int stock = 10 ; @RequestMapping("/reduceStock") public void reduceStock(){ RLock lock = redissonClient.getLock("key"); //加锁,默认加锁时间30秒,每过10秒后台线程锁过期时间更新为30秒 //抢占锁失败的线程,订阅等待释放锁的消息,等待抢占锁 lock.lock(); try{ System.out.println("减库存前1:"+stock); stock=stock-1; System.out.println("减库存后2:"+stock); }finally { //解锁,并通知唤醒等待获取锁的阻塞线程 if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } @RequestMapping("/reduceStockNoWait") public String reduceStockNoWait() { RLock lock = redissonClient.getLock("key"); boolean isLocked = false; // 核心:tryLock(0, 30, TimeUnit.SECONDS) // 第一个参数0 = 等待时间0秒(加锁失败直接返回,不阻塞) // 第二个参数30 = 锁的持有时间30秒(防止死锁) try { // 尝试加锁:等待0秒(不阻塞),持有锁30秒 isLocked = lock.tryLock(0, 30, TimeUnit.SECONDS); if (!isLocked) { // 加锁失败,直接返回提示,不执行减库存 return "当前抢购人数过多,请稍后再试!"; } // 加锁成功,执行减库存逻辑 System.out.println("减库存前:" + stock); stock = stock - 1; System.out.println("减库存后:" + stock); return "减库存成功,剩余库存:" + stock; } catch (InterruptedException e) { // 线程被中断时的异常处理 Thread.currentThread().interrupt(); // 恢复中断状态 return "操作被中断,请重试!"; } finally { // 必须判断:只有当前线程持有锁时才解锁 if (isLocked && lock.isHeldByCurrentThread()) { lock.unlock(); } } } }
1、加锁、循环等待抢占锁、释放锁源码逻辑如下:
//a、查看RedissonLock的#91#行,使用lockInterruptibly()加可中断锁 this.lockInterruptibly(); //b、查看RedissonLock的#109#行,过期时间为-1 this.lockInterruptibly(-1L, (TimeUnit)null); //c、继续从RedissonLock的#113#行,查看具体加锁逻辑如下 public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException { //获取当前线程ID long threadId = Thread.currentThread().getId(); //1、多线程竞争加锁 //1.1线程a使用lua脚本尝试加锁,加锁成功返回ttl为null //1.2线程b使用lua脚本尝试加锁,加锁失败返回ttl为a线程加锁的剩余过期时间 /**lua脚本执行逻辑 * commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command, * //当key为getName()的锁不存在时,则执行加锁,并设置过期时间internalLockLeaseTime * "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; " + * //锁冲入逻辑 * //当k1为getName(),k2为getLockName(threadId)的hash锁存在时, * //则设置hash锁的value值+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; " + * //加锁失败,则返回key为getName()的锁的过期时间 * "return redis.call('pttl', KEYS[1]);", * Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId)); */ Long ttl = tryAcquire(leaseTime, unit, threadId); //线程a加锁成功,则直接返回 if (ttl == null) { return; } //2、线程b加锁失败 //2.1、线程b订阅锁释放消息,也就是lock.unlock()后,执行RedissonLock的#460#行,发布锁释放的消息。 //2.2、接受到锁释放消息后,执行LockPubSub的#36#行代码,调用semaphore.release()唤醒等待的线程b进入阻塞状态,准备抢占锁 RFuture<RedissonLockEntry> future = subscribe(threadId); commandExecutor.syncSubscription(future); //3、线程b抢占锁失败,循环执行抢占锁逻辑 try { while (true) { //3.1、线程b再次尝试加锁, //返回ttl为null,则加锁成功 //返回ttl不为null则加锁失败,返回线程a加锁的剩余有效时间 ttl = tryAcquire(leaseTime, unit, threadId); // lock acquired if (ttl == null) { break; } // waiting for message if (ttl >= 0) { //3.2执行代码后,线程b进入有时限等待状态,此时分为两种情况(注:调用getEntry(threadId).getLatch()返回new Semaphore(0)) //1、等待ttl秒后,线程b进入阻塞状态,调用3.1逻辑准备再次抢占锁 //2、lock.unlock()手动释放锁后,执行semaphore.release()唤醒线程b进入阻塞状态,调用3.1逻辑准备再次抢占锁 getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS); } else { //ttl小于0时,线程b直接进入阻塞状态,调用3.1逻辑准备再次抢占锁 getEntry(threadId).getLatch().acquire(); } } } finally { //4、释放订阅消息 unsubscribe(future, threadId); } }
2、锁续命逻辑如下:
//a、查看RedissonLock的#115#行 Long ttl = tryAcquire(leaseTime, unit, threadId); //b、查看RedissonLock的#146#行 return get(tryAcquireAsync(leaseTime, unit, threadId)); //c、查看RedissonLock的#171#行开始 private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, final long threadId) { if (leaseTime != -1) { return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG); } //1、加锁 RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG); //2、监听加锁逻辑执行完成后,则执行更新过期时间逻辑 ttlRemainingFuture.addListener(new FutureListener<Long>() { @Override public void operationComplete(Future<Long> future) throws Exception { if (!future.isSuccess()) { return; } Long ttlRemaining = future.getNow(); //线程a加锁成功,执行具体更新过期时间逻辑 if (ttlRemaining == null) { scheduleExpirationRenewal(threadId); } } }); return ttlRemainingFuture; } //2.1、具体更新锁过期时间逻辑从RedissonLock的#198#行开始查看 private void scheduleExpirationRenewal(final long threadId) { if (expirationRenewalMap.containsKey(getEntryName())) { return; } //每隔 internalLockLeaseTime / 3秒,更新锁过期时间为 internalLockLeaseTime( internalLockLeaseTime默认为30秒) Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() { @Override public void run(Timeout timeout) throws Exception { RFuture<Boolean> future = commandExecutor.evalWriteAsync(getName(), 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.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId)); //监听future,如果返回1,那么继续调用scheduleExpirationRenewal future.addListener(new FutureListener<Boolean>() { @Override public void operationComplete(Future<Boolean> future) throws Exception { expirationRenewalMap.remove(getEntryName()); if (!future.isSuccess()) { log.error("Can't update lock " + getName() + " expiration", future.cause()); return; } if (future.getNow()) { //循环调用自己 scheduleExpirationRenewal(threadId); } } }); } //每隔 internalLockLeaseTime/3秒执行一次过期时间更新 }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS); // if (expirationRenewalMap.putIfAbsent(getEntryName(), task) != null) { task.cancel(); } }
3、解锁逻辑如下:
//3.1、查看RedissonLock的#365#行 public void unlock() { //解锁 /**解锁lua脚本具体逻辑 * commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, * //当key为getName()的锁不存在,则发布锁释放消息,通知调用semaphore.release()释放锁,唤醒等待线程进入阻塞状态开始抢占锁 * "if (redis.call('exists', KEYS[1]) == 0) then " + * "redis.call('publish', KEYS[2], ARGV[1]); " + * "return 1; " + * "end;" + * //如果hash锁getName(),getLockName(threadId)不存在,直接返回null * "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " + * "return nil;" + * "end; " + * "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " + * //counter>0则可重入锁,重入次数未释放完成 * //继续更新过期时间为internalLockLeaseTime * "if (counter > 0) then " + * "redis.call('pexpire', KEYS[1], ARGV[2]); " + * "return 0; " + * "else " + * //counter=0则为可重入锁,释放完成重入次数 * //counter<0则为非重入锁, * //则删除getName()锁,发布锁释放消息,通知调用semaphore.release()释放锁,唤醒等待线程进入阻塞状态开始抢占锁 * "redis.call('del', KEYS[1]); " + * "redis.call('publish', KEYS[2], ARGV[1]); " + * "return 1; "+ * "end; " + * "return nil;", * Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(threadId)); */ Boolean opStatus = get(unlockInnerAsync(Thread.currentThread().getId())); if (opStatus == null) { throw new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: " + id + " thread-id: " + Thread.currentThread().getId()); } //如果解锁成功,则取消锁续命任务 if (opStatus) { cancelExpirationRenewal(); } }
protected RFuture<Boolean> unlockInnerAsync(long threadId) { return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, //如果getName()锁已不存在,那么就发布消息通知其他竞争锁失败的线程,重新执行tryAcquire(leaseTime, unit, threadId)竞争分布式锁 "if (redis.call('exists', KEYS[1]) == 0) then " + "redis.call('publish', KEYS[2], ARGV[1]); " + "return 1; " + "end;" + //检查当前线程是否持有锁,是则返回null "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " + "return nil;" + "end; " + //减少锁的重入计数(可重入锁逻辑) "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " + //如果大于0,证明是重入锁,重置锁过期时间 "if (counter > 0) then " + "redis.call('pexpire', KEYS[1], ARGV[2]); " + "return 0; " + "else " + //不是重入锁,则删除锁,并发布消息通知其他竞争锁失败的线程,重新执行tryAcquire(leaseTime, unit, threadId)竞争分布式锁 "redis.call('del', KEYS[1]); " + "redis.call('publish', KEYS[2], ARGV[1]); " + "return 1; "+ "end; " + "return nil;", Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(threadId)); }
3.2、执行redis.call('del', KEYS[1]); 删除分布式锁
3.3、执行redis.call('publish', KEYS[2], ARGV[1]);发布锁释放消息
3.4、触发分布式订阅,执行订阅逻辑,具体代码查看LockPubSub的#36#行onMessage方法
protected void onMessage(RedissonLockEntry value, Long message) {
if (message.equals(unlockMessage)) {
//具体通过new Semaphore(0).release()唤醒阻塞的加锁线程
value.getLatch().release();
//
while (true) {
Runnable runnableToExecute = null;
synchronized (value) {
Runnable runnable = value.getListeners().poll();
if (runnable != null) {
if (value.getLatch().tryAcquire()) {
runnableToExecute = runnable;
} else {
value.addListener(runnable);
}
}
}
if (runnableToExecute != null) {
runnableToExecute.run();
} else {
return;
}
}
}
}
3.5、继续执行1步骤**加锁、循环等待抢占锁、释放锁源码**

浙公网安备 33010602011771号