Redisson 分布式锁实现和部分源码解析

一、关于 Redisson

  1. 基于java框架netty进行扩展了的redis客户端
  2. 实现了分布式和可拓展的数据结构
  3. API是线程安全的,所以可以操作单个Redisson连接来完成各种操作
  4. 加锁逻辑都是通过lua脚本来完成的,lua脚本执行能够保证原子性, 锁为可重入锁

二、使用

1. Spring Boot 引入依赖
	<!-- redis -->
	 <dependency>
	 	<groupId>org.springframework.boot</groupId>
	 	<artifactId>spring-boot-starter-data-redis</artifactId>
	 </dependency>
	<dependency>
		<groupId>org.redisson</groupId>
		<artifactId>redisson</artifactId>
		<version>3.11.6</version>
	</dependency>
2. 配置,主要配置方式为

a. 程序化配置
b. JSON 文件引入
c. YAML 文件引入

这里启用第一种配置方式, 更多参数配置参考:Redisson配置方法

package com.sf.vsolution.hn.iwms.redis;


import cn.hutool.core.util.StrUtil;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.config.SingleServerConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class RedissonConfig {

    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private String port;
    @Value("${spring.redis.timeout}")
    private Integer timeout;
    @Value("${spring.redis.password}")
    private String password;
    @Value("${spring.redis.database}")
    private Integer database;

    // 单节点
    @Bean
	@ConditionalOnProperty(name = "spring.redis.mode", havingValue = "single")
    public RedissonClient getRedisson() {
        Config config = new Config();
        // 单节点模式
        SingleServerConfig serverConfig = config.useSingleServer().setAddress("redis://" + host + ":" + port)
                .setTimeout(timeout)
                .setDatabase(database);

        if (StrUtil.isNotBlank(password)) {
            serverConfig.setPassword(password);
        }
        return Redisson.create(config);
    }
    // 集群
    @Bean
	@ConditionalOnProperty(name = "spring.redis.mode", havingValue = "cluster")
    public RedissonClient redissonClientCluster() throws IOException {
        String[] nodes = urls.split(",");
        for (int i = 0; i < nodes.length; i++) {
            nodes[i] = "redis://" + nodes[i];
        }
        Config config = new Config();
        config.useClusterServers() // 这是用的集群server
                .setScanInterval(2000) // 设置集群状态扫描时间
                .addNodeAddress(nodes).setPassword(password);
        RedissonClient redisson = Redisson.create(config);
        // 可通过打印redisson.getConfig().toJSON().toString()来检测是否配置成功
        return redisson;
    }

    // 哨兵
    @Bean
	@ConditionalOnProperty(name = "spring.redis.mode", havingValue = "sentinel")
    public RedissonClient redissonClientCluster()  {
        Config config = new Config();
        // 哨兵模式
        config.useSentinelServers().addSentinelAddress("127.0.0.1:6369", "127.0.0.1:6379", "127.0.0.1:6389")
                .setMasterName("masterName")
                .setPassword("password").setDatabase(0);

        RedissonClient redissonClient = Redisson.create(config);

        return redissonClient;
    }
}


3. 业务使用:推荐使用 tryLock
// 注入RedissClient客户端
@Autowired
private RedissonClient redissonClient;

RLock rLock = redissonClient.getLock(redisKey);
Boolean isBlock = false;
try {
	// 500ms 获取不到锁就失败, 10s 锁过期时间
	isBlock = rLock.tryLock(500, 10000, TimeUnit.MILLISECONDS);
	if(!isBlock){
	   // 获取锁失败
	}
} catch (Exception e) {
	logger.error("获取redission锁失败: {}", e.getMessage());
} finally {
	// 释放锁
	if (isBlock && rLock.isHeldByCurrentThread()) {
		rLock.unlock();
	}
}

三、部分源码解析

1. 加锁
// name为设置的锁名称
public RLock getLock(String name) {
        return new RedissonLock(connectionManager.getCommandExecutor(), name);
    }

public RedissonLock(CommandAsyncExecutor commandExecutor, String name) {
        super(commandExecutor, name);
        // 命令执行器
        this.commandExecutor = commandExecutor;
        // UUID字符串
        this.id = commandExecutor.getConnectionManager().getId();
        // 内部锁过期时间
        this.internalLockLeaseTime = commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout();
        this.entryName = id + ":" + name;
        this.pubSub = commandExecutor.getConnectionManager().getSubscribeService().getLockPubSub();
    }

// 加锁实现 leaseTime:锁占用时间    
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
        long time = unit.toMillis(waitTime);
        long current = System.currentTimeMillis();
		// 当前线程id
        long threadId = Thread.currentThread().getId();
		// 尝试获取锁
        Long ttl = tryAcquire(leaseTime, unit, threadId);
        // lock acquired
		// 如果ttl为空,则证明获取锁成功
        if (ttl == null) {
            return true;
        }
        
        time -= System.currentTimeMillis() - current;
        if (time <= 0) {
            acquireFailed(threadId);
            return false;
        }
        
        current = System.currentTimeMillis();
		// 如果获取锁失败,则订阅到对应这个锁的channel,当其他线程释放锁时,通知线程去获取锁
        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(threadId);
            return false;
        }

        try {
            time -= System.currentTimeMillis() - current;
            if (time <= 0) {
                acquireFailed(threadId);
                return false;
            }
        
            while (true) {
                long currentTime = System.currentTimeMillis();
				// 再次尝试获取锁
                ttl = tryAcquire(leaseTime, unit, threadId);
                // lock acquired
                if (ttl == null) {
                    return true;
                }

                time -= System.currentTimeMillis() - currentTime;
				// 等待超时
                if (time <= 0) {
                    acquireFailed(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(threadId);
                    return false;
                }
            }
        } finally {
			// 取消对channel的订阅
            unsubscribe(subscribeFuture, threadId);
        }
//        return get(tryLockAsync(waitTime, leaseTime, unit));
    }

// 尝试获取锁:
private Long tryAcquire(long leaseTime, TimeUnit unit, long threadId) {
        return get(tryAcquireAsync(leaseTime, unit, threadId));
    }
    
private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) {
		//如果带有过期时间,则按照普通方式获取锁
        if (leaseTime != -1) {
            return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
        }
        // 先按照30秒的过期时间来执行获取锁的方法
        RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
        // 如果还持有这个锁,则开启定时任务不断刷新该锁的过期时间
        ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
            if (e != null) {
                return;
            }

            // lock acquired
            if (ttlRemaining == null) {
                scheduleExpirationRenewal(threadId);
            }
        });
        return ttlRemainingFuture;
    }

// 执行核心 lua 代码
// 这里KEYS[1]就是getName()--对应的redisKey,ARGV[2]是getLockName(threadId)--uuid:threadId,
// ARGV[1] 是leaseTime锁占用时间
<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
        internalLockLeaseTime = unit.toMillis(leaseTime);

        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
        		  //如果锁不存在,则通过hset设置它的值,并设置过期时间
                  "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; " +
                  //如果锁已存在,并且锁的是当前线程,则通过hincrby给数值递增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; " +
                  //如果锁已存在,但并非本线程,则返回过期时间ttl
                  "return redis.call('pttl', KEYS[1]);",
                    Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
    }
2. 解锁
public RFuture<Void> unlockAsync(long threadId) {
        RPromise<Void> result = new RedissonPromise<Void>();
        // 底层解锁方法
        RFuture<Boolean> future = unlockInnerAsync(threadId);

        future.onComplete((opStatus, e) -> {
            if (e != null) {
                cancelExpirationRenewal(threadId);
                result.tryFailure(e);
                return;
            }
			// 如果返回空,则证明解锁的线程和当前锁不是同一个线程,抛出异常
            if (opStatus == null) {
                IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: "
                        + id + " thread-id: " + threadId);
                result.tryFailure(cause);
                return;
            }
            // 解锁成功,取消刷新过期时间的那个定时任务
            cancelExpirationRenewal(threadId);
            result.trySuccess(null);
        });

        return result;
    }

// unlockInnerAsync
// 假设name=redisKey,假设线程ID是Thread-1
// KEYS[1]是getName(),即KEYS[1]=redisKey
// KEYS[2]是getChannelName(),即KEYS[2]=redisson_lock__channel:{redisKey}
// ARGV[1]是LockPubSub.unlockMessage,即ARGV[1]=0
// ARGV[2]是生存时间
// ARGV[3]是getLockName(threadId),即ARGV[3]=6f0829ed-bfd3-4e6f-bba3-6f3d66cd176c:Thread-1
protected RFuture<Boolean> unlockInnerAsync(long threadId) {
        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
        		//如果释放锁的线程和已存在锁的线程不是同一个线程,返回null
                "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
                    "return nil;" +
                "end; " +
                //通过hincrby递减1的方式,释放一次锁
                "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
                "if (counter > 0) then " +
                    "redis.call('pexpire', KEYS[1], ARGV[2]); " +
                    "return 0; " +
                //否则证明锁已经释放,删除key并发布锁释放的消息
                "else " +
                    "redis.call('del', KEYS[1]); " +
                    "redis.call('publish', KEYS[2], ARGV[1]); " +
                    "return 1; "+
                "end; " +
                "return nil;",
                Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId));

    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值