Laravel 中为何不使用 blpop 取队列?

Redis 的 list 数据结构常用来做消息队列,通常使用的命令有 lpop/rpop ,还有带阻塞版的 blpop/brpop 等。blpop 的优点是避免一直轮询占用资源,而且支持多个列表作为参数并按照顺序弹出数据,如 blpop high low 30,可以更方便实现队列的优先级。

Laravel 5.3 消息队列是用的 lpop 取消息,为什么不用阻塞版的 blpop 呢?

安全队列和不安全队列

首先要了解一下安全队列和不安全队列。

什么是不安全的队列?比如客户端 lpop(统一以 lpop 为例) 从 list 中取出来的 job(任务)还没处理完进程挂掉了或者遇到了异常,由于此时服务器上已经没有副本了,这个 job 就丢失了。这种队列就是不安全的。

Laravel 正是为了保证消息队列的可靠,进程挂掉了或者处理失败还可以重试等,做了比较完善的机制,如取队列的同时把队列放入另一个集合中“暂存”起来。如代码所示,使用 lpop 取出队列,同时 zadd 到另一个集合,使用 redis lua 脚本来保证原子性。

public static function pop()
{
    return <<<'LUA'
-- Pop the first job off of the queue...
local job = redis.call('lpop', KEYS[1])
local reserved = false

if(job ~= false) then
-- Increment the attempt count and place job on the reserved queue...
reserved = cjson.decode(job)
reserved['attempts'] = reserved['attempts'] + 1
reserved = cjson.encode(reserved)
redis.call('zadd', KEYS[2], ARGV[1], reserved)
end

return {job, reserved}
LUA;
}

具体 Laravel 队列工作原理之前有一篇博文进行了整理,请参考:https://blog.tanteng.me/2017/12/laravel-qu...

为什么不用 blpop?

这里为什么不使用阻塞版本的 blpop 呢?

blpop 是阻塞版的 lpop,如果队列没有数据过来,那么在超时时间内就会一直阻塞,直到 rpush 数据到队列,有点类似 http 的长轮询。

你可能会问为何不跟 lpop 一样用 lua 脚本来处理呢?这个问题作者在 github 上有回答。(https://github.com/laravel/framework/issue...

larevel-redis-blpop.pngfile

我们知道 redis lua 脚本实际上就是事务,为了保证原子性不可能一直阻塞,它跟 MULTI/EXEC 包裹起来的 blpop 一样不进行任何阻塞操作,因此这里用 blpop 没有意义。

Redis 官方文档也有说明:

在MULTI/EXEC事务中的BLPOP

BLPOP 可以用于流水线(pipline,批量地发送多个命令并读入多个回复),但把它用在 MULTI / EXEC 块当中没有意义。因为这要求整个服务器被阻塞以保证块执行时的原子性,该行为阻止了其他客户端执行 LPUSH 或 RPUSH 命令。

因此,一个被包裹在 MULTI / EXEC 块内的 BLPOP 命令,行为表现得就像 LPOP 一样,对空列表返回 nil ,对非空列表弹出列表元素,不进行任何阻塞操作。

结论就是:通过 lua 脚本操作 blpop 和 zadd 没有意义,因为没用到阻塞的特性,或者无法保证原子性。

此外,Redis 操作 list 还有一个命令 RPOPLPUSH/BRPOPLPUSH 可以用来维护安全队列。

(原文链接:https://blog.tanteng.me/2018/08/laravel-wh...

本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由 leo 于 6年前 加精
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!