2

延时队列用途

  • 订单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);

运行效果

image.png

总结

优点

  • 实现简单

缺点

  • 轮询redis,会造成无用的IO消耗
  • 可能有1s延时
  • 只有单线程处理

小小的太阳
123 声望7 粉丝

reloading...