延时队列用途
- 订单30分钟未支付自动取消
实现方式
- 轮询mysql
- redis zset
- RabbitMQ
- 其他开源实现
mysql轮询效率低,一般不考虑使用,这里主要使用redis zset来实现
Redis Zset
原理
1.向zset中插入数据
score保存订单超时时间戳,订单如果30s后超时,将当时时间戳+30即可
value保存订单ID
2.轮询zset
根据score值范围查询【0,当前时间戳】即为要处理的订单
使用zrem删除此订单ID,若删除成功就开始处理订单超时逻辑
源码
<?php
Class DelayQueue
{
public $redis;
/**
* 连接mysql
* DelayQueue constructor.
* @throws Exception
*/
public function __construct()
{
$redis = new \Redis();
$res = $redis->connect('127.0.0.1', 6379, 5);
if ($res === false) {
throw new Exception('连接失败');
}
$this->redis = $redis;
}
/**
* 插入延时队列
* @param $key
* @param $value
* @param $score
* @throws Exception
*/
public function set($key, $value, $score)
{
$res = $this->redis->zAdd($key, ['NX'], $score, $value);
if ($res == 0) {
throw new Exception('入队列失败');
}
}
/**
* 处理延时队列
* @param $key
*/
public function deal($key)
{
while (true) {
$res = $this->redis->zRangeByScore($key, 0, time(), ['limit' => [0, 1]]);
echo '正常处理' . PHP_EOL;
if (empty($res)) {
sleep(1);
continue;
}
$value = $res[0];
$res = $this->redis->zRem($key, $value);
//在多线程处理时,只有删除成功的才有订单处理权
if ($res) {
//处理订单处理逻辑,更新订单状态,给用户发送提醒消息
var_dump(sprintf("订单【%s】30分钟未支付,已自动取消", $value));
//如果这里的任务处理失败,需要重新加入延时队列
}
}
}
}
$model = new DelayQueue();
//zset key名
$key = "order:delayqueue";
// 订单ID
$ordId = "S000001";
$model->set($key, $ordId, time() + 10);//10s之后处理
$ordId = "S000002";
$model->set($key, $ordId, time() + 30);//30s之后处理
$ordId = "S000003";
$model->set($key, $ordId, time() + 60);//60s之后处理
$model->deal($key);
运行效果
总结
优点
- 实现简单
缺点
- 轮询redis,会造成无用的IO消耗
- 可能有1s延时
- 只有单线程处理
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。