PHP轻量级延时队列任务实现以及可能存在的问题点

4 篇文章 1 订阅
3 篇文章 0 订阅
<?php

$redis = new Redis;
$redis->connect('127.0.0.1');
$key = "order:delayqueue";

function generateOrderNo()
{
    return '订单号:' . mt_rand(100000, 999999);
}


// 一秒后执行
$redis->zAdd($key, ['NX'], time() + 1, json_encode(['orderNo' => generateOrderNo(), 'delay' => 1]));
// 两秒后执行
$redis->zAdd($key, ['NX'], time() + 2, json_encode(['orderNo' => generateOrderNo(), 'delay' => 2]));
// 三秒后执行
$redis->zAdd($key, ['NX'], time() + 3, json_encode(['orderNo' => generateOrderNo(), 'delay' => 3]));
// 在这一秒同时写入了三条,这是一个并发点,如果出现上万条数据的情况可能会在 zRevRangeByScore 的时候把内存打爆
$redis->zAdd($key, ['NX'], time() + 4, json_encode(['orderNo' => generateOrderNo(), 'delay' => 4]));
$redis->zAdd($key, ['NX'], time() + 4, json_encode(['orderNo' => generateOrderNo(), 'delay' => 4]));
$redis->zAdd($key, ['NX'], time() + 4, json_encode(['orderNo' => generateOrderNo(), 'delay' => 4]));


// 着重要改造的地方
//while (true) {
//    // 当小于现在的数据都拿出来消费掉
//    $rows = $redis->zRevRangeByScore($key, time(), 0);
//    sleep(1); // 这里可以适当放开
//
//    // 这里拿到所有的数据循环进行处理,但是这里有问题,数据过大可能导致此处内存爆掉
//    foreach ($rows as $row) {
//        $result = json_decode($row, true);
//        echo "当前数据被消费了:订单号【{$result['orderNo']}】,延时{$result['delay']}秒后执行的" . PHP_EOL;
//        $redis->zRem($key, $row);
//    }
//}


// 1.改造方案一,单条获取执行,问题是并发条件要处理的记录过多的时候,会阻塞进程
while (true) {
    // 我们每次只获取一条数据来执行
    $rows = $redis->zRevRangeByScore($key, time(), 0, ['limit' => [0, 1]]);
    sleep(1); // 这里可以适当放开

    $row = $rows[0];
    // 这里拿到所有的数据循环进行处理,但是这里有问题,数据过大可能导致此处内存爆掉
    $result = json_decode($row, true);
    echo "当前数据被消费了:订单号【{$result['orderNo']}】,延时{$result['delay']}秒后执行的" . PHP_EOL;
    $redis->zRem($key, $row);
}


// 2.第二种方案,利用Shell或者其他语言唤起多个PHP消费进程,有点多此一举,同时会存在资源争抢的问题,所以
// zAdd的时候要设置第二个参数 ['NX']key不存在的时候才设置,对资源进行加锁


3.第三种方案,换语言实现,还是通过Redis来处理,比如Golang,大概思路是一样的,只不过在循环的时候可能阻塞的地方
使用Go关键字开启多个协程并发消费,不过同时也要注意锁的问题

假设taskChannel是redis zRevRangeByScore拿出来的数组集合,一次性有1000条,这种情况下如果单条任务出现拥塞会导致后面任务延时,严重堆积的时候业务就会出现问题,那么下面是golang的解决方案

func main() {
    taskChannel := make(chan int, 1000)
	resultChannel := make(chan int, 1000)
	closeChannel := make(chan bool, 10)

  //这里我们模拟一下,一次性从redis中取出了1000条数据
	go func() {
		for i := 0; i < 1000; i++ {
            taskChannel <- i
		}
		close(taskChannel)
	}()

    // 开辟十个协程去消费数据
	for i := 0; i < 10; i++ {
        go Process(taskChannel, resultChannel, closeChannel)
	}

	for i := 0; i < 10; i++ {
        <-closeChannel
	}
	close(closeChannel)
	close(resultChannel)

	for result := range resultChannel {
        fmt.Println(result)
	}
}

func Process(taskChannel chan int, resultChannel chan int, closeChannel chan bool) {
    for task := range taskChannel {
        // todo 在这里处理task的数据写入到结果中
        resultChannel <- task
	 }
	closeChannel <- true
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值