【redis学习篇】Redisson实现的分布式独占锁核心流程剖析

一、Redisson分布式锁锁竞争流程

【redis学习篇】Redisson实现的分布式独占锁核心流程剖析_第1张图片

二、加锁核心源码剖析

2.1 lockInterruptibly方法

@Override
public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException {
    long threadId = Thread.currentThread().getId();
    // 核心加锁流程
    Long ttl = tryAcquire(leaseTime, unit, threadId);
    // 加锁成功 返回
    if (ttl == null) {
        return;
    }
	
	// 订阅解锁消息
    RFuture<RedissonLockEntry> future = subscribe(threadId);
    commandExecutor.syncSubscription(future);

	// 加锁失败自旋
    try {
        while (true) {
            ttl = tryAcquire(leaseTime, unit, threadId);
            // lock acquired
            if (ttl == null) {
                break;
            }

            // waiting for message
            if (ttl >= 0) {
                getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
            } else {
                getEntry(threadId).getLatch().acquire();
            }
        }
    } finally {
        unsubscribe(future, threadId);
    }
//        get(lockAsync(leaseTime, unit));
}

2.2 tryAcquireAsync方法

private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, final long threadId) {
    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;
}

2.3 tryLockInnerAsync(核心加锁逻辑)

<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 " + // 判断key是否已经存在,如果不存在则直接加锁操作
                    "redis.call('hincrby', KEYS[1], ARGV[2], 1); " + // 给key的value自增1(核心加锁原理)
                    "redis.call('pexpire', KEYS[1], ARGV[1]); " +  // 设置锁超时时间
                    "return nil; " + // 加锁成功返回null
                    "end; " +
                    // 加锁的key存在,判断该key存储的value是否是当前请求加锁的线程id,如果是,执行锁重入的操作
                    "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +  
                    "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +  // value自增1
                    "redis.call('pexpire', KEYS[1], ARGV[1]); " +  // 刷新过期时间
                    "return nil; " +
                    "end; " +
                    "return redis.call('pttl', KEYS[1]);", // 加锁失败,返回锁的过期时间
            Collections.singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
}

2.4 定时刷新锁超时时间 - 看门狗

private void scheduleExpirationRenewal(final long threadId) {
    if (expirationRenewalMap.containsKey(getEntryName())) {
        return;
    }

    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.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()) {
                        // reschedule itself
                        scheduleExpirationRenewal(threadId);
                    }
                }
            });
        }
    }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);// 定时任务何时执行 ? 锁超时时间 / 3 (看门狗核心)

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

三、解锁核心源码剖析

3.1 unlockAsync方法

    @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();
    }

//        Future future = unlockAsync();
//        future.awaitUninterruptibly();
//        if (future.isSuccess()) {
//            return;
//        }
//        if (future.cause() instanceof IllegalMonitorStateException) {
//            throw (IllegalMonitorStateException)future.cause();
//        }
//        throw commandExecutor.convertException(future);
}

核心解锁代码

protected RFuture<Boolean> unlockInnerAsync(long threadId) {
    return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
     		// 判断锁是否存在,如果不存在,则发布一条解锁的消息 --> 返回1
            "if (redis.call('exists', KEYS[1]) == 0) then " +
                "redis.call('publish', KEYS[2], ARGV[1]); " +
                "return 1; " +
            "end;" +
            // 判断当前线程加的这把锁是否存在(value 是否 = getLockName(threadId)),不存在则直接返回null
            "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
                "return nil;" +
            "end; " +
            //  // 加锁的时候+1 ,这边解锁需要减一
            "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
            // 判断减一以后是否还是>0,如果是则代表存在锁重入 --> 刷新锁超时时间 --> 返回 0
            "if (counter > 0) then " +
                "redis.call('pexpire', KEYS[1], ARGV[2]); " +
                "return 0; " +
            "else " +
            	// 否则,执行删除锁操作,并且发布一条解锁的消息 --> 返回1
                "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));

}

我们可以看到,在前面加锁的时候其实已经subscribe了这个channel,所以这边解锁publish了这个解锁的消息,之前创建的监听任务就可以监听到这个解锁消息 「这里就不对这块流程说明了,无非是发布订阅的机制,我们核心是要掌握一整个加锁/解锁,阻塞/唤醒的机制」接下来我们来看看,监听到消息后的处理

@Override
protected void onMessage(RedissonLockEntry value, Long message) {
	// 判断订阅到消息是否是解锁消息
    if (message.equals(unlockMessage)) {
    	// 唤醒之前加锁失败阻塞在第同步队列的的线程
        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;
            }
        }
    }
}
}

你可能感兴趣的:(Redis,redis,学习,分布式,java)