redisson锁笔记

本文详细介绍了Redisson分布式锁的工作原理,通过分析源码揭示了其获取和释放锁的过程。在获取锁时,Redisson使用了Lua脚本避免竞态条件,并支持可重入特性。解锁过程同样遵循特定逻辑。此外,针对Redis Cluster,Redisson有一套心跳检测机制,确保锁的有效性。

redisson version:2.1.4

去查看源码的原因:

以前简单理解的基于redis的锁,就是去setnx一个key,然后设置一个过期时间。多线程的时候,谁能set成功,说明谁获取锁成功。

然后解锁操作就是删除之前的key或者等待过期。

现在用了下redisson的锁,开始也以为是这样的,认为其他线程也能强制解锁。结果测试发现不行。

源码:

获取锁

 

<span style="font-size:14px;">private Long tryLockInner(final long leaseTime, final TimeUnit unit) {
        internalLockLeaseTime = unit.toMillis(leaseTime);

        return commandExecutor.evalWrite(getName(), RedisCommands.EVAL_INTEGER,
		//step1
                "local v = redis.call('get', KEYS[1]); " +
                                "if (v == false) then " +
				//step2
                                "  redis.call('set', KEYS[1], cjson.encode({['o'] = ARGV[1], ['c'] = 1}), 'px', ARGV[2]); " +
                                "  return nil; " +
                                "else " +
                                "  local o = cjson.decode(v); " +
                                "  if (o['o'] == ARGV[1]) then " +
				//step3
                                "    o['c'] = o['c'] + 1; redis.call('set', KEYS[1], cjson.encode(o), 'px', ARGV[2]); " +
                                "    return nil; " +
                                "  end;" +
                                "  return redis.call('pttl', KEYS[1]); " +
                                "end",
			//这个getName()就是加锁的key, 后面有2个参数, 加锁线程的唯一标识 和 锁定时间(毫秒)
                        Collections.<Object>singletonList(getName()), id.toString() + "-" + Thread.currentThread().getId(), internalLockLeaseTime);
}</span>

 

 

 

一段lua脚本,我加了一点注释.

本来刚开始看的时候,最上面step1通过先比较,在赋值的方式,这会存在竞态条件,不过redis的服务器是单线程执行的,所以没问题。

如果key不存在,到step2,保存了一个json的字符串值,下面是我本机的值。

"{\"o\":\"e0969e37-9520-4ca9-9995-48920756d159-23\",\"c\":1}"

这里取值的时候通过如下方式

eval "local v = redis.call('get', KEYS[1]); return v" 1 redisson.lock.selectId

如果从命令行直接输入 get redisson.lock.selectId, 则返回nil。获取不到值

如果可以存在,进入step3

这里是可重入锁,如果当前加锁的线程是之前成功获取到锁的线程,则把获取锁的数量+1。

 

上面脚本的完整版本就是 eval "${上面的脚本}" 1(key的数量) redisson.lock.selectId(锁的key) e0969e37-9520-4ca9-9995-48920756d159-23(锁定线程唯一标识) 300000(我锁定300秒) 

 

解锁

 

public void unlock() {
        Boolean opStatus = commandExecutor.evalWrite(getName(), RedisCommands.EVAL_BOOLEAN_R2,
                "local v = redis.call('get', KEYS[1]); " +
				//step1,无此key,则不需要其他操作,通知其他等锁的线程
                                "if (v == false) then " +
                                "  redis.call('publish', ARGV[4], ARGV[2]); " +
                                "  return true; " +
                                "else " +
                                "  local o = cjson.decode(v); " +
				//step2,依然先判断当前想要解锁的线程 是否是之前成功获取到锁的线程
				//是,继续往下
				//否,进入step6
                                "  if (o['o'] == ARGV[1]) then " +
				//step3,锁的次数先-1,对应上面加锁(锁可重入)
                                "    o['c'] = o['c'] - 1; " +
                                "    if (o['c'] > 0) then " +
				//step4,说明发生了锁重入,解锁次数不是一次就完成的,更新当前锁的次数
                                "      redis.call('set', KEYS[1], cjson.encode(o), 'px', ARGV[3]); " +
                                "      return false;"+
                                "    else " +
				//step5,已完全解锁,删除key
                                "      redis.call('del', KEYS[1]);" +
                                "      redis.call('publish', ARGV[4], ARGV[2]); " +
                                "      return true;"+
                                "    end" +
                                "  end;" +
				//step6,对应step2的条件分支
                                "  return nil; " +
                                "end",
                        Collections.<Object>singletonList(getName()), id.toString() + "-" + Thread.currentThread().getId(), unlockMessage, internalLockLeaseTime, getChannelName());
        if (opStatus == null) {
            throw new IllegalStateException("Can't unlock lock Current id: "
                    + id + " thread-id: " + Thread.currentThread().getId());
        }
        if (opStatus) {
            stopRefreshTask();
        }
}

基本解锁流程见上面注释即可。

 

完整的执行脚本如下。

eval "${上面的脚本}" 1(key的数量)  redisson.lock.selectId(锁的key,即KEYS[1])  b69c1b77-8ff8-4dd3-9c90-012eee74bd34-23(锁定线程唯一标识) 0 300000 redisson__lock__channel__{redisson.lock.selectLoan}

最后4个值就是脚本里面的ARGV[1],ARGV[2],ARGV[3],ARGV[4]

 

redisson:3.10.2

redisson对应的是redis cluster时,会有专门的线程进行类似的心跳检测,使用的线程的配置在

org.redisson.config.Config --> private int nettyThread = 32;

这个值可以进行调整,在测试环境发现进行检测是一直都在线程序号为2的线程(即一直是一个线程),比如调小,减少资源开销。


欢迎关注微信公众号:  假程序员

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值