skip to Main Content

I have this configuration for my pub/sub implementation:

@Bean
public RedisMessageListenerContainer container(LettuceConnectionFactory connectionFactory,
                                               MessageListenerAdapter listenerAdapter) {

    RedisMessageListenerContainer container = new RedisMessageListenerContainer();
    container.setConnectionFactory(connectionFactory);
    container.addMessageListener(listenerAdapter, new ChannelTopic(publishChannel));
    return container;
}

@Bean
public MessageListenerAdapter listenerAdapter(RedisReceiver receiver) {
    return new MessageListenerAdapter(receiver, "receiveMessage");
}

@Bean
public StringRedisTemplate template(LettuceConnectionFactory connectionFactory) {
    return new StringRedisTemplate(connectionFactory);
}

This code worked fine until I updated to Spring-Boot 2.7 (previously 2.6.7).
Now this code throws the following error on startup, when my Redis is not running:

Exception encountered during context initialization – cancelling refresh attempt: org.springframework.context.ApplicationContextException: Failed to start bean ‘container‘; nested exception is org.springframework.data.redis.RedisConnectionFailureException: Unable to connect to Redis; nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to localhost/:6379

("container" is the Bean at the top in my code snippet)

Where or how can I configure that it catches the thrown exception on startup and just retries it again until the connection to Redis is available?

UPDATE:

Did some digging and this part of the code is the culprit:

https://github.com/spring-projects/spring-data-redis/blob/main/src/main/java/org/springframework/data/redis/listener/RedisMessageListenerContainer.java#L1169

In comparison to the 2.6.x branch, where this was inside a try/catch (not sure though as it seems to be a completely different implementation), it is missing in version 2.7.

2

Answers


  1. I think you have to wait for the next release, someone has fixed it and just merged into maim

    Login or Signup to reply.
  2. It looks like the PR didn’t fix the issue in the latest release (2.7.1), it really "works as intended" to Spring team :

    Here’s my workaround:

    RedisMessageListenerContainer.Subscriber#initialize(...) method is called when adding listener to the container, so what you need is to make sure the redis connection is available first, and then add the listener to container.

    @AllArgsConstructor
    public class MessageListenerSubscriber implements ApplicationListener<ApplicationReadyEvent> {
    
      private RedisConnectionFactory connectionFactory;
      private RedisMessageListenerContainer container;
      private MessageListener listener;
      private Collection<? extends Topic> topics;
    
      @Async
      @Override
      public void onApplicationEvent(ApplicationReadyEvent event) {
        var template = new RetryTemplateBuilder()
          .maxAttempts(Integer.MAX_VALUE)
          .fixedBackoff(5000)
          .build();
        template.execute(context -> {
          try {
            var connection = connectionFactory.getConnection();
            if (connection.isSubscribed()) {
              log.debug("Retrieved connection is already subscribed; aborting listening");
              return null;
            }
          } catch (Exception e) {
            log.error("Connection failure occurred. Restarting subscription task after 5000 ms");
            throw e;
          }
          this.container.addMessageListener(listener, topics);
          log.debug("Listeners registered successfully after {} retries.", context.getRetryCount());
          return null;
        });
      }
    }
    

    and the configuration looks like:

    @Bean
    public RedisMessageListenerContainer container(LettuceConnectionFactory connectionFactory) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        return container;
    }
    
    @Bean
    public MessageListenerAdapter listenerAdapter(RedisReceiver receiver) {
        return new MessageListenerAdapter(receiver, "receiveMessage");
    }
    
    @Bean
    public MessageListenerSubscriber messageListenerSubscriber(
        RedisConnectionFactory connectionFactory,
        RedisMessageListenerContainer container,
        MessageListenerAdapter listener){
      return new MessageListenerSubscriber(
        connectionFactory,
        container,
        listener,
        List.of(new ChannelTopic(publishChannel))
      );
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search