订单功能:生成订单后一段时间不支付订单会自动关闭。最简单的想法是设置定时任务轮询,但是每个订单的创建时间不一样,定时任务的规则无法设定,如果将定时任务执行的间隔设置的过短,太影响效率。还有一种想法,在用户进入订单界面的时候,判断时间执行相关操作。方式可能有很多

  • 在这里介绍一种监听 Redis 键值对过期时间来实现订单自动关闭


实现思路

1、在生成订单时,向 Redis 中增加一个 KV 键值对

K 为订单号,保证通过 K 能定位到数据库中的某个订单即可,V 可为任意值。

假设,生成订单时向 Redis 中存放 K 为订单号,V 也为订单号的键值对,并设置过期时间为 30 分钟,如果该键值对在 30 分钟过期后能够发送给程序一个通知,或者执行一个方法,那么即可解决订单关闭问题。

实现:通过监听 Redis 提供的过期队列来实现,监听过期队列后,如果 Redis 中某一个 KV 键值对过期了,那么将向监听者发送消息,监听者可以获取到该键值对的 K,注意,是获取不到 V 的,因为已经过期了,这就是上面所提到的,为什么要保证能通过 K 来定位到订单,而 V 为任意值即可。拿到 K 后,通过 K 定位订单,并判断其状态,如果是未支付,更新为关闭,或者取消状态即可。

2、开启 Redis key 过期提醒

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

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

实现

修改redis配置文件启动监听

  1. 1475行修改为 notify-keyspace-events "Ex"

启动redis

1、依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-redis</artifactId>
  4. </dependency>

2、相关配置

1、定义配置

在redis的相关配置类中添加监听器。
实现监听 Redis key 过期时间

  1. @Configuration
  2. public class RedisConfig {
  3. /**
  4. * 监听器
  5. *
  6. * @param connectionFactory 连接工厂
  7. * @return 监听器
  8. */
  9. @Bean
  10. RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
  11. RedisMessageListenerContainer container = new RedisMessageListenerContainer();
  12. container.setConnectionFactory(connectionFactory);
  13. return container;
  14. }
  15. }


2、定义监听器

定义监听器RedisKeyExpirationListener,继承KeyExpirationEventMessageListener 类,
查看源码发现,该接口监听所有 db 的过期事件 keyevent@*:expired”

  1. @Component
  2. public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
  3. public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
  4. super(listenerContainer);
  5. }
  6. /**
  7. * 针对 redis 失效key事件,进行数据处理
  8. * @param message 消息
  9. * @param pattern 匹配
  10. */
  11. @Override
  12. public void onMessage(Message message, byte[] pattern) {
  13. // 用户做自己的业务处理即可,注意message.toString()可以获取失效的key
  14. String expiredKey = message.toString();
  15. System.out.println(expiredKey);
  16. }
  17. }

3、使用

  1. @PostMapping("setEx")
  2. public String setEx(@RequestBody Map<String ,String> map) {
  3. redisUtils.setEx(map.get("key"),map.get("value"),5000, TimeUnit.MILLISECONDS);
  4. return "setEX";
  5. }

时间到了之后会进行删除这个key,可以根据这个key对数据库的数据进行处理