Skip to content

Redisson Unable to acquire subscription lock 异常

🏷️ Redisson

今天在使用 Redisson 的 RedissonDelayedQueue 功能时,项目启动报了如下错误:

txt
Caused by: org.redisson.client.RedisTimeoutException: Unable to acquire subscription lock after 2ms. Try to increase 'subscriptionTimeout', 'subscriptionsPerConnection', 'subscriptionConnectionPoolSize' parameters.
    at org.redisson.pubsub.PublishSubscribeService.lambda$timeout$12(PublishSubscribeService.java:328)
    at io.netty.util.HashedWheelTimer$HashedWheelTimeout.run(HashedWheelTimer.java:715)
    at io.netty.util.concurrent.ImmediateExecutor.execute(ImmediateExecutor.java:34)
    at io.netty.util.HashedWheelTimer$HashedWheelTimeout.expire(HashedWheelTimer.java:703)
    at io.netty.util.HashedWheelTimer$HashedWheelBucket.expireTimeouts(HashedWheelTimer.java:790)
    at io.netty.util.HashedWheelTimer$Worker.run(HashedWheelTimer.java:503)
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
    at java.base/java.lang.Thread.run(Thread.java:833)

提示去修改 subscriptionTimeout 参数,但是这个参数默认是 7500 ms,完全够用的。

仔细查看了 Redisson 的源码,果然发现了一些奇怪的地方。

java
private CompletableFuture<PubSubConnectionEntry> subscribe(PubSubType type, Codec codec, ChannelName channelName,
                                                            MasterSlaveEntry entry, ClientConnectionsEntry clientEntry, RedisPubSubListener<?>... listeners) {
    CompletableFuture<PubSubConnectionEntry> promise = new CompletableFuture<>();
    AsyncSemaphore lock = getSemaphore(channelName);
    int timeout = config.getSubscriptionTimeout();
    long start = System.nanoTime();
    Timeout lockTimeout = connectionManager.getServiceManager().newTimeout(t -> {
        promise.completeExceptionally(new RedisTimeoutException(
                "Unable to acquire subscription lock after " + timeout + "ms. " +
                        "Try to increase 'timeout', 'subscriptionsPerConnection', 'subscriptionConnectionPoolSize' parameters."));
    }, timeout, TimeUnit.MILLISECONDS);
    lock.acquire().thenAccept(r -> {
        if (!lockTimeout.cancel() || promise.isDone()) {
            lock.release();
            return;
        }

        long newTimeout = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
        subscribeNoTimeout(codec, channelName, entry, clientEntry, promise, type, lock, new AtomicInteger(), listeners);
        timeout(promise, newTimeout);
    });
    return promise;
}

第 18 行计算的超时时间 newTimeout 大概率会非常小,很容易导致超时的发生。去源码仓库看了下这个文件,发现在 3.24.2 版本(当前使用的版本是 3.24.1)及之后,这行代码被修改为了如下形式。

java
long newTimeout = timeout - TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);

综上,只需要将 Redisson 升级到 3.24.2 及以上的版本就可以解决这个问题了。