Redission分布式锁

前置学习:Redis发布订阅模式Redis主从切换(单点故障)解决源码-CSDN博客 

工作过程

 

分布式锁加锁

        工作过程

        1、先判断当前锁对象是否存在(exists命令)

        2、如果不存在当前锁对象,重新初始化锁对象,并且设置到期时间 (pexpire命令),加锁成功。

        3、如果存在当前锁对象,执行锁重入,即根据锁对象和当前线程id找到计数器,执行重入一次+1操作。

        4、当其他线程访问无法获得正在加锁对象,返回到期时间,表示加锁失败。

源码

锁对象

"sdslock":{ "f400aad5-4b1f-4246-a81e-80c2717c3afb:1":"1"}

//可重入锁,两种上锁方式,第二种方式用了看门狗+租赁续约,效果更好,
org.redisson.RedissonLock#lock(long, java.util.concurrent.TimeUnit, boolean)
org.redisson.RedissonLock#tryLock(long, long, java.util.concurrent.TimeUnit)

//实际底层调用链路,关键还是tryLockInnerAsync执行的lua脚本
org.redisson.RedissonLock#tryAcquire
org.redisson.RedissonLock#tryAcquireAsync
org.redisson.RedissonLock#tryLockInnerAsync
//返回值 ,当结果为nil的时候,加锁成功,否则,返回的是当前临界资源的剩余时间
//pttl命令是自动计算当前键的剩余时间的,同样是毫秒级别,-1就代表键不存在或没有设置过期时间    
<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,
                "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));
    }

long threadId = Thread.currentThread().getId();
this.id = commandExecutor.getConnectionManager().getId();
protected String getLockName(long threadId) {
        return id + ":" + threadId;
}
KEYS[1]:Collections.singletonList(getName()),临界资源(锁名称,例如字符串anyLock)
ARGV[1]:internalLockLeaseTime,到期时间,毫秒级别,代表实际临界资源锁了多久时间
ARGV[2]:getLockName(threadId),字符串:uuid:当前线程

分布式获取锁

工作过程

源码

org.redisson.RedissonLock#lock(long, java.util.concurrent.TimeUnit, boolean)
private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
        long threadId = Thread.currentThread().getId();
        //获取当前临界资源的锁失败,直接返回,获取成功就返回当前临界资源的到期时间
        Long ttl = tryAcquire(-1, leaseTime, unit, threadId);
        // lock acquired
        if (ttl == null) {
            return;
        }
        //当前临界资源订阅频道的消息,实际就是等待解锁的通知。
        RFuture<RedissonLockEntry> future = subscribe(threadId);
        //中断锁
        if (interruptibly) {
            commandExecutor.syncSubscriptionInterrupted(future);
        } else {
            commandExecutor.syncSubscription(future);
        }

        try {
            while (true) {
                //获取当前临界资源的锁失败,直接返回,获取成功就返回当前临界资源的到期时间
                ttl = tryAcquire(-1, leaseTime, unit, threadId);
                // lock acquired
                if (ttl == null) {
                    break;
                }

                // waiting for message
                //获取锁成功,续约。
                if (ttl >= 0) {
                    try {
                        future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                    } catch (InterruptedException e) {
                        if (interruptibly) {
                            throw e;
                        }
                        future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                    }
                } else {
                    
                    if (interruptibly) {
                        future.getNow().getLatch().acquire();
                    } else {
                        future.getNow().getLatch().acquireUninterruptibly();
                    }
                }
            }
        } finally {
            //获取锁成功后,退订释放锁的通知。
            unsubscribe(future, threadId);
        }
//        get(lockAsync(leaseTime, unit));
    }

分布式解锁

工作过程

1、先判断当前锁对象是否存在(hexists命令)

2、如果存在当前锁对象,执行锁重入,即根据锁对象和当前线程id找到计数器,执行重入一次-1操作。

3、如果当前锁对象的计数器大于0,可租赁续约。

4、如果当前锁对象的计数器小于0,解锁成功,即删除当前锁对象。

5、将解锁成功消息发布给所有订阅了该Redis的实例。

源码

org.redisson.RedissonLock#unlock
org.redisson.RedissonLock#unlockAsync(long)
org.redisson.RedissonLock#unlockInnerAsync
//1代表解锁成功,当前临界资源所有线程可以访问。
//0代表,锁可以重入
 protected RFuture<Boolean> unlockInnerAsync(long threadId) {
        return evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
                        "return nil;" +
                        "end; " +
                        "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
                        "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;",
                Arrays.asList(getName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId));
    }


KEYS[1]:getName(),临界资源
KEYS[2]:getChannelName() 订阅的频道
ARGV[1]:LockPubSub.UNLOCK_MESSAGE 订阅频道的值
ARGV[2]:internalLockLeaseTime 过期时间
ARGV[3]:getLockName(threadId) 字符串:uuid:当前线程id

看门狗

工作过程

   递归开启一个延迟10毫秒的任务调度执行锁对象续约到期时间

源码

org.redisson.RedissonLock#tryAcquire
org.redisson.RedissonLock#tryAcquireAsync
//加锁成功
if (ttlRemaining == null) {
       scheduleExpirationRenewal(threadId);
}
org.redisson.RedissonLock#scheduleExpirationRenewal
org.redisson.RedissonLock#renewExpiration
private void renewExpiration() {
   ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());
   if (ee == null) {
        return;
   }
   Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
        @Override
        public void run(Timeout timeout) throws Exception {
            ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());
                if (ent == null) {
                    return;
                }
                Long threadId = ent.getFirstThreadId();
                if (threadId == null) {
                    return;
                }
                //异步续租过程,这里通过返回1表示可以续租,0无须递归续租。
                RFuture<Boolean> future = renewExpirationAsync(threadId);
                future.onComplete((res, e) -> {
                    if (e != null) {
                        log.error("Can't update lock " + getName() + " expiration", e);
                        return;
                    }
                    
                    if (res) {
                        // reschedule itself
                        //递归执行,重新开启一个任务调度,延迟 internalLockLeaseTime / 3秒后                        
                        //执行TimerTask
                        renewExpiration();
                    }
                });
           }
        }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
        
        ee.setTimeout(task);
}
this.internalLockLeaseTime = commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout();
private long lockWatchdogTimeout = 30 * 1000;
internalLockLeaseTime / 3等于10毫秒

续租脚本:当前锁可重入的情况下,重新设置锁的到期时间,返回1表示可以续约。

org.redisson.RedissonLock#renewExpirationAsync
    protected RFuture<Boolean> renewExpirationAsync(long threadId) {
        return 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.singletonList(getName()),
                internalLockLeaseTime, getLockName(threadId));
    }

分布式锁原子性

1、单台机器部署是通过redis本身Reactor单线程事件驱动来保证的。

2、多台部署是通过发布订阅来保证每一台机器能够加解锁成功。其中一个Redis实例发布加解锁成功,其他Redis实例也会被通知到。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值