当前位置:   article > 正文

Springboot 监听redis key的过期事件_springboot监听redis过期key

springboot监听redis过期key

需求:

处理订单过期自动取消,比如下单30分钟未支付自动更改订单状态

解决方案1:

可以利用redis天然的key自动过期机制,下单时将订单id写入redis,过期时间30分钟,30分钟后检查订单状态,如果未支付,则进行处理但是key过期了redis有通知吗?答案是肯定的。

开启redis key过期提醒

修改redis相关事件配置。找到redis配置文件redis.conf,查看“notify-keyspace-events”的配置项,如果没有,添加“notify-keyspace-events Ex”,如果有值,添加Ex,相关参数说明如下:

 

  1. K:keyspace事件,事件以__keyspace@<db>__为前缀进行发布;
  2. E:keyevent事件,事件以__keyevent@<db>__为前缀进行发布;
  3. g:一般性的,非特定类型的命令,比如del,expire,rename等;
  4. $:字符串特定命令;
  5. l:列表特定命令;
  6. s:集合特定命令;
  7. h:哈希特定命令;
  8. z:有序集合特定命令;
  9. x:过期事件,当某个键过期并删除时会产生该事件;
  10. e:驱逐事件,当某个键因maxmemore策略而被删除时,产生该事件;
  11. A:g$lshzxe的别名,因此”AKE”意味着所有事件。

redis测试:

打开一个redis-cli ,监控db0的key过期事件

 

  1. 127.0.0.1:6379> PSUBSCRIBE __keyevent@0__:expired
  2. Reading messages... (press Ctrl-C to quit)
  3. 1) "psubscribe"
  4. 2) "__keyevent@0__:expired"
  5. 3) (integer) 1

打开另一个redis-cli ,发送定时过期key

 

127.0.0.1:6379> setex test_key 3 test_value

观察上一个redis-cli ,会发现收到了过期的keytest_key,但是无法收到过期的value test_value

 

  1. 127.0.0.1:6379> PSUBSCRIBE __keyevent@0__:expired
  2. Reading messages... (press Ctrl-C to quit)
  3. 1) "psubscribe"
  4. 2) "__keyevent@0__:expired"
  5. 3) (integer) 1
  6. 1) "pmessage"
  7. 2) "__keyevent@0__:expired"
  8. 3) "__keyevent@0__:expired"
  9. 4) "test_key"

在springboot中使用

  • 1.pom 中添加依赖

 

  1. <!-- redis -->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-data-redis</artifactId>
  5. </dependency>
  • 2.定义配置RedisListenerConfig

 

  1. import edu.zut.ding.listener.RedisExpiredListener;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.data.redis.connection.RedisConnectionFactory;
  5. import org.springframework.data.redis.listener.PatternTopic;
  6. import org.springframework.data.redis.listener.RedisMessageListenerContainer;
  7. /**
  8. * @Author lsm
  9. * @Date 2018/10/27 20:56
  10. */
  11. @Configuration
  12. public class RedisListenerConfig {
  13. @Bean
  14. RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
  15. RedisMessageListenerContainer container = new RedisMessageListenerContainer();
  16. container.setConnectionFactory(connectionFactory);
  17. // container.addMessageListener(new RedisExpiredListener(), new PatternTopic("__keyevent@0__:expired"));
  18. return container;
  19. }
  20. }
  • 3.定义监听器,实现KeyExpirationEventMessageListener接口,查看源码发现,该接口监听所有db的过期事件keyevent@*:expired"

 

  1. import edu.zut.ding.constants.SystemConstant;
  2. import edu.zut.ding.enums.OrderState;
  3. import edu.zut.ding.service.OrderService;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.data.redis.connection.Message;
  6. import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
  7. import org.springframework.data.redis.listener.RedisMessageListenerContainer;
  8. import org.springframework.stereotype.Component;
  9. /**
  10. * 监听所有db的过期事件__keyevent@*__:expired"
  11. * @author lsm
  12. */
  13. @Component
  14. public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
  15. public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
  16. super(listenerContainer);
  17. }
  18. /**
  19. * 针对redis数据失效事件,进行数据处理
  20. * @param message
  21. * @param pattern
  22. */
  23. @Override
  24. public void onMessage(Message message, byte[] pattern) {
  25. // 用户做自己的业务处理即可,注意message.toString()可以获取失效的key
  26. String expiredKey = message.toString();
  27. if(expiredKey.startsWith("Order:")){
  28. //如果是Order:开头的key,进行处理
  29. }
  30. }
  31. }
  • 或者打开RedisListenerConfigcontainer.addMessageListener(new RedisExpiredListener(), new PatternTopic("__keyevent@0__:expired")); 注释,再定义监听器,监控__keyevent@0__:expired事件,即db0过期事件。这个地方定义的比较灵活,可以自己定义监控什么事件。

 

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

解决方案2

使用spring + quartz定时任务(支持任务信息写入mysql,多节点分布式执行任务),下单成功后,生成一个30分钟后运行的任务,30分钟后检查订单状态,如果未支付,则进行处理

解决方案3

将订单过期时间信息写入mysql,按分钟轮询查询mysql,如果超时则进行处理,效率差!时间精准度底!

解决方案4

使用Java的定时器,不支持高可用,设置定时器的节点挂掉或者重启,任务失效!

结论

推荐使用方案1和方案2

参考:https://spring.io/guides/gs/messaging-redis/

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/685114
推荐阅读
相关标签
  

闽ICP备14008679号