You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot 2.7中Redis发布/订阅(PUB/SUB)在Redis未启动时启动失败问题

Fix Redis Connection Retries on Startup for Spring Boot 2.7+ Redis Pub/Sub

The issue you're facing is indeed due to a change in Spring Data Redis 2.7+, where the RedisMessageListenerContainer no longer catches connection failures during startup (as you found in the source code comparison with 2.6.x). To make your application retry connecting to Redis until it succeeds instead of failing startup, here are a few robust solutions:

Option 1: Customize Container Startup with Retry Logic (Manual Control)

This approach disables auto-startup for the container and manually handles retries once the application is ready, giving you full control over retry timing and logging.

First, modify your container bean to disable auto-start:

@Bean
public RedisMessageListenerContainer container(LettuceConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter) {
    RedisMessageListenerContainer container = new RedisMessageListenerContainer();
    container.setConnectionFactory(connectionFactory);
    container.addMessageListener(listenerAdapter, new ChannelTopic(publishChannel));
    // Disable auto-start to handle startup manually
    container.setAutoStartup(false);
    return container;
}

Then add a component to handle retry logic when the application is ready:

@Component
public class RedisContainerRetryStarter implements ApplicationListener<ApplicationReadyEvent> {

    private final RedisMessageListenerContainer container;
    private final ScheduledExecutorService retryScheduler = Executors.newSingleThreadScheduledExecutor();
    private static final long RETRY_INTERVAL_SECONDS = 3;

    public RedisContainerRetryStarter(RedisMessageListenerContainer container) {
        this.container = container;
    }

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        attemptContainerStartup();
    }

    private void attemptContainerStartup() {
        try {
            container.start();
            System.out.println("✅ Redis message listener container started successfully");
        } catch (RedisConnectionFailureException e) {
            System.err.printf("❌ Failed to start Redis container: %s. Retrying in %d seconds...%n", e.getMessage(), RETRY_INTERVAL_SECONDS);
            retryScheduler.schedule(this::attemptContainerStartup, RETRY_INTERVAL_SECONDS, TimeUnit.SECONDS);
        }
    }

    @PreDestroy
    public void cleanUp() {
        retryScheduler.shutdown();
    }
}

Option 2: Extend RedisMessageListenerContainer to Add Retries

If you prefer encapsulating the retry logic directly within the container, extend the default class and override the startup method:

public class RetryableRedisMessageListenerContainer extends RedisMessageListenerContainer {

    private static final long RETRY_DELAY_MS = 3000;
    // Use Integer.MAX_VALUE for infinite retries, or set a fixed limit
    private static final int MAX_RETRY_ATTEMPTS = Integer.MAX_VALUE;

    @Override
    protected void doStart() {
        int attemptCount = 0;
        while (attemptCount < MAX_RETRY_ATTEMPTS) {
            try {
                super.doStart();
                System.out.println("✅ Redis container started on attempt " + (attemptCount + 1));
                return;
            } catch (RedisConnectionFailureException e) {
                attemptCount++;
                System.err.printf("❌ Attempt %d failed to start Redis container: %s. Retrying in %dms...%n", 
                                  attemptCount, e.getMessage(), RETRY_DELAY_MS);
                try {
                    Thread.sleep(RETRY_DELAY_MS);
                } catch (InterruptedException interrupted) {
                    Thread.currentThread().interrupt();
                    throw new RuntimeException("Retry process interrupted", interrupted);
                }
            }
        }
        throw new RuntimeException("Failed to start Redis container after " + MAX_RETRY_ATTEMPTS + " attempts");
    }
}

Then update your bean definition to use this custom container:

@Bean
public RedisMessageListenerContainer container(LettuceConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter) {
    RetryableRedisMessageListenerContainer container = new RetryableRedisMessageListenerContainer();
    container.setConnectionFactory(connectionFactory);
    container.addMessageListener(listenerAdapter, new ChannelTopic(publishChannel));
    return container;
}

Option 3: Configure Lettuce Connection Retries (Complementary)

While this won't fix the startup failure alone, adding connection retry logic to your Lettuce factory enhances overall connection resilience. Pair it with one of the above options for full coverage:

@Bean
public LettuceConnectionFactory lettuceConnectionFactory() {
    RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration("localhost", 6379);
    
    LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
            // Configure exponential backoff retry for connection attempts
            .retryPolicy(RetryPolicy.backoff(RetryBackoffType.exponential, 
                                             Duration.ofMillis(100), 
                                             Duration.ofSeconds(5), 
                                             2))
            .build();
            
    return new LettuceConnectionFactory(redisConfig, clientConfig);
}

Which Option to Choose?

  • Option 1 is great for flexibility—you can adjust retry intervals, add alerting, or integrate with monitoring tools easily.
  • Option 2 keeps retry logic encapsulated within the container, making your configuration cleaner if you don't need extra customization.
  • Option 3 should be used alongside either 1 or 2 to handle connection drops after startup, not just initial startup failures.

内容的提问来源于stack exchange,提问作者kinglite

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 22:23:14