Redisson分布式锁分析

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

RedissonLock继承结构:

Redisson分布式锁分析_第1张图片

RLock接口中定义的方法:主要分析tryLock()实现。

Redisson分布式锁分析_第2张图片


**(一) RedissonLock#tryLock:**加锁逻辑

public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
    long time = unit.toMillis(waitTime);// 等待时间
    long current = System.currentTimeMillis();
    final long threadId = Thread.currentThread().getId();// 使用当前线程ID,实现重入锁
    Long ttl = tryAcquire(leaseTime, unit, threadId);// 尝试获取锁
    // lock acquired
    if (ttl == null) {// 没有过期时间,未上锁,直接返回获取成功
        return true;
    }
    time -= (System.currentTimeMillis() - current);
    if (time <= 0) {// 等待时间已超时
        acquireFailed(threadId);
        return false;
    }
    current = System.currentTimeMillis();
    // 订阅锁的队列,等待锁被其余线程释放后通知
    // 通过Redis的Channel订阅监听队列,subscribe内部通过信号量semaphore,再通过await方法阻塞,内部其实是用CountDownLatch来实现阻塞,获取subscribe异步执行的结果,来保证订阅成功
    final RFuture subscribeFuture = subscribe(threadId);
    if (!await(subscribeFuture, time, TimeUnit.MILLISECONDS)) {// 阻塞等待subscribe的future的结果对象(即分布式解锁消息的结果)
        if (!subscribeFuture.cancel(false)) {
            subscribeFuture.addListener(new FutureListener() {
                @Override
                public void operationComplete(Future future) throws Exception {
                    if (subscribeFuture.isSuccess()) {
                        unsubscribe(subscribeFuture, threadId);
                    }
                }
            });
        }
        acquireFailed(threadId);
        return false;
    }

    try {
        time -= (System.currentTimeMillis() - current);
        if (time <= 0) {// subscribe方法调用超时,取消订阅,不再继续申请锁
            acquireFailed(threadId);
            return false;
        }

        while (true) {
            long currentTime = System.currentTimeMillis();
            ttl = tryAcquire(leaseTime, unit, threadId);// 再次尝试申请锁
            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) {// 在ttl内,从Entry的信号量获取一个许可(除非被中断或者一直没有可用的许可)
                getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
            } else {// 在等待剩余时间内...
                getEntry(threadId).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
            }

            time -= (System.currentTimeMillis() - currentTime);
            if (time <= 0) {// 超时
                acquireFailed(threadId);
                return false;
            }
        }
    } finally {
        unsubscribe(subscribeFuture, threadId);// 无论是否获得锁,都要取消订阅解锁消息
    }
//        return get(tryLockAsync(waitTime, leaseTime, unit));
}
## RedissonLock#getEntry -> RedissonLock#getEntryName
protected String getEntryName() {
    return id + ":" + getName();// 客户端实例ID+锁名称来保证多个实例下的锁可重入
}

0x0001:**RedissonLock#tryAcquire:**尝试获取锁

private Long tryAcquire(long leaseTime, TimeUnit unit, long threadId) {
    return get(tryAcquireAsync(leaseTime, unit, threadId));
}
## RedissonObject#get
protected final CommandAsyncExecutor commandExecutor;
protected final  V get(RFuture future) {
    return commandExecutor.get(future);
}

0x0010:**RedissonLock#tryAcquireAsync:**异步尝试获取锁,异步调用,get方法阻塞

## RedissonLock#tryAcquireAsync
private  RFuture tryAcquireAsync(long leaseTime, TimeUnit unit, final long threadId) {
    if (leaseTime != -1) {// 起租时间不为空
        return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
    }
    // lockWatchdogTimeout = 30 * 1000默认30秒
    RFuture ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
    ttlRemainingFuture.addListener(new FutureListener() {
        @Override
        public void operationComplete(Future future) throws Exception {
            if (!future.isSuccess()) {
                return;
            }
            Long ttlRemaining = future.getNow();
            if (ttlRemaining == null) {// 拿到锁
                scheduleExpirationRenewal(threadId);
            }
        }
    });
    return ttlRemainingFuture;
}
## RedissonLock#scheduleExpirationRenewal Redisson避免死锁方案,避免锁未被释放。
## 若未设置过期时间的话,redission默认的过期时间是30s,同时未避免锁在业务未处理完成之前被提前释放,Redisson在获取到锁且默认过期时间的时候,会在当前客户端内部启动一个定时任务,每隔internalLockLeaseTime/3的时间去刷新key的过期时间,这样既避免了锁提前释放,同时如果客户端宕机的话,这个锁最多存活30s的时间就会自动释放
if (expirationRenewalMap.containsKey(getEntryName())) {
    return;
}
Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
    @Override
    public void run(Timeout timeout) throws Exception {
        RFuture 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.singletonList(getName()), internalLockLeaseTime, getLockName(threadId));

        future.addListener(new FutureListener() {
            @Override
            public void operationComplete(Future 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);
if (expirationRenewalMap.putIfAbsent(getEntryName(), task) != null) {
    task.cancel();
}


0x0011:**CommandAsyncService#get:**包装了CountDownLatch是为了支持线程可中断操作

public  V get(RFuture future) {
    if (!future.isDone()) {// Task还没执行完成
        final CountDownLatch l = new CountDownLatch(1);// 设置一个单线程的同步控制器
        future.addListener(new FutureListener() {
            @Override
            public void operationComplete(Future future) throws Exception {
                l.countDown();// 操作完成
            }
        });

        boolean interrupted = false;
        while (!future.isDone()) {
            try {
                l.await();
            } catch (InterruptedException e) {
                interrupted = true;
                break;
            }
        }

        if (interrupted) {
            Thread.currentThread().interrupt();
        }
    }

    // commented out due to blocking issues up to 200 ms per minute for each thread
    // future.awaitUninterruptibly();
    if (future.isSuccess()) {
        return future.getNow();
    }
    throw convertException(future);
}

0x0100:**RedissonLock#tryLockInnerAsync:**真正执行的是一段具有原子性的Lua脚本,并且最终也是由CommandAsynExecutor去执行。使用Lua的好处:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。因此在编写脚本的过程中无需担心会出现竞态条件,无需使用事务。

 RFuture tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand command) {
    internalLockLeaseTime = unit.toMillis(leaseTime);// 锁过期时间-毫秒
    // getName() - 逻辑锁名称:比如方法名+名称
    return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,"Lua脚本",Collections.singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
}
## RedissonLock#getLockName 锁对应的线程级别的名称,支持相同线程可重入,不同线程不可重入
protected String getLockName(long threadId) {
    return id + ":" + threadId;
}

    锁的实际存储类型是hash。KEY[1]->逻辑锁名称 ARGV[2]->线程级别的锁名称

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;
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]);

    分析这段Lua脚本:

  • 检查锁名称是否存在,如果不存在,获取成功,同时设置线程级别锁名称,设置过期时间为internalLockLeaseTime
  • 如果检查存在KEYS[1], ARGV[2],获取成功,自增1,记录重入的次数,更新过期时间
  • 如果key不存在,直接返回key的剩余过期时间

0x0101:**RedissonLock#acquireFailed:**把该线程从获取锁操作的等待队列中直接删掉

private void acquireFailed(long threadId) {
    get(acquireFailedAsync(threadId));
}
## RedissonFairLock#acquireFailedAsync
@Override
protected RFuture acquireFailedAsync(long threadId) {
    return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_VOID,
                "local firstThreadId = redis.call('lindex', KEYS[1], 0); " + 
                "if firstThreadId == ARGV[1] then " + 
                    "local keys = redis.call('zrange', KEYS[2], 0, -1); " + 
                    "for i = 1, #keys, 1 do " + 
                        "redis.call('zincrby', KEYS[2], -tonumber(ARGV[2]), keys[i]);" + 
                    "end;" + 
                "end;" +
                "redis.call('zrem', KEYS[2], ARGV[1]); " +
                "redis.call('lrem', KEYS[1], 0, ARGV[1]); ",
                Arrays.asList(threadsQueueName, timeoutSetName), 
                getLockName(threadId), threadWaitTime);
}


0x0110:**PublishSubscribe#subscribe:**订阅消息

final AtomicReference listenerHolder = new AtomicReference();
// 根据channelName拿到信号量,channelName=UUID+":"+name,对应一个锁
final AsyncSemaphore semaphore = subscribeService.getSemaphore(new ChannelName(channelName));
final RPromise newPromise = new RedissonPromise() {
    @Override
    public boolean cancel(boolean mayInterruptIfRunning) {
        return semaphore.remove(listenerHolder.get());
    }
};
Runnable listener = new Runnable() {
    @Override
    public void run() {
        E entry = entries.get(entryName);
        if (entry != null) {
            entry.aquire();
            semaphore.release();
            entry.getPromise().addListener(new TransferListener(newPromise));
            return;
        }

        E value = createEntry(newPromise);
        value.aquire();

        E oldValue = entries.putIfAbsent(entryName, value);
        if (oldValue != null) {
            oldValue.aquire();
            semaphore.release();
            oldValue.getPromise().addListener(new TransferListener(newPromise));
            return;
        }

        RedisPubSubListener listener = createListener(channelName, value);
        subscribeService.subscribe(LongCodec.INSTANCE, channelName, semaphore, listener);
    }
};
// 把生成的监听线程listenser加入到信号量的监听集合中去,后面发布解锁消息的时候,会唤醒
semaphore.acquire(listener);
listenerHolder.set(listener);
return newPromise;


**(二) RedissonLock#unlock:**解锁逻辑

@Override
public void unlock() {
    try {
        get(unlockAsync(Thread.currentThread().getId()));
    } catch (RedisException e) {
        ...
    }
}

0x0001:**RedissonLock#unlockAsync:**异步解锁

@Override
public RFuture unlockAsync(final long threadId) {
    final RPromise result = new RedissonPromise();
    RFuture future = unlockInnerAsync(threadId);// 此处Redis实现

    future.addListener(new FutureListener() {
        @Override
        public void operationComplete(Future future) throws Exception {
            if (!future.isSuccess()) {
                cancelExpirationRenewal(threadId);
                result.tryFailure(future.cause());
                return;
            }

            Boolean opStatus = future.getNow();
            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;
            }
            if (opStatus) {// 解锁成功之后取消更新锁expire的时间任务,针对于没有锁过期时间的
                cancelExpirationRenewal(null);
            }
            result.trySuccess(null);
        }
    });
    return result;
}

0x0010:**RedissonLock#unlockInnerAsync:**当其他线程释放锁的时候,会同时根据锁的唯一通道publish一条分布式的解锁信息,接收到分布式消息后, 等待获取锁的Semaphore中的监听队列中的listenser线程可重新申请锁。

protected RFuture unlockInnerAsync(long threadId) {
    return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,"Lua脚本",Arrays.asList(getName(), getChannelName()), LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(threadId));
}

    拿出来Lua看看如何实现的:KEY[1]->逻辑锁名称 KEY[2]->getChannelName() ARGV[1]->0L ARGV[2]->过期时间 ARGV[3]->线程级别的锁名称

if (redis.call('exists', KEYS[1]) == 0) then
	redis.call('publish', KEYS[2], ARGV[1]);
	return 1;
end;
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;

    分析这段Lua脚本:

  • 如果键不存在,说明锁可以用了,发布锁释放消息后返回
  • 如果锁不是被当前线程锁定,则返回nil
  • Redisson支持重入,在解锁的时候,引用数减1
  • 如果重入数>0,重新设置过期时间
  • 如果重入数>=0,说明锁可以使用了,发布锁释放消息后返回

**(三) LockPubSub#unlockMessage:**处理解锁消息

Redisson在LockPubSub中处理解锁消息,首先看一下LockPubSub继承结构:

Redisson分布式锁分析_第3张图片

PublishSubscribe#createListener

// 模板方法,提供给子类实现
protected abstract void onMessage(E value, Long message);

private RedisPubSubListener createListener(final String channelName, final E value) {
    RedisPubSubListener listener = new BaseRedisPubSubListener() {
        @Override
        public void onMessage(CharSequence channel, Object message) {
            if (!channelName.equals(channel.toString())) {
                return;
            }
            PublishSubscribe.this.onMessage(value, (Long)message);// 此处会调用到LockPubSub.onMessage
        }
        ...
    };
    return listener;
}

**LockPubSub:**解锁消息

public class LockPubSub extends PublishSubscribe {
    public static final Long unlockMessage = 0L;// 解锁消息,redis执行lua返回值
    public static final Long readUnlockMessage = 1L;
    @Override
    protected RedissonLockEntry createEntry(RPromise newPromise) {
        return new RedissonLockEntry(newPromise);
    }
    @Override
    protected void onMessage(RedissonLockEntry value, Long message) {
        if (message.equals(unlockMessage)) {// 解锁消息
            Runnable runnableToExecute = value.getListeners().poll();
            if (runnableToExecute != null) {
                runnableToExecute.run();
            }
			// 释放一个,唤醒等待的entry.getLatch().tryAcquire去再次尝试获取锁
            value.getLatch().release();
        } else if (message.equals(readUnlockMessage)) {
            while (true) {// 如果还有其他Listeners回调,也唤醒执行
                Runnable runnableToExecute = value.getListeners().poll();
                if (runnableToExecute == null) {
                    break;
                }
                runnableToExecute.run();
            }
            value.getLatch().release(value.getLatch().getQueueLength());
        }
    }
}

转载于:https://my.oschina.net/javamaster/blog/3006215

你可能感兴趣的:(Redisson分布式锁分析)