Redis 延迟队列实现(基于PHP)

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 顾名思义,延迟队列就是进入该队列的消息会被延迟消费的队列。例如:滴滴打车订单完成后,如果用户一直不评价,48小时后会将自动评价为5星。

延迟队列介绍

顾名思义,延迟队列就是进入该队列的消息会被延迟消费的队列。

例如:滴滴打车订单完成后,如果用户一直不评价,48小时后会将自动评价为5星。

Redis实现延迟队列

Redis 可以利用 zset (有序列表)来实现,将消息序列化成一个字符串作为 zsetvalue

这个消息的到期处理时间作为 score,利用多个线程轮询 zset 获取到期的任务进行处理。

多线程是为了保证可用性,万一挂了一个线程还有其他线程可以继续处理;

因为有多个线程,所以需要考虑并发争抢任务,确保任务不会多次执行

代码实现

require_once("../RedisClient.php");
$client = RedisClient::getInstance();
//延时队列
function delay(string $message, int $timeout = 5)
{
    global $client;
    $time = microtime(true) + $timeout;
    return $client->zadd('delay:', [$message => $time]);
}
//顺序消费延迟队列中的消息
function loop()
{
    global $client;
    while (true) {
        //从延迟队列获取一条最近时间的消息
        $message_data = $client->zrangebyscore('delay:', '-inf', microtime(true), ['withscores' => true, 'limit' => [0, 1]]);
        //延迟队列中无消息
        if (!$message_data) {
            sleep(1);
            continue;
        }
        //提取消息数据
        $message = key($message_data);
        //从延迟队列中删除刚获取的消息
        $success = $client->zrem('delay:', $message);
        //多线程或多进程争抢消息时,
        //根据zrem返回值判断,当前实例有没有抢到任务
        //抢到任务,做业务处理后返回
        if ($success) {
            //do something..
            echo sprintf("消费的消息,[%s]", $message) . PHP_EOL;
        }
    }
}
//delay('test1');
//delay('test2');
//delay('test2');
//loop();
# php queue.php 
消费的消息,[mmm1]
消费的消息,[mmm2]
消费的消息,[mmm3]


进一步优化

细心的同学会发现上面算法代码中,有几处问题

  • 同一个任务被多个进程取到后再使用 zrem 进行争抢,没有抢到的进程白白浪费了一次任务;
  • 取出条数和删除只能一条,且 zrangebyscorezrem 不是原子操作;
  • 消息取出后,执行了一部分逻辑,服务器突然重启了,剩下的逻辑没有执行完成该如何处理?

我们可以通过使用lua脚本,解决前面两个问题,至于第三个问题可以通过代码层面其他数据库事务解决。

require_once("../RedisClient.php");
use Predis\Command\ScriptCommand;
$client = RedisClient::getInstance();
/**
 * 从消息队列中搜索符合条件的最近n条消息
 * 返回消息内容并从消息队列中删除
 * @param string queue_key 消息队列的key
 * @param int $min      搜索时间戳开始时间
 * @param int $max      搜索时间戳结束时间
 * @param int $offset   要跳过的消息数量
 * @param int $limit    获取消息数量
 * @return array 删除成功的消息的消息内容
 * @
 */
class getAndDeleteRecentMessageScript extends ScriptCommand {
    public function getKeysCount()
    {
        return 1;
    }
    public function getScript()
    {
        return <<<LUA
-- 消息队列的redisKey
local queue = KEYS[1]
-- 搜索范围的最大/最小值,偏移量和取值数量
local min, max, offset, count = ARGV[1], ARGV[2], ARGV[3], ARGV[4] 
local message = false
local messages = {}
local queue_value = {}
local insert = table.insert
-- 获取最近n条消息并删除消息
queue_value = redis.call("ZRANGEBYSCORE",queue,min,max,"LIMIT",offset,count)
for idx, message in pairs(queue_value) do
    if redis.call("ZREM",queue,message) then
        insert(messages, idx, message)
    end
end
-- 返回删除成功的消息
return messages
LUA;
    }
}
$client->getProfile()->defineCommand('get_and_delete_recent_message','getAndDeleteRecentMessageScript');
//向延迟队列中写入10条数据
foreach(range(1,10) as $msg_id){
    $success = delay("msg{$msg_id}");
    if($success){
        echo "写入消息[msg{$msg_id}], 成功" . PHP_EOL;
    }
}
//删除最近写入的 2条
$ret = $conn->get_and_delete_recent_message('delay:',0,microtime(true),3,2);
var_export($ret);


后记

延时队列是一个实现“延时消息”的好方法,解决了业务问题。至于可达性、幂等性未来另述。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
2月前
|
缓存 NoSQL 网络安全
【Azure Redis 缓存】Azure Redis服务开启了SSL(6380端口), PHP如何访问缓存呢?
【Azure Redis 缓存】Azure Redis服务开启了SSL(6380端口), PHP如何访问缓存呢?
|
2月前
|
编解码 NoSQL Java
使用Spring Boot + Redis 队列实现视频文件上传及FFmpeg转码的技术分享
【8月更文挑战第30天】在当前的互联网应用中,视频内容的处理与分发已成为不可或缺的一部分。对于视频平台而言,高效、稳定地处理用户上传的视频文件,并对其进行转码以适应不同设备的播放需求,是提升用户体验的关键。本文将围绕使用Spring Boot结合Redis队列技术来实现视频文件上传及FFmpeg转码的过程,分享一系列技术干货。
88 3
|
26天前
|
消息中间件 NoSQL Go
PHP转Go系列 | ThinkPHP与Gin框架之Redis延时消息队列技术实践
【9月更文挑战第7天】在从 PHP 的 ThinkPHP 框架迁移到 Go 的 Gin 框架时,涉及 Redis 延时消息队列的技术实践主要包括:理解延时消息队列概念,其能在特定时间处理消息,适用于定时任务等场景;在 ThinkPHP 中使用 Redis 实现延时队列;在 Gin 中结合 Go 的 Redis 客户端库实现类似功能;Go 具有更高性能和简洁性,适合处理大量消息。迁移过程中需考虑业务需求及系统稳定性。
|
3月前
|
NoSQL Linux Redis
Redis性能优化问题之想确认Redis延迟变大是否因为fork耗时导致的,如何解决
Redis性能优化问题之想确认Redis延迟变大是否因为fork耗时导致的,如何解决
|
3月前
|
NoSQL Redis
Redis性能优化问题之为什么配置为 appendfsync everysec 的 AOF 也可能导致 Redis 延迟变大
Redis性能优化问题之为什么配置为 appendfsync everysec 的 AOF 也可能导致 Redis 延迟变大
|
3月前
|
监控 NoSQL Redis
Redis性能优化问题之配置 Redis 的自动碎片整理功能,如何解决
Redis性能优化问题之配置 Redis 的自动碎片整理功能,如何解决
|
4月前
|
NoSQL 关系型数据库 MySQL
linux服务器重启php,nginx,redis,mysql命令
linux服务器重启php,nginx,redis,mysql命令
64 1
|
NoSQL PHP Redis
Redis安装整理(window平台) +php扩展redis
window平台Redis安装 redis windows安装文件下载地址:http://code.google.com/p/servicestack/wiki/RedisWindowsDownload#Download_32bit_Cygwin_builds_for_Windows我选择的redis为最新版的安装文件,见下图:    Redis安装文件解压后,有以下几个文件。
893 0
|
21天前
|
canal 缓存 NoSQL
Redis缓存与数据库如何保证一致性?同步删除+延时双删+异步监听+多重保障方案
根据对一致性的要求程度,提出多种解决方案:同步删除、同步删除+可靠消息、延时双删、异步监听+可靠消息、多重保障方案
Redis缓存与数据库如何保证一致性?同步删除+延时双删+异步监听+多重保障方案
|
2月前
|
缓存 NoSQL Java
Redis深度解析:解锁高性能缓存的终极武器,让你的应用飞起来
【8月更文挑战第29天】本文从基本概念入手,通过实战示例、原理解析和高级使用技巧,全面讲解Redis这一高性能键值对数据库。Redis基于内存存储,支持多种数据结构,如字符串、列表和哈希表等,常用于数据库、缓存及消息队列。文中详细介绍了如何在Spring Boot项目中集成Redis,并展示了其工作原理、缓存实现方法及高级特性,如事务、发布/订阅、Lua脚本和集群等,帮助读者从入门到精通Redis,大幅提升应用性能与可扩展性。
60 0
下一篇
无影云桌面