Spring Boot 2.7中Redis发布/订阅(PUB/SUB)在Redis未启动时启动失败问题
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

