一、问题背景

前几天,在给公司测试环境部署新项目时,出现了一个问题。有一个“订单三十分钟未支付自动取消”的业务,用到了Redis的过期时间监听事件的技术。但是项目成功启动,创建完订单时,过了三十分钟,订单却迟迟没有取消。我们的开发人员在监听事件代码中打log,也一直没有看到日志。浏览了一些文章,终于解决了这个问题,做一下记录。

二、代码示例

1、创建订单接口

Controller

@RestController
@RequestMapping("/api/order")
@Api(tags = "订单相关")
public class OrderController {

    @Resource
    private OrderService orderService;

    @PostMapping("/create")
    @ApiOperation(value = "创建订单")
    public ResponseEntity<Void> createOrder(@RequestBody CreateOrderRequest request) {
		orderService.createOrderService(request)
        return ResponseEntity.ok().build();
    }

}

Service

@Transactional
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {

		// 创建订单
    public void createOrderService(CreateOrderRequest request) {
        // 校验入参是否正确

        // 判断用户是否存在,状态是否正常

        // 生成订单记录
        Order order = new Order();
        // 初始化订单状态;1待付款;2已付款;3已取消
        order.setOrderStatus(1);
        boolean flag = this.save(order);

        // 订单创建成功后,将订单ID存入Redis,并设置过期时间为5分钟
        if (flag) {
            String redisKey = "order:" + order.getId();
            redisTemplate.opsForValue().set(redisKey, order.getId(), 30, TimeUnit.MINUTES);
        }
    }

}

2、监听事件

@Component
public class OrderMonitorListener extends KeyExpirationEventMessageListener {

    @Resource
    private OrderMapper orderMapper;

    public OrderMonitorListener(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
        setKeyspaceNotificationsConfigParameter(null);
    }

    @Override
    public void onMessage(Message message, byte[] pattern) {
        String msg = message.toString();
        if (msg.contains("order:")) {
            String[] msgArray = msg.split(":");
            //查询订单
            String orderId = msgArray[1];
            Order order = orderMapper.selectById(orderId);
            if (ObjectUtils.isEmpty(order)) {
                throw new RuntimeException("订单不存在或已被删除");
            }
            //存在则修改订单状态为已取消
            order.setOrderStatus(3);
            orderMapper.updateById(order);
        }
    }

}

3、支付接口

支付接口代码不作展示,记得把创建订单时创建的RedisKey删除即可。

三、解决问题

  1. 问题根源,Redis配置文件中的过期时间通知未开启;
  2. 打开Redis配置文件,最后一行加上“notify-keyspace-events Ex”;
  3. 重启Redis即可。
docker restart redis

四、配置文件展示

protected-mode no
port 6379
timeout 0
save 900 1
save 300 10
save 60 10000
rdbcompression yes
dbfilename dump.rdb
dir /data
appendonly yes
appendfsync everysec
requirepass 123456
notify-keyspace-events Ex

notify-keyspace-events 的参数为:

字符

发送的通知

K

键空间通知,所有通知以 keyspace@ 为前缀

E

键事件通知,所有通知以 keyevent@ 为前缀

g

DEL 、 EXPIRE 、 RENAME 等类型无关的通用命令的通知

$

字符串命令的通知

l

列表命令的通知

s

集合命令的通知

h

哈希命令的通知

z

有序集合命令的通知

x

过期事件:每当有过期键被删除时发送

e

驱逐(evict)事件:每当有键因为 maxmemory 政策而被删除时发送

A

参数 g$lshzxe 的别名

看一下配置项和代码,我们就能明白,这个过期时间监听事件的原理,也是Redis那边做所有Key的轮询,然后将到过期时间的Key通过消息的方式,发送到服务端,服务端接收到消息之后,做业务的处理;这么一想,其实跟消息队列MQ没啥区别,这也是Redis能实现MQ功能的原因。

所有你想象的一切,皆是现实!