将”spring-data-redis”从2.6.2迁移到3.1。无法添加/连接消息监听器。

huangapple go评论58阅读模式
英文:

Migrating spring-data-redis from 2.6.2 to 3.1. Unable to add/connect Message Listener

问题

在将Spring Boot从版本2.6.2迁移到3.1的过程中,我还将spring-data-redis从2.6.2升级到3.1,并遇到了消息监听器无法启动的问题。异常本身并没有提供太多信息,只是说:

在上下文初始化期间遇到异常 - 取消刷新尝试: org.springframework.context.ApplicationContextException: Failed to start bean 'jedisMessageListenerContainer'"

jedisMessageListenerContainer看起来如下所示:

@Bean
RedisMessageListenerContainer jedisMessageListenerContainer(JedisConnectionFactory jedisConnectionFactory,
                                                            RedisKeyExpirationListener redisListener) {
    RedisMessageListenerContainer container = new RedisMessageListenerContainer();
    container.setConnectionFactory(jedisConnectionFactory);
    container.addMessageListener(redisListener, new PatternTopic("__keyevent@0__:expired"));
    container.setErrorHandler(e -> log.error("There was an error in redis key expiration listener container", e));
    return container;
}

RedisKeyExpirationListener是一个实现了spring-data-redis中的MessageListener接口的类。

经过一些调试,我发现RedisMessageListenerContainer只是捕获了TimeoutException,然后抛出了:

IllegalStateException("Subscription registration timeout exceeded", e);

稍后这个异常消息被Bean处理器捕获并解析为"Failed to start bean"异常消息。

我尝试增加了容器的MaxSubscriptionRegistrationWaitingTime(最多等待60秒),但结果只是导致了更长的等待时间,同样的异常仍然发生。


我使用消息监听器与Jedis(版本4.4.2,但也检查了4.3.2,应该兼容)作为连接工厂,并且使用版本为3.1.0spring-boot-starter-data-redis。以下是连接工厂的实现:

@Bean
public JedisPoolConfig jedisPoolConfig() {
    var jedisPoolConfig = new JedisPoolConfig();
    jedisPoolConfig.setMaxTotal(10);
    jedisPoolConfig.setMinIdle(0);
    jedisPoolConfig.setMaxIdle(8);
    return jedisPoolConfig;
}

@Bean
public JedisConnectionFactory jedisConnectionFactory(JedisPoolConfig jedisPoolConfig) {

    var jedisClientConfiguration = JedisClientConfiguration.builder()
            .usePooling()
            .poolConfig(jedisPoolConfig)
            .and()
            .readTimeout(Duration.ofMillis(1000L))
            .useSsl()
            .build();

作为附加测试,我刚刚删除了Redis容器上的addMessageListener调用,应用程序能够正常与Redis一起工作。问题只在我将Message Listener添加到RedisMessageListenerContainer时才开始出现。

而且,当将Spring版本降回2.6.2并与spring-data-redis一起使用时,Message Listener可以正常工作,我可以看到过期事件被处理。

有人可以解释为什么会发生这种情况以及为什么我无法初始化这些bean吗?

我尝试过以下操作:

  1. 将Jedis、Spring Boot、spring-data-redis和spring-boot-starter-data-redis依赖项更新到最新版本,以及与Spring Boot版本3兼容的版本。
  2. 增加连接池和超时时间。
  3. 删除监听器以检查应用程序是否能够正常启动(没有监听器时可以正常工作)。
  4. 根据spring-data-redis的文档更改实现,如下所示:
@Bean
RedisKeyExpirationListener redisListener() {
    return new RedisKeyExpirationListener();
}

@Bean
MessageListenerAdapter redisListenerAdapter(RedisKeyExpirationListener redisListener) {
    return new MessageListenerAdapter(redisListener, "handleMessage");
}

@Bean
RedisMessageListenerContainer jedisMessageListenerContainer(JedisConnectionFactory jedisConnectionFactory,
                                                            MessageListenerAdapter redisListenerAdapter) {
    RedisMessageListenerContainer container = new RedisMessageListenerContainer();
    container.setConnectionFactory(jedisConnectionFactory);
    container.addMessageListener(redisListenerAdapter, new PatternTopic("__keyevent@0__:expired"));
    container.setErrorHandler(e -> log.error("There was an error in redis key expiration listener container", e));
    return container;
}

其中RedisKeyExpirationListener现在实现了自己的接口:

public interface MessageDelegate {

    void handleMessage(String message);

    void handleMessage(Message message, byte[] pattern);
}

而不是实现spring-data-redis中的MessageListener接口。

英文:

During migration Spring Boot from version 2.6.2 to 3.1, I was switching also spring-data-redis from 2.6.2 to 3.1 and have encountered problem that Message Listener bean can not be started anymore. Exception itself does not describe too much as it only says that:

Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Failed to start bean 'jedisMessageListenerContainer'","logger_name":"org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext"}

Where jedisMessageListenerContainer looks as following:

    @Bean
    RedisMessageListenerContainer jedisMessageListenerContainer(JedisConnectionFactory jedisConnectionFactory,
                                                                RedisKeyExpirationListener redisListener) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(jedisConnectionFactory);
        container.addMessageListener(redisListener, new PatternTopic(
                "__keyevent@0__:expired"));
        container.setErrorHandler(e -> log.error("There was an error in redis key expiration listener container", e));
        return container;
    }

RedisKeyExpirationListener is a class that implements MessageListener interface from the spring-data-redis

After some debugging I have find out only that RedisMessageListenerContainer is catching TimeoutException and then throwing

IllegalStateException("Subscription registration timeout exceeded", e);

Which is later being caught by Bean Processor and parsed into Failed to start bean exception message.

I have tried to increase the MaxSubscriptionRegistrationWaitingTime for the container (up to 60 seconds) but it just resulted in longer waiting time for same exception.


I am using the Message Listener together with Jedis (version 4.4.2 but also checked with 4.3.2 which should be compatible) as the connection factory and spring-boot-starter-data-redis with version 3.1.0.
Here is implementation of the connection factory:

    @Bean
    public JedisPoolConfig jedisPoolConfig() {
        var jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(10);
        jedisPoolConfig.setMinIdle(0);
        jedisPoolConfig.setMaxIdle(8);
        return jedisPoolConfig;
    }

    @Bean
    public JedisConnectionFactory jedisConnectionFactory(JedisPoolConfig jedisPoolConfig) {

        var jedisClientConfiguration = JedisClientConfiguration.builder()
                .usePooling()
                .poolConfig(jedisPoolConfig)
                .and()
                .readTimeout(Duration.ofMillis(1000L))
                .useSsl()
                .build();

As additional test I have just removed the addMessageListener invocation on the Redis container and application was able to work with the Redis itself. The problem only starts to appear when I add the Message Listener to the RedisMessageListenerContainer.

Also when bringing back spring to version 2.6.2 together with spring-data-redis Message Listener works and I can see that expired events are being handled.

Can someone help why this happening and why I cannot initialize the beans once want to add this listener?

I tried to:

  1. Update the Jedis, Spring Boot, spring-data-redis and spring-boot-starter-data-redis dependencies to the newest as also to one that are compatible with Spring Boot version 3
  2. Increase the connection pool and timeouts.
  3. Remove the listener to check if application starts to work (It worked without listener)
  4. Change the implementation according to the spring-data-redis documentation to the following one:
    @Bean
    RedisKeyExpirationListener redisListener() {
        return new RedisKeyExpirationListener();
    }

    @Bean
    MessageListenerAdapter redisListenerAdapter(RedisKeyExpirationListener redisListener) {
        return new MessageListenerAdapter(redisListener, "handleMessage");
    }

    @Bean
    RedisMessageListenerContainer jedisMessageListenerContainer(JedisConnectionFactory jedisConnectionFactory,
                                                                MessageListenerAdapter redisListenerAdapter) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(jedisConnectionFactory);
        container.addMessageListener(redisListenerAdapter, new PatternTopic(
                "__keyevent@0__:expired"));
        container.setErrorHandler(e -> log.error("There was an error in redis key expiration listener container", e));
        return container;
    }

where now RedisKeyExpirationListener does implement own interface:

public interface MessageDelegate {

    void handleMessage(String message);

    void handleMessage(Message message, byte[] pattern);
}

instead implementing MessageListener interface from the spring-data-redis

答案1

得分: 0

如果在本地测试Redis功能时遇到Failed to start bean exception message错误,请尝试在jedisConnectionFactory类中注释掉**.useSSL()**。

英文:

If you are expierencing Failed to start bean exception messageduring local testing of Redis functionalities, try to comment .useSSl() in jedisConnectionFactory class.

huangapple
  • 本文由 发表于 2023年6月15日 18:42:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/76481694.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定