英文:
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.0的spring-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吗?
我尝试过以下操作:
- 将Jedis、Spring Boot、spring-data-redis和spring-boot-starter-data-redis依赖项更新到最新版本,以及与Spring Boot版本3兼容的版本。
- 增加连接池和超时时间。
- 删除监听器以检查应用程序是否能够正常启动(没有监听器时可以正常工作)。
- 根据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:
- 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
- Increase the connection pool and timeouts.
- Remove the listener to check if application starts to work (It worked without listener)
- 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 message
during local testing of Redis functionalities, try to comment .useSSl() in jedisConnectionFactory class.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论