Spring Boot对接3节点Redis集群:过期事件不触发问题排查
Redis集群下key过期事件无法接收的排查与解决
针对你遇到的Redis集群(3节点)中手动发布过期事件能收到,但实际key过期无通知的问题,可按以下步骤排查解决:
1. 确保集群所有节点都配置了过期事件通知
Redis集群中,key分布在不同节点,只有key所在的节点会发送对应的过期事件。如果仅配置了单个节点,其他节点的key过期不会触发通知:
- 登录每个Redis节点,执行命令:
CONFIG SET notify-keyspace-events Ex - 修改每个节点的
redis.conf文件,添加配置:notify-keyspace-events Ex - 重启节点后,在每个节点执行
CONFIG GET notify-keyspace-events,确认返回结果均为"Ex"。
2. 检查Redis过期键删除策略
Redis默认采用惰性删除+定期删除策略:
- 惰性删除:只有当key被访问时才会检查是否过期并删除
- 定期删除:Redis每隔一段时间(由
hz参数控制,默认10次/秒)随机扫描部分key
如果key过期后未被访问,且未被定期扫描到,不会立即触发过期事件。 - 测试时可临时调高
hz参数(如改为100),提高扫描频率,修改redis.conf后重启节点(生产环境需评估CPU占用影响):hz 100
3. 修复Spring Boot的集群订阅配置
Lettuce连接Redis集群时,默认共享连接只会连接单个节点,导致无法接收其他节点的事件。需调整RedisMessageListenerContainer配置:
修改CacheConfig中的redisContainer Bean,禁用共享连接:
@Bean public RedisMessageListenerContainer redisContainer(RedisConnectionFactory connectionFactory, RedisExpirationListenerService listener) { RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(connectionFactory); // 集群环境下禁用共享连接,确保订阅所有节点的事件 if (connectionFactory instanceof LettuceConnectionFactory lettuceConnFactory) { lettuceConnFactory.setShareNativeConnection(false); } container.addMessageListener(new MessageListenerAdapter(listener), new PatternTopic("__keyevent@*__:expired")); log.info("Redis expiration listener subscribed to __keyevent@*__:expired"); return container; }
解释:禁用共享连接后,Spring会为每个节点创建独立的订阅连接,从而接收集群所有节点的过期事件。
4. 验证key的存储与过期状态
- 确认测试key的完整名称:如果使用Spring Cache,key会带有配置的前缀(如
checkout::cacheName::your-key),需用完整key测试。 - 找到key所在节点:执行
CLUSTER KEYSLOT "full-key-name"获取槽位,再执行CLUSTER GETKEYSINSLOT 槽位值 10查看该槽位的key,确认key存在。 - 检查过期时间:执行
TTL "full-key-name",返回-2表示key已被删除,此时应触发事件。
5. 修正监听器的消息解析逻辑
避免message.toString()可能存在的编码问题,改为直接解析消息体字节数组:
@Service @Slf4j public class RedisExpirationListenerService implements MessageListener { @Override public void onMessage(Message message, byte[] pattern) { String expiredKey = new String(message.getBody(), StandardCharsets.UTF_8); log.info("Redis key expired: {}", expiredKey); if (expiredKey.endsWith("_dsm")) { log.info("Triggering call to expire slot for: {}", expiredKey); //triggerApiCall(expiredKey); } } }
内容的提问来源于stack exchange,提问作者Lucifer
相关产品推荐
相关产品推荐

