天天看點

聊一聊redisson及優雅實作

相信看了這個标題的同學,對這個問題以已經非常不陌生了,信手拈來redisson的幾大特性:

可重入性

【多個業務線同一時刻n條扣款,如果用setnx,我怎麼監控的加鎖解鎖線程?死鎖不得發生呐?】

redisson使用hash結構,業務名稱作為key,uuid+線程id作為field,加鎖次數作為value,這不就解決上述問題了嗎

阻塞能力

【加鎖有兩個政策,一是互斥,二是阻塞。互斥比如說定時任務,同一時刻我需要隻執行一次就行。阻塞的話-例如多個業務線同一時刻n條扣款,我不能互斥掉吧,那不得被噴死。我得寫個while死循環【标準用語就是自旋】一段時間在擷取一次,保證系統都能處理這些請求】

續約

加鎖過期時間短-加鎖邏輯還沒執行完就解鎖了是不是很尴尬,過長的話-當機恢複時間又變長

redisson預設30秒給你執行,30秒沒執行完就續約30s,當機的話恢複時間也不會太長。

原理呢就是使用了netty的時間輪實作。簡單點說就是環形數組。

直通車:

RedissonLock --> renewExpiration -->TimerTask

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;
        }

        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
                renewExpiration();
            }
        });
    }
}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);

// internalLockLeaseTime預設30s,是以這裡預設延遲10s後執行
// 其中TimerTask引用的就是netty中的TimerTask      

再看看初始化timer的代碼

protected void initTimer(MasterSlaveServersConfig config) {
    int[] timeouts = new int[]{config.getRetryInterval(), config.getTimeout()};
    Arrays.sort(timeouts);
    int minTimeout = timeouts[0];
    if (minTimeout % 100 != 0) {
        minTimeout = (minTimeout % 100) / 2;
    } else if (minTimeout == 100) {
        minTimeout = 50;
    } else {
        minTimeout = 100;
    }
    // 就是分成了1024個格子,每個格子預設100ms
    timer = new HashedWheelTimer(new DefaultThreadFactory("redisson-timer"), minTimeout, TimeUnit.MILLISECONDS, 1024, false);

    connectionWatcher = new IdleConnectionWatcher(this, config);
    subscribeService = new PublishSubscribeService(this, config);
}      

大白話講就是:例如将一個圓分成4等分,1,2,3,4,從1到2需要100ms,那麼我需要計算延遲500ms需要走幾步,下标是多少?

既然是時間總要跳動嘛,沒猜錯就是sleep😄

紅鎖

如果redis節點當機大部分節點同意擷取鎖,才能擷取到鎖。

聯鎖

全部節點加鎖成功才算加鎖成功。

  1. lua判斷鎖是否存在

    --> 不存在建立hash,目前業務key,uuid+線程id為field,value =1 ,設定過期時間并傳回

    --> 存在檢查uuid+線程id是否一緻,一緻遞增value更新過期時間傳回。存在鎖檢查uuid+線程id不一緻,判斷是否到期,沒到期我就幫你續期,自己加鎖也沒有成功,就以目前業務key釋出消息訂閱pubsub,等收到解鎖消息在重試

  1. lua判斷鎖是否存在

    -->不存在則直接釋出解鎖消息并傳回

    -->存在就檢查uuid+線程id是否一緻,不一緻就報錯,因為加鎖線程不一緻。如果一緻,就遞減value,判斷是否大于0,如果等于0,删除key并釋出解鎖消息

繼續閱讀