基於Redis的分佈式鎖RedissonLock原理剖析

RedissonLock#subscribe

訂閱鎖釋放事件,並阻塞等待鎖釋放,有效的解決了無效的鎖申請浪費資源的問題:
基於信號量,當鎖被其它資源佔用時,當前線程通過 Redis 的 channel 訂閱鎖的釋放事件,一旦鎖釋放會發消息通知待等待的線程進行競爭.

1、當 this.await 返回 false,說明等待時間已經超出獲取鎖最大等待時間,取消訂閱並返回獲取鎖失敗.
2、當 this.await 返回 true,進入循環嘗試獲取鎖.

protected final LockPubSub pubSub;

...

protected RFuture<RedissonLockEntry> subscribe(long threadId) {
    return pubSub.subscribe(getEntryName(), getChannelName());
}

entryName 格式:“id:name”;
channelName 格式:“redisson_lock__channel:{name}”;

RedissonLock#pubSub 是在RedissonLock構造函數中通過如下方式初始化的:

this.pubSub = commandExecutor.getConnectionManager().getSubscribeService().getLockPubSub();

subscribeServiceMasterSlaveConnectionManager的實現中又是通過如下方式構造的,其中this就是MasterSlaveConnectionManager實例,config則爲MasterSlaveServersConfig實例:

subscribeService = new PublishSubscribeService(this, config);

//PublishSubscribeService.java
private final LockPubSub lockPubSub = new LockPubSub(this);
private final AsyncSemaphore[] locks = new AsyncSemaphore[50];
public PublishSubscribeService(ConnectionManager connectionManager, MasterSlaveServersConfig config) {
   super();
    this.connectionManager = connectionManager;
    this.config = config;
    for (int i = 0; i < locks.length; i++) {
        locks[i] = new AsyncSemaphore(1);
    }
}

我們會發現其在初始化時,會初始化一組信號量,至於用途是什麼,我們會在後面揭曉。現在我們知道RedissonLock#pubSub是怎麼初始化的了,讓我們回到訂閱流程。

/*
* @param entryName     格式:“id:name”
* @param channelName   格式:“redisson_lock__channel:{name}”
* @return
*/
//PublishSubscribe.java
public RFuture<E> subscribe(String entryName, String channelName) {
    //代碼@1 對於同一個鎖,semaphore爲單例
    AsyncSemaphore semaphore = service.getSemaphore(new ChannelName(channelName));

    //每一個嘗試獲取鎖失敗的線程都會創建一個listenerHolder和一個newPromise,所以這裏的listenerHolder、newPromise是與當前獲取鎖的線程綁定的
    AtomicReference<Runnable> listenerHolder = new AtomicReference<>();
    RPromise<E> newPromise = new RedissonPromise<E>() {
        @Override
        public boolean cancel(boolean mayInterruptIfRunning) {
            return semaphore.remove(listenerHolder.get());
        }
    };

    //每一個嘗試獲取鎖失敗的線程都會創建一個listener
    Runnable listener = () -> {
        // entry's type is RedissonLockEntry
        E entry = entries.get(entryName);
        if (entry != null) {
            entry.acquire();
            semaphore.release();
            entry.getPromise().onComplete(new TransferListener<E>(newPromise));
            return;
        }

        // new RedissonLockEntry
        E value = createEntry(newPromise);
        value.acquire();

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

        RedisPubSubListener<Object> redisPubSubListener = createListener(channelName, value);
        service.subscribe(LongCodec.INSTANCE, channelName, semaphore, redisPubSubListener);
    };

    //首個嘗試獲取鎖失敗的線程acquire操作不會阻塞,會直接觸發執行listener.run方法
    semaphore.acquire(listener);
    listenerHolder.set(listener);
    
    return newPromise;
}

代碼@1

public AsyncSemaphore getSemaphore(ChannelName channelName) {
    return locks[Math.abs(channelName.hashCode() % locks.length)];
}

通過入參channelName的格式redisson_lock__channel:{name}我們知道,對於同一個鎖,這裏獲取的信號量是同一個。

代碼@2

public void acquire(Runnable listener) {
    acquire(listener, 1);
}

public void acquire(Runnable listener, int permits) {

    synchronized (this) {
        if (counter < permits) {
            listeners.add(new Entry(listener, permits));
            return;
        } else {
            counter -= permits;
        }
    }

    listener.run();
}

AsyncSemaphore#counter代表當前信號量允許的請求數,初始值爲1,所以對於首個嘗試獲取該鎖失敗的線程會直接觸發執行listener.run方法。而對於後續嘗試獲取該鎖失敗的線程則會創建Entry對象(保存listenerpermits的映射關係)並保存到AsyncSemaphore#listeners

嘗試獲取鎖失敗的線程會走到此流程訂閱redis通知。

  1. 假設有A、B、C、etc多個線程順序調用{@link #subscribe}方法,因爲semaphore初始信號量爲1(參見{@link PublishSubscribeService#PublishSubscribeService}方法),所以線程A可以獲得信號量(參見{@link AsyncSemaphore#acquire}方法),並執行{@code listener.run}方法,而對於之後的線程B、C、etc,其listener會被封裝成{@link AsyncSemaphore.Entry}保存在semaphore的{@link AsyncSemaphore#listeners}中。

  2. 首個嘗試獲取鎖失敗的線程{@code semaphore.acquire(listener);}操作不會阻塞,會直接觸發執行{@code listener.run}方法。這裏線程A作爲首個嘗試獲取鎖失敗的線程,會執行{@code listener.run}方法,其發現{@link PublishSubscribe#entries}中並沒有當前鎖對應的記錄,會創建一個{@link RedissonLockEntry}(參見{@link #createEntry}方法)並添加到{@link PublishSubscribe#entries}(姑且記爲 A_e),key爲"id:name"。同時會註冊監聽器redisPubSubListener。

    (1) semaphore的{@link AsyncSemaphore#release()}方法會被調用,從{@link AsyncSemaphore#listeners}中取出一個{@link AsyncSemaphore.Entry}對象,並進而調用{@link AsyncSemaphore#acquire}方法此時能夠成功獲取信號量,並執行{@code listener.run}方法,繼續調用semaphore的{@link AsyncSemaphore#release()}方法,以此類推。。。

    在上述執行{@code listener.run}方法時,{@code E entry = entries.get(entryName);}獲取到的{@link RedissonLockEntry}對象是前面線程A寫入的A_e。接着調用{@code RedissonPromise#onComplete}方法爲線程A的newPromise添加監聽,監聽器保存在{@link RedissonPromise#promise}對象的{@link DefaultPromise#listeners}中。監聽器的作用用於在線程A的newPromise({@link RedissonLockEntry#promise})完成時將其的結果同步到當前線程(線程B、C、etc)的newPromise。代碼如下:

    @Override
    public void onComplete(BiConsumer<? super T, ? super Throwable> action) {
        promise.addListener(f -> {
            if (!f.isSuccess()) {
                action.accept(null, f.cause());
                return;
            }
            
            action.accept((T) f.getNow(), null);
        });
    }
    

    (2) 監聽器的{@link BaseRedisPubSubListener#onStatus}方法被調用,標記A_e的{@link RedissonLockEntry#promise}也即線程A的newPromise完成(執行代碼:value.getPromise().trySuccess(value)),其會喚醒註冊在newPromise的{@link RedissonPromise#promise}對象的{@link DefaultPromise#listeners}中的所有監聽器,從而在線程A的newPromise({@link RedissonLockEntry#promise})完成時將其的結果同步到當前線程(線程B、C、etc)的newPromise

    上述(1)(2)所達到的效果就是同步等待(參見 RedissonLock {@code subscribeFuture.await(time, TimeUnit.MILLISECONDS)})註冊redis通知完成。

  3. redisPubSubListener監聽器在收到redis的通知時,對於{@link @channelName}的消息會調用{@link PublishSubscribe.onMessage}方法釋放一個信號量,喚醒等待的entry.getLatch().tryAcquire去再次嘗試申請鎖。

之後會通過如下方式阻塞等待訂閱redis通知完成。

if (!subscribeFuture.await(time, TimeUnit.MILLISECONDS)) {
    if (!subscribeFuture.cancel(false)) {
        subscribeFuture.onComplete((res, e) -> {
            if (e == null) {
                unsubscribe(subscribeFuture, threadId);
            }
        });
    }
    acquireFailed(threadId);
    return false;
}

如果等待超時,會通過RedissonPromise#cancel方法取消當前線程的訂閱。而cancel方法的實現如下:

RPromise<E> newPromise = new RedissonPromise<E>() {
   @Override
    public boolean cancel(boolean mayInterruptIfRunning) {
        return semaphore.remove(listenerHolder.get());
    }
};

//AsyncSemaphore.java
public boolean remove(Runnable listener) {
   synchronized (this) {
        return listeners.remove(new Entry(listener, 0));
    }
}

前面我們提到過,線程B、C、etc因爲獲取不到信號量,其listener會被封裝成{@link AsyncSemaphore.Entry}保存在semaphore的{@link AsyncSemaphore#listeners}中。這裏就是各線程將各自的listenersemaphore的{@link AsyncSemaphore#listeners}中移除。對於第一個嘗試獲取鎖失敗的線程A,其並不會被保存在listeners中,所以這裏移除會失敗,即RedissonPromise#cancel方法會返回false。進而走到下面的subscribeFuture.onComplete邏輯。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章