最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Redis Subscribe timeout 報(bào)錯(cuò)的問題解決

 更新時(shí)間:2025年08月15日 11:04:59   作者:一樂小哥  
最近系統(tǒng)偶爾報(bào)出org.redisson.client.RedisTimeoutException: Subscribe timeout: (7500ms)的錯(cuò)誤,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

?? 介紹

Redisson版本 2.8.2

最近公司系統(tǒng)偶爾報(bào)出org.redisson.client.RedisTimeoutException: Subscribe timeout: (7500ms)的錯(cuò)誤,觀察堆棧信息看到報(bào)錯(cuò)是一段使用Redisson的redis鎖的地方,去除業(yè)務(wù)邏輯代碼基本如下

public void mockLock(String phoneNum) {
log.info("{} - prepare lock", threadName);
RLock lock = redissonClient.getLock("redis_cache_test" + phoneNum);
try {
    lock.lock();
    log.info("{} - get lock", threadName);
    //睡眠10s
    Thread.sleep(10000);
} catch (Exception e) {
    log.info("{} - exception", threadName,e);
} finally {
    log.info("{} - unlock lock", threadName);
    lock.unlock();
}

導(dǎo)致報(bào)錯(cuò)的代碼是lock.lock()的實(shí)現(xiàn)

@Override
public void syncSubscription(RFuture<?> future) {
    MasterSlaveServersConfig config = connectionManager.getConfig();
    try {
        int timeout = config.getTimeout() + config.getRetryInterval()*config.getRetryAttempts();
        if (!future.await(timeout)) {
            throw new RedisTimeoutException("Subscribe timeout: (" + timeout + "ms)");
        }
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
    future.syncUninterruptibly();
}

溯因

syncSubscription中的futureRedissonLock.subscribe(long threadId)方法

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

這里可以看出大概是在PUBSUB中獲取一個(gè)訂閱,再往下看源碼

public RFuture<E> subscribe(final String entryName, final String channelName, final ConnectionManager connectionManager) {
    //監(jiān)聽持有
    final AtomicReference<Runnable> listenerHolder = new AtomicReference<Runnable>();
    //獲取鎖訂閱隊(duì)列
    final AsyncSemaphore semaphore = connectionManager.getSemaphore(channelName);
    //訂閱拒絕實(shí)現(xiàn)
    final RPromise<E> newPromise = new PromiseDelegator<E>(connectionManager.<E>newPromise()) {
        @Override
        public boolean cancel(boolean mayInterruptIfRunning) {
            return semaphore.remove(listenerHolder.get());
        }
    };

    Runnable listener = new Runnable() {

        @Override
        public void run() {
        //判斷是否已經(jīng)存在相同的entry
            E entry = entries.get(entryName);
            if (entry != null) {
                entry.aquire();
                semaphore.release();
                entry.getPromise().addListener(new TransferListener<E>(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<E>(newPromise));
                return;
            }
            //監(jiān)聽對應(yīng)的entry
            RedisPubSubListener<Object> listener = createListener(channelName, value);
            //訂閱事件
            connectionManager.subscribe(LongCodec.INSTANCE, channelName, listener, semaphore);
        }
    };
    //用semaphore管理監(jiān)聽隊(duì)列,因?yàn)榭赡艽嬖诙鄠€(gè)線程等待一個(gè)鎖
    semaphore.acquire(listener);
    //保證訂閱拒絕邏輯
    listenerHolder.set(listener);
    
    return newPromise;
}

這里可以看到這個(gè)方法其實(shí)只是定義了一個(gè)名叫listener的Runnable, semaphore.acquire(listener);則保證了同一個(gè)channel僅會有一個(gè)線程去監(jiān)聽,其他的繼續(xù)等待,而訂閱邏輯還在connectionManager.subscribe里面

private void subscribe(final Codec codec, final String channelName, final RedisPubSubListener<?> listener, 
        final RPromise<PubSubConnectionEntry> promise, final PubSubType type, final AsyncSemaphore lock) {
    final PubSubConnectionEntry connEntry = name2PubSubConnection.get(channelName);
    if (connEntry != null) {
        connEntry.addListener(channelName, listener);
        connEntry.getSubscribeFuture(channelName, type).addListener(new FutureListener<Void>() {
            @Override
            public void operationComplete(Future<Void> future) throws Exception {
                lock.release();
                promise.trySuccess(connEntry);
            }
        });
        return;
    }

    freePubSubLock.acquire(new Runnable() {

        @Override
        public void run() {
            if (promise.isDone()) {
                return;
            }
            //如果沒有獲取到公共的連接直接返回
            final PubSubConnectionEntry freeEntry = freePubSubConnections.peek();
            if (freeEntry == null) {
                connect(codec, channelName, listener, promise, type, lock);
                return;
            }
            //entry有個(gè)計(jì)數(shù)器subscriptionsPerConnection
            如果為-1報(bào)錯(cuò)因?yàn)橄旅嬗?的判斷
            int remainFreeAmount = freeEntry.tryAcquire();
            if (remainFreeAmount == -1) {
                throw new IllegalStateException();
            }
            
            final PubSubConnectionEntry oldEntry = name2PubSubConnection.putIfAbsent(channelName, freeEntry);
            if (oldEntry != null) {
                freeEntry.release();
                freePubSubLock.release();
                
                oldEntry.addListener(channelName, listener);
                oldEntry.getSubscribeFuture(channelName, type).addListener(new FutureListener<Void>() {
                    @Override
                    public void operationComplete(Future<Void> future) throws Exception {
                        lock.release();
                        promise.trySuccess(oldEntry);
                    }
                });
                return;
            }
            //subscriptionsPerConnection為0時(shí)從公共連接池中吐出
            if (remainFreeAmount == 0) {
                freePubSubConnections.poll();
            }
            freePubSubLock.release();
            
            freeEntry.addListener(channelName, listener);
            freeEntry.getSubscribeFuture(channelName, type).addListener(new FutureListener<Void>() {
                @Override
                public void operationComplete(Future<Void> future) throws Exception {
                    lock.release();
                    promise.trySuccess(freeEntry);
                }
            });
            
            if (PubSubType.PSUBSCRIBE == type) {
                freeEntry.psubscribe(codec, channelName);
            } else {
                freeEntry.subscribe(codec, channelName);
            }
        }
        
    });
}

這里在沒有連接的情況下會進(jìn)到connect(codec, channelName, listener, promise, type, lock);中去

private void connect(final Codec codec, final String channelName, final RedisPubSubListener<?> listener,
        final RPromise<PubSubConnectionEntry> promise, final PubSubType type, final AsyncSemaphore lock) {
    final int slot = calcSlot(channelName);
    //根據(jù)subscriptionConnectionPoolSize獲取下一個(gè)鏈接
    RFuture<RedisPubSubConnection> connFuture = nextPubSubConnection(slot);
    connFuture.addListener(new FutureListener<RedisPubSubConnection>() {

        @Override
        public void operationComplete(Future<RedisPubSubConnection> future) throws Exception {
            if (!future.isSuccess()) {
                freePubSubLock.release();
                lock.release();
                promise.tryFailure(future.cause());
                return;
            }

            RedisPubSubConnection conn = future.getNow();
            
            final PubSubConnectionEntry entry = new PubSubConnectionEntry(conn, config.getSubscriptionsPerConnection());
            entry.tryAcquire();
            
            final PubSubConnectionEntry oldEntry = name2PubSubConnection.putIfAbsent(channelName, entry);
            if (oldEntry != null) {
                releaseSubscribeConnection(slot, entry);
                
                freePubSubLock.release();
                
                oldEntry.addListener(channelName, listener);
                oldEntry.getSubscribeFuture(channelName, type).addListener(new FutureListener<Void>() {
                    @Override
                    public void operationComplete(Future<Void> future) throws Exception {
                        lock.release();
                        promise.trySuccess(oldEntry);
                    }
                });
                return;
            }
            
            freePubSubConnections.add(entry);
            freePubSubLock.release();
            
            entry.addListener(channelName, listener);
            entry.getSubscribeFuture(channelName, type).addListener(new FutureListener<Void>() {
                @Override
                public void operationComplete(Future<Void> future) throws Exception {
                    lock.release();
                    promise.trySuccess(entry);
                }
            });
            
            if (PubSubType.PSUBSCRIBE == type) {
                entry.psubscribe(codec, channelName);
            } else {
                entry.subscribe(codec, channelName);
            }
            
        }
    });
}

這里的RFuture<RedisPubSubConnection> connFuture = nextPubSubConnection(slot);最終會調(diào)用ClientConnectionsEntry#acquireSubscribeConnection方法的
freeSubscribeConnectionsCounter.acquire(runnable) 至此我們找到原因 當(dāng)同時(shí)等待鎖訂閱消息達(dá)到subscriptionConnectionPoolSize*subscriptionsPerConnection個(gè)時(shí),再多一個(gè)訂閱消息,連接一直無法獲取導(dǎo)致MasterSlaveConnectionManager中的freePubSubLock沒有釋放。 另外由于在超時(shí)場景下MasterSlaveConnectionManager向連接池獲取連接后是直接緩存下來,不把分發(fā)訂閱鏈接釋返回給連接池的,因此導(dǎo)致freeSubscribeConnectionsCounter一直等待,出現(xiàn)死鎖情況。

最終表現(xiàn)就是org.redisson.client.RedisTimeoutException: Subscribe timeout: (7500ms)

復(fù)現(xiàn)

Redis配置

public RedissonClient redissonClient(RedisConfig redisConfig) {

    Config config = new Config();
    config.useSingleServer()
        .setAddress(redisConfig.getHost() + ":" + redisConfig.getPort())
        .setPassword(redisConfig.getPassword())
        .setDatabase(redisConfig.getDatabase())
        .setConnectTimeout(redisConfig.getConnectionTimeout())
        .setTimeout(redisConfig.getTimeout())
        //把兩個(gè)配置項(xiàng)設(shè)置為1
        .setSubscriptionConnectionPoolSize(1)
        .setSubscriptionsPerConnection(1);
    return Redisson.create(config);
}

測試方法

void contextLoads() throws InterruptedException {
    Runnable runnable = () -> {
        redissonLock.tryRedissonLock();
    };
    new Thread(runnable, "線程1").start();
    new Thread(runnable, "線程12").start();
    new Thread(runnable, "線程23").start();
    new Thread(runnable, "線程21").start();
    
    Thread.sleep(200000);
}

結(jié)果

org.redisson.client.RedisTimeoutException: Subscribe timeout: (5500ms)
	at org.redisson.command.CommandAsyncService.syncSubscription(CommandAsyncService.java:126) ~[redisson-2.8.2.jar:na]
	at org.redisson.RedissonLock.lockInterruptibly(RedissonLock.java:121) ~[redisson-2.8.2.jar:na]
	at org.redisson.RedissonLock.lockInterruptibly(RedissonLock.java:108) ~[redisson-2.8.2.jar:na]
	at org.redisson.RedissonLock.lock(RedissonLock.java:90) ~[redisson-2.8.2.jar:na]
	at com.rick.redislock.lock.RedissonLock.registerPersonalMember(RedissonLock.java:30) ~[classes/:na]
	at com.rick.redislock.RedisLockApplicationTests.lambda$contextLoads$0(RedisLockApplicationTests.java:15) [test-classes/:na]
	at java.lang.Thread.run(Thread.java:748) ~[na:1.8.0_144]

符合預(yù)期

到此這篇關(guān)于Redis Subscribe timeout 報(bào)錯(cuò)的問題解決的文章就介紹到這了,更多相關(guān)Redis Subscribe timeout 報(bào)錯(cuò)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 使用redis管理用戶登錄會話的方法

    使用redis管理用戶登錄會話的方法

    今天小編就為大家分享一篇使用redis管理用戶登錄會話的方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-05-05
  • redis.config配置文件

    redis.config配置文件

    在使用Redis時(shí),我們通常需要對Redis進(jìn)行一些配置,以確保其能夠正常運(yùn)行并滿足我們的需求,本文主要介紹了redis.config配置文件,感興趣的可以了解一下
    2023-11-11
  • Redis實(shí)現(xiàn)短信登錄的企業(yè)實(shí)戰(zhàn)

    Redis實(shí)現(xiàn)短信登錄的企業(yè)實(shí)戰(zhàn)

    本文主要介紹了Redis實(shí)現(xiàn)短信登錄的企業(yè)實(shí)戰(zhàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07
  • Redis 數(shù)值范圍查詢(Numeric Range Queries)的實(shí)現(xiàn)

    Redis 數(shù)值范圍查詢(Numeric Range Queries)的實(shí)現(xiàn)

    本文主要介紹了Redis 數(shù)值范圍查詢(Numeric Range Queries)的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2025-11-11
  • 聊一聊redis奇葩數(shù)據(jù)類型與集群知識

    聊一聊redis奇葩數(shù)據(jù)類型與集群知識

    現(xiàn)在越來越多的項(xiàng)目都會利用到redis,多實(shí)例redis服務(wù)比單實(shí)例要復(fù)雜的多,這里面涉及到定位、容錯(cuò)、擴(kuò)容等技術(shù)問題,下面這篇文章主要給大家介紹了關(guān)于redis奇葩數(shù)據(jù)類型與集群知識的相關(guān)資料,需要的朋友可以參考下
    2022-01-01
  • redis啟動停止,查看redis端口實(shí)現(xiàn)方式

    redis啟動停止,查看redis端口實(shí)現(xiàn)方式

    文章主要講述了如何查看Redis進(jìn)程、停止Redis以及使用配置文件啟動Redis集群和Sentinel哨兵的方法,并提到可以通過兩種方法后臺啟動Redis服務(wù),作者表示這些是個(gè)人經(jīng)驗(yàn),希望能為大家提供參考
    2026-05-05
  • redis實(shí)現(xiàn)排行榜的簡單方法

    redis實(shí)現(xiàn)排行榜的簡單方法

    這篇文章主要給大家介紹了關(guān)于redis實(shí)現(xiàn)排行榜的簡單方法,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用redis具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-08-08
  • Redis安全高效刪除包含特定模式的所有鍵值對的完整方案

    Redis安全高效刪除包含特定模式的所有鍵值對的完整方案

    隨著業(yè)務(wù)的發(fā)展,數(shù)據(jù)庫中往往會積累大量臨時(shí)數(shù)據(jù)或過期的鍵值對,特別是那些包含特定時(shí)間戳或模式的鍵,然而,錯(cuò)誤的刪除操作可能帶來災(zāi)難性后果,本文將深入探討如何在Redis中安全、高效地刪除包含特定模式的所有鍵值對,,需要的朋友可以參考下
    2026-01-01
  • SpringBoot整合Redis入門之緩存數(shù)據(jù)的方法

    SpringBoot整合Redis入門之緩存數(shù)據(jù)的方法

    Redis是一個(gè)開源的使用ANSI C語言編寫、支持網(wǎng)絡(luò)、可基于內(nèi)存亦可持久化的日志型、Key-Value數(shù)據(jù)庫,并提供多種語言的API,下面通過本文給大家介紹下SpringBoot整合Redis入門之緩存數(shù)據(jù)的相關(guān)知識,感興趣的朋友一起看看吧
    2021-11-11
  • 基于Redis實(shí)現(xiàn)附近商鋪查詢功能

    基于Redis實(shí)現(xiàn)附近商鋪查詢功能

    這篇文章主要介紹了基于Redis實(shí)現(xiàn)-附近商鋪查詢功能,這個(gè)功能將使用到Redis中的GEO這種數(shù)據(jù)結(jié)構(gòu)來實(shí)現(xiàn),需要的朋友可以參考下
    2025-05-05

最新評論

涟水县| 徐汇区| 全州县| 峨边| 石棉县| 林芝县| 瓮安县| 牟定县| 博乐市| 巨鹿县| 法库县| 萨嘎县| 曲阳县| 曲沃县| 贵德县| 汽车| 林西县| 固原市| 石河子市| 苗栗县| 榆中县| 濮阳县| 诸暨市| 宜兰县| 开封县| 阿荣旗| 禄丰县| 阿拉善右旗| 微山县| 汽车| 随州市| 文水县| 赫章县| 铜鼓县| 晋中市| 儋州市| 郑州市| 白沙| 吴旗县| 陈巴尔虎旗| 尚义县|