php redis 怎么能无延迟处理队列?

需求是要通过服务器发送微信模板消息

目前使用的方案:
1、发送任务是先进redis队列里
2、然后crontab每分钟去执行出队列的发送任务
3、但是是每分钟去执行的,如何能无延迟去处理任务

============================分隔线==============================
补充:
首先感谢回复的亲们!

还有几个疑问想要咨询的
1、如果用脚本do.php,守护进程去处理,并通过定时任务脚本去监控这个进程,这样运行对服务器会产生多大的压力?还是可以忽略不计
2、如果采用其他的异步消息处理队列方法(主要需求是高频发送微信模板消息):
如用swool开启WebSocket,直接把任务发送至WebSocket去处理
这样的方法和采用 redis 守护进程去执行出队列的方法相比,会有哪些优点和不足的?

============================分隔线==============================
补充2:
又遇到几个问题

问题一:

在终端使用如下nohup命令,创建不了进程

nohup php /www/wwwroot/127.0.0.1/do.php &

后台切换终端目录至网站根目录下,再次运行nohup命令,创建进程成功了

cd /www/wwwroot/127.0.0.1
nohup php do.php &

不知道造成这个问题的原因是什么?
难道是do.php里面有include包含其他文件的关系吗?

问题二:

do.php脚本创建进程后,通过如下ps命令可以看到,重启服务器后,这个进程就没有了。
看了下shell语法,do.sh文件如下

#!/bin/bash
ps -fe | grep do.php | grep -v grep
if [ $? -eq 0 ]
then
    echo "runing"
else
    echo "not runing"
fi

但是一直提示如下错误

do.sh:行8: 未预期的符号 `fi' 附近有语法错误
do.sh:行8: `fi'

do.sh的语句应该没有问题啊

阅读 7.5k
9 个回答

要做到无延迟处理,就只能起一个常驻内存的 php 服务,可以写成 php 脚本,然后 nohup 启动,阻塞监听 redis 消息

代码参考

while(true) {
    $result = $redis->brpop(REDIS_KEY, 0);
    var_dump($result);
}

疑问解答:

  1. 忽略不计,除非监控脚本有问题
  2. 直接发给 socket 而不是队列,相当于把压力都给到 socket 服务
    优点: 少了一环,架构变简单,消息链路短,反馈效果好
    缺点: socket 所在服务要承担更高的压力

补充2:

  1. 使用 nohup 如果不指定日志文件,会有一个叫 nohup.out 的日志文件在执行目录,看一下报错信息
  2. 怀疑是换行符的问题,用 dos2unix 改为 Linux 换行符: dos2unix do.sh。遇到这种诡异的问题,如果确认语法没问题,可以尝试在新文件重写一次(不要复制)

一、如果是每分钟执行一下,为什么不放到mysql中呢?redis现在做的也就是数据库的作用啊!!!所以这个问题和redis的关系不大。
二、redis中有个pub/sub模式,其实是个变相的消息队列,这个基本上是可以实现你这边发送,接受者那边就会收到。
三、其实这个更多的可以用消息队列去做,比如rabbitMq等,

1、如果用脚本do.php,守护进程去处理,并通过定时任务脚本去监控这个进程,这样运行对服务器会产生多大的压力?还是可以忽略不计。

这个常驻进程,主要是通过死循环来处理,然后结合redis的brpop阻塞处理队列信息,这个进程对系统的消耗还是有,但是并不是主要消耗的来源,因为这个进程主要是监控redis队列,也可以理解成忽略不计。监控进程可以结合supervisor来处理。

2、如果采用其他的异步消息处理队列方法(主要需求是高频发送微信模板消息):
如用swool开启WebSocket,直接把任务发送至WebSocket去处理 这样的方法和采用 redis
守护进程去执行出队列的方法相比,会有哪些优点和不足的?

这个要看你们应用的场景,你高频发送模版消息,使用redis队列,应该是考虑到消息模版返回的时间比较长,通过队列的异步处理,可以避免逻辑的阻塞,影响体验。
如果是基于这种场景,使用swoole的task异步处理,也能很好的完成需求。你所说的用swoole的websocket来处理,是怎么处理?

你这种场景,无延迟处理可以用Pub/Sub机制

用这个扩展,可以帮你进一步简化
http://github.com/immusen/yii...
调用的时候直接从你的Web应用(比如xx/controllers/OrderContrller.com的某个action里面)执行如下代码:
Yii::$app->hook->runOnce('wechat/push', ['openid' =>xxx, 'template'=>'xxx','data' => [...]]);
如果你本来就一台服务器(一个实例)运行swoole,可以把runOnce()改成run()...

当然,这行代码能真正执行的前提还有,你自己去创建一个WechatController.php在指定目录下,然后里面有一个actionPush(),里面是执行推送的代码,这里也可以不重复造轮子,直接调用你在Web那边已经下好的(比如xx/utils/what::push($params)),因为是Yii的一个扩展,可以用过命名空间调框架下任意方法。
这里有个中文简介: https://www.yiichina.com/exte...

1.如果想无延迟就只能阻塞处理,但是服务器性能得不到保障。
2.降低处理频率,每分钟 改为 每秒。

Redis pub/sub了解下。

我曾经用swoole + php 来实现你这样的需求,见博文:http://blog.star7th.com/2016/...
后来用node写一个一个任务队列,从此抛弃用php实现队列的想法。https://github.com/star7th/htq
这个队列服务起来后,通过http接口来进出队列,并且支持三种队列场景,可以完美跟多种语言(包括php)一起使用

while (true) {
    $result = $queue->get();
    if ($result) {
        $data = $result->getBody();
        sleep(3); //模拟处理过程
        $queue->ack($result->getDeliveryTag());
    } else {
        sleep(3);
    }
}

最后,再搭配supervisor.
done.

我用的是rabbitMQ,上面是consumer的伪代码

看是要简单做还是要复杂做。
1.复杂做的话,可以考虑用swoole的swoole_timer_tick
2.简单做的话,你可以写一个消费脚本,consume.php,伪代码大概如下

$beginTime = time();
while(true){
    //你的业务逻辑
    $currentTime = time();
    //下面的条件是为了保证一个脚本执行大概一分钟后退出
    if($currentTime - $beginTime >= 59){
        break;
    }
}

然后我们可以用crontab分钟的粒度来执行这个脚本。因为redis取出数据就是取出,不像其他mq的服务还需要ack确认消费才能弹出队列,所以不用担心重复消费问题。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题