1. 项目背景与核心思路
电商系统中订单自动取消是个经典场景。当用户下单后未支付,系统需要在指定时间后自动取消订单释放库存。传统方案是定时轮询数据库,但这种方式存在性能瓶颈。Redis的键空间通知(Keyspace Notifications)特性提供了更优雅的解决方案。
核心原理是利用Redis的过期键事件通知机制:
- 创建订单时,在Redis设置一个带过期时间的键
- 当键过期时,Redis会发布通知事件
- PHP服务订阅这些事件,触发订单取消逻辑
这种方案相比传统轮询有三大优势:
- 零延迟:精确到毫秒级的触发
- 低开销:无需持续查询数据库
- 高可靠:基于Redis的持久化机制
2. 环境准备与配置
2.1 Redis服务配置
首先需要确保Redis版本≥2.8,然后修改redis.conf:
# 默认配置是关闭的 notify-keyspace-events "" # 修改为启用过期事件通知 notify-keyspace-events "Ex"配置说明:
- E:启用键事件通知
- x:特别启用过期事件通知
Windows系统修改redis.windows.conf后,需要重启服务:
redis-server --service-stop redis-server --service-start2.2 PHP环境准备
需要安装phpredis扩展:
pecl install redis然后在php.ini中添加:
extension=redis.so3. 核心代码实现
3.1 Redis操作类封装
class RedisOrderService { private $redis; private $dbNumber = 15; // 建议使用独立数据库 public function __construct() { $this->redis = new Redis(); $this->redis->connect('127.0.0.1', 6379); $this->redis->select($this->dbNumber); } public function setOrderTimeout($orderSn, $minutes) { $key = "order_timeout:$orderSn"; // 设置键和过期时间(单位秒) return $this->redis->setex($key, $minutes * 60, $orderSn); } }3.2 订单服务集成
class OrderService { public function createOrder($orderData) { // 1. 数据库入库 $orderSn = $this->saveToDB($orderData); // 2. 设置Redis过期监听 $redis = new RedisOrderService(); $redis->setOrderTimeout($orderSn, 30); // 30分钟后过期 return $orderSn; } }3.3 事件订阅服务
创建subscribe.php作为常驻进程:
<?php ini_set('default_socket_timeout', -1); // 防止超时断开 $redis = new Redis(); $redis->connect('127.0.0.1', 6379); $redis->select(15); // 与设置时相同的DB // 设置永不超时 $redis->setOption(Redis::OPT_READ_TIMEOUT, -1); // 订阅过期事件 $redis->psubscribe(['__keyevent@15__:expired'], function($redis, $pattern, $channel, $msg) { if (strpos($msg, 'order_timeout:') === 0) { $orderSn = substr($msg, 13); // 处理订单取消逻辑 $this->cancelOrder($orderSn); } }); function cancelOrder($orderSn) { // 1. 检查订单状态 $order = $this->getOrderFromDB($orderSn); // 2. 如果未支付则取消 if ($order['status'] == 'unpaid') { $this->updateOrderStatus($orderSn, 'cancelled'); // 3. 释放库存等后续操作 $this->releaseInventory($order); } }4. 生产环境部署方案
4.1 进程守护方案
使用Supervisor管理订阅进程:
[program:order_timeout] command=php /path/to/subscribe.php autostart=true autorestart=true user=www-data numprocs=1 redirect_stderr=true stdout_logfile=/var/log/order_timeout.log4.2 高可用方案
- 多机部署:在不同服务器部署多个订阅进程
- 心跳检测:增加健康检查机制
- 异常重启:配置合理的重启策略
5. 性能优化与注意事项
5.1 性能优化点
- 批量处理:合并短时间内的多个订单取消操作
- 连接复用:使用连接池管理Redis连接
- 异步处理:耗时操作放入队列异步执行
5.2 常见问题排查
事件未触发:
- 检查Redis配置是否正确
- 确认键是否真的过期(用TTL命令检查)
重复处理:
- 实现幂等处理逻辑
- 添加处理状态标记
数据库连接超时:
// 在回调结束时主动关闭连接 $db->close();
6. 替代方案对比
| 方案 | 精度 | 性能影响 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| Redis过期通知 | 秒级 | 低 | 中 | 实时性要求高 |
| 定时任务扫描 | 分钟级 | 高 | 低 | 简单场景 |
| 消息队列延迟 | 毫秒级 | 中 | 高 | 复杂业务流 |
7. 扩展应用场景
- 优惠券自动过期
- 拍卖超时处理
- 预约超时释放
- 临时锁自动释放
在实际项目中,我们使用这套方案处理日均10万+订单的自动取消,相比原来的定时任务方案,服务器负载降低了60%。关键点在于:
- 使用独立的Redis数据库隔离事件
- 完善的异常处理和日志记录
- 订阅进程的无缝重启机制