php+Redis实现延迟队列

534 阅读1分钟

应用场景

  • 订单超过30分钟未支付,自动关闭
  • 订单完成后, 如果用户一直未评价, 5天后自动好评
  • 会员到期前15天, 到期前3天分别发送短信提醒

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);