在捣鼓swoole结合redis做队列时碰到阻塞队列与无阻塞取数的疑惑,遂找了很多资料,发现很多想当然的PHPer的写法都是想当然的,进而导致阻塞和不阻塞没什么两样,直到看到下面这篇文章的用法,如醍醐灌顶……
经测试在PHP和swoole中完全支持这种写法。
要点如下:
关于阻塞的正确理解:
1、阻塞,指的是brpop语句会阻塞住程序,不让其继续往下执行,像这里如果队列集为空的时候,就会占着不执行下一条语句了;
2、可以不用if判断是否返回值为真了,因为brpop命令如果拿不到数据就会一直占着等,直到取出有效数据返回;
3、有他阻塞占着也不用再实时用力地猛轮询了,比如占半个小时,那我们完全可以半个小时到若干个小时触发一次任务执行(看任务量与任务处理时长),因为你一旦有新任务执行的话占坑阻塞时间就后延了
//redis的php版参考用法
while(true) {
$result = $redis->brpop('lists:msg', 1800); //阻塞1800秒如果取不到数据的话,0的话是一直阻塞
echo "我又被执行了"; //做个语句执行标记
$id = $result[1]; //返回值是一个数组
//处理数据
echo "I am here $id \n";
}
20190320补充,经过测试发现一个问题,就是如果你是一直阻塞下去不停止的话上面的写法没问题,但如果设定了阻塞时间的话类似上面的1800秒,那么到时间之后他会返回一个空数组(array(0) { }),还是会执行下边的echo命令,也就是说,如果要想在无结果时不执行相关命令的话就要加一个判断//如果返回结果不为空则执行
if(!empty($result)){...}
,一直阻塞则不受影响。
其它的为转载
redis研究(十五)—任务队列
在网站开发中,当页面需要进行如发送邮件、复杂数据运算等耗时较长的操作时会阻塞页面的渲染。为了避免用户等待太久,应该使用独立的线程来完成这类操作。不过一些编程语言或框架不易实现多线程,这时很容易就会想到通过其他进程来实现。设想有一个进程能够完成发邮件的功能,那么在页面中只需要想办法通知这个进程向指定的地址发送邮件就可以了。
通知的过程可以借助任务队列来实现。任务队列顾名思义,就是“传递任务的队列”。与任务队列进行交互的实体有两类,一类是生产者(producer),一类是消费者(consumer)。生产者会将需要处理的任务放入任务队列中,而消费者则不断地从任务队列中读入任务信息并执行。
对于发邮件这个操作来说页面程序就是生产者,而发邮件的进程就是消费者。当需要发送邮件时,页面程序会将收件地址、邮件主题和邮件正文组装成一个任务后存入任务队列中。同时发邮件的进程会不断检查任务队列,一旦发现有新的任务便会将其从队列中取出并执行。由此实现了进程间的通信。
使用任务队列有如下好处:
(1)松耦合。生产者和消费者无需知道彼此的实现细节,只需要约定好任务的描述格式。这使得生产者和消费者可以由不同的团队使用不同的编程语言编写。
(2)易于扩展消费者可以有多个,而且可以分布在不同的服务器中,借此可以轻易地降低单台服务器的负载。
$redis->brpop的正确用法就参考下面的使用Redis实现任务队列
Redis的列表类型,使用LPUSH和RPOP命令实现队列。如果要实现任务队列,只需要让生产者将任务使用LPUSH命令加入到某个键中,另一边让消费者不断地使用RPOP命令从该键中取出任务即可。
在上面例子中,完成发邮件的任务需要知道收件地址、邮件主题和邮件正文。所以生产者需要将这三个信息组成对象并序列化成字符串,然后将其加入到任务队列中。而消费者则循环从队列中拉取任务,就像如下伪代码:
#无限循环读取任务队列中的内容
loop
$task=RPOR queue
if $task
#如果任务队列中有任务则执行它
execute($task)
else
#如果没有则等待1秒以免过于频繁地请求数据
wait 1 second
到此一个使用Redis实现的简单的任务队列就写好了。不过还有一点不完美的地方:当任务队列中没有任务时消费者每秒都会调用一次RPOP命令查看是否有新任务。如果可以实现一旦有新任务加入任务队列就通知消费者就好了。其实借助 BRPOP 命令就可以实现这样的需求。
BRPOP命令和RPOP命令相似,唯一的区别是当列表中没有元素时BRPOP命令会一直阻塞住连接,直到有新元素加入。如上段代码可改写为:
loop
#如果任务队列中没有新任务,BRPOP命令会一直阻塞,不会执行execute()。
$task=BRPOP queue, 0
#返回值是一个数组(见下介绍),数组第二个元素是我们需要的任务。
execute($task[1])
BRPOP命令接收两个参数,第一个是键名,第二个是超时时间,单位是秒。当超过了此时间仍然没有获得新元素的话就会返回nil。上例中超时时间为“0”,表示不限制等待的时间,即如果没有新元素加入列表就会永远阻塞下去。
当获得一个元素后BRPOP命令返回两个值,分别是键名和元素值。为了测试BRPOP命令,我们可以打开两个redis-cli实例,在实例A中:
redis A>BRPOP queue 0
键入回车后实例1会处于阻塞状态,这时在实例B中向queue中加入一个元素:
redis B>LPUSH queue task
(integer) 1
在LPUSH命令执行后实例A马上就返回了结果:
1) "queue"
2) "task"
同时会发现queue中的元素已经被取走:
redis>LLEN queue
(integer) 0
除了BRPOP命令外,Redis还提供了BLPOP,和BRPOP的区别在与从队列取元素时BLPOP会从队列左边取。
Redis优先级队列
当发送确认邮件和发送通知邮件(可以延迟)两种任务同时存在时,应该优先执行前者。为了实现这一目的,我们需要实现一个优先级队列。
BRPOP命令可以同时接收多个键,其完整的命令格式为BLPOP key [key …]timeout ,如BLPOP queue:1 queue:2 0。意义是同时检测多个键,如果所有键都没有元素则阻塞,如果其中有一个键有元素则会从该键中弹出元素。例如,打开两个redis-cli实例,在实例A中:
redis A>BLPOP queue:1 queue:2 queue:3 0
在实例B中:
redis B>LPUSH queue:2 task
(integer) 1
则实例A中会返回:
1) "queue:2"
2) "task"
如果多个键都有元素则按照从左到右的顺序取第一个键中的一个元素。我们先在queue:2和queue:3中各加入一个元素:
redis>LPUSH queue:2 task1
1) (integer) 1
redis>LPUSH queue:3 task2
2) integer) 1
然后执行BRPOP命令:
redis>BRPOP queue:1 queue:2 queue:3 0
1) "queue:2"
2) "task1"
借此特性可以实现区分优先级的任务队列。我们分别使用queue:confirmation.email和queue:notification.email两个键存储发送确认邮件和发送通知邮件两种任务,然后将消费者的代码改为:
loop
$task =
BRPOP queue:confirmation.email,
queue:notification.email,
0
execute(task[1])
这时一旦发送确认邮件的任务被加入到queue:confirmation.email队列中,无论queue:notification.email还有多少任务,消费者都会优先完成发送确认邮件的任务。
文章参考https://blog.csdn.net/wtyvhreal/article/details/43112735