一、问题背景
前几天,在给公司测试环境部署新项目时,出现了一个问题。有一个“订单三十分钟未支付自动取消”的业务,用到了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删除即可。
三、解决问题
- 问题根源,Redis配置文件中的过期时间通知未开启;
- 打开Redis配置文件,最后一行加上“notify-keyspace-events Ex”;
- 重启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功能的原因。
所有你想象的一切,皆是现实!