Redisson分布式锁源码解析

分布式锁

在微服务等多节点部署的环境下,系统会产生分布式事务问题,其解决的方式有很多,如ZooKeeper、Redis、Seata等,本文介绍基于Redis开发的Redisson框架如何实现Redis版本的分布式锁。

使用(基于SpringBoot)

引入依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson</artifactId>
        <version>3.6.5</version>
    </dependency>
</dependencies>

添加配置信息

@Configuration
public class RedissonConfig {

    @Bean
    public RedissonClient redissonClient(){
        Config config = new Config();
        config.useSingleServer()
                .setAddress("redis://192.168.93.128:6379")
                .setDatabase(0)
                .setPassword("123456")
                .setConnectionPoolSize(50)
                .setConnectionMinimumIdleSize(10);
        return Redisson.create(config);
    }

}

测试接口

@Slf4j
@RestController
public class LockController {

    @Autowired
    private RedissonClient redissonClient;

    @RequestMapping("/test_lock")
    public String testLock(@RequestParam("key") String key) {
        RLock redissonLock = redissonClient.getLock("lock:" + key);
        try {
            redissonLock.lock();
            // todo 业务代码
            return "success";
        } catch (Exception e) {
            log.error(e + "");
            return "error";
        } finally {
            redissonLock.unlock();
        }
    }

}


源码分析

上方示例通过redissonLock.lock();和redissonLock.unlock();两个命令即可实现基于Redis的分布式锁,下面会针对该API进行源码分析。

源码调用流程图
在这里插入图片描述

加锁源码

lockInterruptibly()源码分析

    @Override
    public void lock(long leaseTime, TimeUnit unit) {
        try {
            lockInterruptibly(leaseTime, unit);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
    	// -1和null表示采用默认的锁超时时间
        lockInterruptibly(-1, null);
    }

    @Override
    public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException {
    	// 获取当前线程id
        long threadId = Thread.currentThread().getId();
        // 尝试加锁
        Long ttl = tryAcquire(leaseTime, unit, threadId);
        // 如果ttl为空表示加锁成功,否则该线程被其他客户端加锁
        if (ttl == null) {
			// 加锁成功直接返回
            return;
        }
		
		// 加锁失败对该线程进行订阅等待
        RFuture<RedissonLockEntry> future = subscribe(threadId);
        commandExecutor.syncSubscription(future);

        try {
        	// 自旋重新获取锁
            while (true) {
            	// 再次尝试获取锁
                ttl = tryAcquire(leaseTime, unit, threadId);
                // 获取成功返回,否则继续自旋
                if (ttl == null) {
                    break;
                }

                // 如果锁资源被占用则在等待时间结束后再重试,否则直接尝试加锁
                if (ttl >= 0) {
                    getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                } else {
                    getEntry(threadId).getLatch().acquire();
                }
            }
        } finally {
        	// 取消对该线程的订阅
            unsubscribe(future, threadId);
        }
    }

tryAcquire()源码分析

    private Long tryAcquire(long leaseTime, TimeUnit unit, long threadId) {
        return get(tryAcquireAsync(leaseTime, unit, threadId));
    }
    
    private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, final long threadId) {
    	// 在lockInterruptibly(-1, null);传入了-1,表示使用默认的leastTime
        if (leaseTime != -1) {
            return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
        }
        // 使用异步方式尝试加锁
        RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
        // 将该锁加入订阅,并进行锁续命
        ttlRemainingFuture.addListener(new FutureListener<Long>() {
            @Override
            public void operationComplete(Future<Long> future) throws Exception {
                if (!future.isSuccess()) {
                    return;
                }

                Long ttlRemaining = future.getNow();
                // 如果锁再次获取成功,获取新的超时时间,对锁进行续命
                if (ttlRemaining == null) {
                    scheduleExpirationRenewal(threadId);
                }
            }
        });
        // 返回锁的超时时间
        return ttlRemainingFuture;
    }

tryLockInnerAsync()源码分析

    <T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
        internalLockLeaseTime = unit.toMillis(leaseTime);
		
		// 使用Lua脚本保证原子性,底层进行hash结构的锁赋值操作,并设置超时时间
        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
        		 // 如果该锁不存在,则初始化key fleid value分别为传入的keyName,锁标识,1(表示重入次数),并设置超时时间
                  "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; " +
                  // 如果该锁的name和锁标识都相同,则表示是可重入锁,将value+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]);",
                  // getName()传入KEYS[1],表示传入加锁的keyName
                  // internalLockLeaseTime传入ARGV[1],表示锁的超时时间
                  // getLockName(threadId)传入ARGV[2],表示锁的唯一标识,由UUID+":"+线程id组成
                    Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
    }

scheduleExpirationRenewal()源码分析

    private void scheduleExpirationRenewal(final long threadId) {
        if (expirationRenewalMap.containsKey(getEntryName())) {
            return;
        }
		
		// 启用定时任务线程更新锁的超时时间,每internalLockLeaseTime / 3 秒执行一次(10s执行一次)
        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,
                		// 如果redis中存在该锁,则更新锁的超时时间为30s(默认值)
                        "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.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, TimeUnit.MILLISECONDS);

        if (expirationRenewalMap.putIfAbsent(getEntryName(), task) != null) {
            task.cancel();
        }
    }

解锁源码

unlock()源码分析

    @Override
    public void unlock() {
	    // 调用异步解锁方法
        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();
        }
    }

unlockInnerAsync()源码分析

    protected RFuture<Boolean> unlockInnerAsync(long threadId) {
        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
		        // 如果该锁不存在,则发布已经解锁的消息
                "if (redis.call('exists', KEYS[1]) == 0) then " +
                    "redis.call('publish', KEYS[2], ARGV[1]); " +
                    "return 1; " +
                "end;" +
                // 如果该锁对应的name和锁标识不匹配,说明该客户端没有该锁,无法解锁
                "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
                    "return nil;" +
                "end; " +
                // 可重入锁的value-1
                "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 " +
                    "redis.call('del', KEYS[1]); " +
                    "redis.call('publish', KEYS[2], ARGV[1]); " +
                    "return 1; "+
                "end; " +
                "return nil;",
                // getName()传入KEYS[1],表示传入解锁的keyName
                // getChannelName()传入KEYS[2],表示redis内部的消息订阅channel
                // LockPubSub.unlockMessage传入ARGV[1],表示向其他redis客户端线程发送解锁消息
                // internalLockLeaseTime传入ARGV[2],表示锁的超时时间
                // getLockName(threadId)传入ARGV[3],表示锁的唯一标识,由UUID+":"+线程id组成
                Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(threadId));

    }
    

cancelExpirationRenewal() 源码分析

    void cancelExpirationRenewal() {
    	// 将该线程从定时任务中删除
        Timeout task = expirationRenewalMap.remove(getEntryName());
        if (task != null) {
            task.cancel();
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值