监听Redis Key事件

507 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

1.使用场景

处理订单过期自动取消,比如下单30分钟未支付自动更改订单状态. 注意:只能适用于单机redis, 较优方案:Mq、时间轮询

2.使用

开启 redis 的事件监听与发布

修改redis相关事件配置。

  • 找到redis配置文件redis.conf
  • 修改配置文件redis.conf中的:notify-keyspace-events Ex,默认为notify-keyspace-events ""
  • 查看“notify-keyspace-events”的配置项,如果没有,添加“notify-keyspace-events Ex”,如果有值,添加Ex,

Springboot集成

  • 依赖
<!-- redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
  • 定义配置RedisListenerConfig
@Configuration
public class RedisListenerConfig {
    @Bean
    RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
 
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        //container.addMessageListener(new RedisExpiredListener(), new PatternTopic("__keyevent@0__:expired"));
        return container;
    }
}
  • 定义监听器,实现KeyExpirationEventMessageListener接口,该接口监听所有db的过期事件keyevent@*:expired"
@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
    private final static Logger logger = LoggerFactory.getLogger(RedisKeyExpirationListener.class);
    @Autowired
    private RedisClient redisClient;
    /**
     * Creates new {@link MessageListener} for {@code __keyevent@*__:expired} messages.
     *
     * @param listenerContainer must not be {@literal null}.
     */
    public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
    }
 
    @Override
    public void onMessage(Message message, byte[] pattern) {
        // 用户做自己的业务处理即可,注意message.toString()可以获取失效的key
        String expiredKey = message.toString();
        if(expiredKey.startsWith("zeus:order")){
            //TODO
        }
    }
}

或者打开RedisListenerConfig中container.addMessageListener(new RedisExpiredListener(), new PatternTopic("keyevent@0:expired")); 注释, 再定义监听器,监控__keyevent@0__:expired事件,即db0过期事件,可以自己定义监控什么事件。


public class RedisExpiredListener implements MessageListener {
 
    /**
     * 客户端监听订阅的topic,当有消息的时候,会触发该方法;
     * 并不能得到value, 只能得到key。
     * 姑且理解为: redis服务在key失效时(或失效后)通知到java服务某个key失效了, 那么在java中不可能得到这个redis-key对应的redis-value。
     *      * 解决方案:
     *  创建copy/shadow key, 例如 set vkey "vergilyn"; 对应copykey: set copykey:vkey "" ex 10;
     *  真正的key是"vkey"(业务中使用), 失效触发key是"copykey:vkey"(其value为空字符为了减少内存空间消耗)。
     *  当"copykey:vkey"触发失效时, 从"vkey"得到失效时的值, 并在逻辑处理完后"del vkey"
     *
     * 缺陷:
     *  1: 存在多余的key; (copykey/shadowkey)
     *  2: 不严谨, 假设copykey在 12:00:00失效, 通知在12:10:00收到, 这间隔的10min内程序修改了key, 得到的并不是 失效时的value.
     *  (第1点影响不大; 第2点貌似redis本身的Pub/Sub就不是严谨的, 失效后还存在value的修改, 应该在设计/逻辑上杜绝)
     *  当"copykey:vkey"触发失效时, 从"vkey"得到失效时的值, 并在逻辑处理完后"del vkey"
     *
     */
    @Override
    public void onMessage(Message message, byte[] bytes) {
        byte[] body = message.getBody();// 建议使用: valueSerializer
        byte[] channel = message.getChannel();
        System.out.print("onMessage >> " );
        System.out.println(String.format("channel: %s, body: %s, bytes: %s"
                ,new String(channel), new String(body), new String(bytes)));
    }
 
}