0

0

浅谈Redis中消息队列和延时消息队列的实现方法

青灯夜游

青灯夜游

发布时间:2021-12-10 10:02:01

|

2972人浏览过

|

来源于掘金社区

转载

redis如何实现消息队列与延时消息队列?下面本篇文章给大家介绍一下redis中消息队列和延时消息队列的实现方法,希望对大家有所帮助!

浅谈Redis中消息队列和延时消息队列的实现方法

提到redis,更多的可能想到用作缓存的用途,其实redis也可以实现一些简单的消息队列用途,我们可以使用 list 数据结构实现队列。【相关推荐:Redis视频教程

list的几个命令

lpush (left push)

由队列的左边存放进去

rpush (right push)

由队列的右边存放进去

lpop  (left pop)

由队列的左边取出来

rpop (right pop)

由队列的右边取出来

以上的四个命令,可以让 list 帮我们实现队列 或者 栈,队列的特性是先进先出,栈的特性是先进后出,

所以队列的实现可以使用 lpush + rpop 或者 rpush + lpop,

栈的实现则是lpush + lpop 或者 rpush + rpop。

1.png

使用命令演示队列

生产者发布消息

首先我们使用 rpush 对一个叫做notify-queue的队列,增加五个元素,即 1 2 3 4 5,也就是作为生产者发布消息啦

2.png

消费者消费消息

既然生产者使用的是rpush,那么消费者就要用lpop,可以看下下图,我们不停对notify-queue进行消息消费,并且是按照顺序的,从1一直到5,按顺序读出,最终队列中没有消息了,弹出的则一直是空

3.png

空轮询问题

在上面使用lpop消费消息时,可以看到,消息消费完后,我们每次再去pop时,读到的都是一个空的信息,

上面是手动执行命令,但是如果是写好的代码程序不停的去pop数据(拉取数据)的话,会造成空轮询(无用的读取),

既拉高了客户端的CPU消耗,又拉高了redis的QPS,并且还是无用操作,这些无用操作可能会造成其他客户端对redis的访问变得响应缓慢。

解决方案A (休眠)

既然空轮询会让客户端和redis的资源消耗都会变得较高,那么我们可以让客户端在收到空数据的时候,进行1s的休眠,1s后再进行数据拉取,这样可以降低消耗

Thread.sleep(1000)

这个方案也是存在瑕疵的,即消息消费延迟性增大了,如果只有一个消费者的话,延迟就是1s,即空轮询后,正好休眠了,但是这时候刚好有消息过来了,还是要等到1s醒来后才能消费,

如果有多个消费者的话,由于每个消费者的睡眠时间是岔开的,会降低一些延迟性,但是有没有办法更好的方法,可以做到几乎 0 延迟?

解决方案B (阻塞读)

redis中关于队列取数据其实还有两个命令,即阻塞读取,

blpop (blocking left pop)

brpop (blocking right pop)

阻塞读在队列没有数据的时候,会进入休眠状态,一旦有消息来了以后,则立刻做出反应,读取数据,因此使用 blpop/brpop 替换 lpop/rpop 则可以解决消息延迟性的问题,

继续入队3个属性,6、7、8

4.png

使用 blpop 进行队列的读取,最后一个参数是阻塞读的等待时间,如果超过这个时间还没有消息,将会返回nil,此时可以继续重复blpop操作,

5.png

阻塞读的空闲连接自动断开问题

客户端使用阻塞读时,如果阻塞的时间过长,服务一般会当成空闲连接,从而对其进行主动断开,减少无用的连接占用资源,这个时候客户端会抛出异常,

Vondy
Vondy

下一代AI应用平台,汇集了一流的工具/应用程序

下载

所以请注意,在客户端使用阻塞读的时候,要进行异常的捕获,从而做出相应的处理,例如重试。

java客户端实现消息队列

思路和上述一样,只是由命令行客户端redis-cli变成了java语言,一个线程或多个线程进行 rpush 的发布,

另外一个或多个线程进行 blpop 消费,完成的代码在:https://github.com/qiaomengnan16/redis-demo/tree/main/redis-queue

发布者

6.png

订阅者

7.png

延时队列的实现思路

延时队列指的是,消息发送的一段时间后,再由消费者进行消费,而不是发送过去后,消费者就能立即读取到,

zset的可以帮我们做到这个事情,首先zset可以通过score进行排序,score可以存一个时间戳,所以我们每次发布消息的时候,用当前时间戳加上延时的时间戳,

随后消费者取消息的时候,通过截取zset的数据,取到已经满足当前时间的消息(即取score小于等于当前时间戳的数据,score小于等于当前时间戳代表消息已经到时间了,如果大于的话,说明还得等一会才能消费)。

关键命令 zadd (发布者),zrangebyscore(订阅者),zrem (订阅者消费完数据后删除)

命令实现

我们使用zadd添加了4个数据,分别是1、2、3秒(伪说法,这里其实只是个score)后才能消费的数据,还有一个10秒后才能消费的kafka,

8.png

假如现在已经到了第三秒,我们取zset中大于等于1秒的和小于等于3秒的数据,因为这个区间的数据正好是我们可以消费的,可以看到,我们取出了符合条件的3条数据,

9.png

如果每次只能消费一个数据的话,可以加一个limit限制条件,可以看下图取出了第一个可以消费的数据,redis

10.png

同时注意,和list的lpop/和blpop不同(它们弹出即自动删除原始队列里的该数据),

虽然获取到了数据,但是如果不使用zrem进行删除的话,这条数据还会被其他人读到,因为他还一直存在zset中,

不过zrem可能会发生已经被别人抢先一步删除(消费)的情况,所以代码中还需要根据zrem的返回值是否大于0判断,本次消息我们是否抢占成功,成功后再进行正确消费。

代码实现

发布者

11.png

订阅者

12.png

测试延迟效果

13.png

完整代码地址:https://github.com/qiaomengnan16/redis-demo/tree/main/redis-delayed-queue

优化, 使用lua实现

上面实现的延迟队列中,有一个问题,就是使用zrem判断是否抢到这个数据时,很有可能没有抢到,这样继续进行读取,可能几轮都抢不到,资源白白浪费了,所以可以通过lua脚本,来进行优化,

让zrangebyscore 和 zrem成为一个原子化操作,这就可以避免多线程争抢,抢不到的资源浪费了。

14.png

15.png

结语

一些专业的队列中间件,应用起来会比较复杂并且增加运维成本,例如RabbitMQ,发消息前需要创建Exchange交换机,再创建Queue,随后Exchange和Queue要进行绑定,发消息的时候还要指定routing-key 才能和Exchange匹配,最终到达Queue中,

如果场景简单的话,可以使用redis实现一个队列,但是需要注意,redis没有专业队列的特性,没有ack的保证,也就是说消息是不可靠的,消费失败后,就没有了,如果需要百分百的可靠性,还是需要采用专业的队列中间件的ack等机制作为保障。

更多编程相关知识,请访问:编程入门!!

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

26

2026.03.13

Python异步编程与Asyncio高并发应用实践
Python异步编程与Asyncio高并发应用实践

本专题围绕 Python 异步编程模型展开,深入讲解 Asyncio 框架的核心原理与应用实践。内容包括事件循环机制、协程任务调度、异步 IO 处理以及并发任务管理策略。通过构建高并发网络请求与异步数据处理案例,帮助开发者掌握 Python 在高并发场景中的高效开发方法,并提升系统资源利用率与整体运行性能。

46

2026.03.12

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

178

2026.03.11

Go高并发任务调度与Goroutine池化实践
Go高并发任务调度与Goroutine池化实践

本专题围绕 Go 语言在高并发任务处理场景中的实践展开,系统讲解 Goroutine 调度模型、Channel 通信机制以及并发控制策略。内容包括任务队列设计、Goroutine 池化管理、资源限制控制以及并发任务的性能优化方法。通过实际案例演示,帮助开发者构建稳定高效的 Go 并发任务处理系统,提高系统在高负载环境下的处理能力与稳定性。

51

2026.03.10

Kotlin Android模块化架构与组件化开发实践
Kotlin Android模块化架构与组件化开发实践

本专题围绕 Kotlin 在 Android 应用开发中的架构实践展开,重点讲解模块化设计与组件化开发的实现思路。内容包括项目模块拆分策略、公共组件封装、依赖管理优化、路由通信机制以及大型项目的工程化管理方法。通过真实项目案例分析,帮助开发者构建结构清晰、易扩展且维护成本低的 Android 应用架构体系,提升团队协作效率与项目迭代速度。

92

2026.03.09

JavaScript浏览器渲染机制与前端性能优化实践
JavaScript浏览器渲染机制与前端性能优化实践

本专题围绕 JavaScript 在浏览器中的执行与渲染机制展开,系统讲解 DOM 构建、CSSOM 解析、重排与重绘原理,以及关键渲染路径优化方法。内容涵盖事件循环机制、异步任务调度、资源加载优化、代码拆分与懒加载等性能优化策略。通过真实前端项目案例,帮助开发者理解浏览器底层工作原理,并掌握提升网页加载速度与交互体验的实用技巧。

102

2026.03.06

Rust内存安全机制与所有权模型深度实践
Rust内存安全机制与所有权模型深度实践

本专题围绕 Rust 语言核心特性展开,深入讲解所有权机制、借用规则、生命周期管理以及智能指针等关键概念。通过系统级开发案例,分析内存安全保障原理与零成本抽象优势,并结合并发场景讲解 Send 与 Sync 特性实现机制。帮助开发者真正理解 Rust 的设计哲学,掌握在高性能与安全性并重场景中的工程实践能力。

227

2026.03.05

PHP高性能API设计与Laravel服务架构实践
PHP高性能API设计与Laravel服务架构实践

本专题围绕 PHP 在现代 Web 后端开发中的高性能实践展开,重点讲解基于 Laravel 框架构建可扩展 API 服务的核心方法。内容涵盖路由与中间件机制、服务容器与依赖注入、接口版本管理、缓存策略设计以及队列异步处理方案。同时结合高并发场景,深入分析性能瓶颈定位与优化思路,帮助开发者构建稳定、高效、易维护的 PHP 后端服务体系。

532

2026.03.04

AI安装教程大全
AI安装教程大全

2026最全AI工具安装教程专题:包含各版本AI绘图、AI视频、智能办公软件的本地化部署手册。全篇零基础友好,附带最新模型下载地址、一键安装脚本及常见报错修复方案。每日更新,收藏这一篇就够了,让AI安装不再报错!

171

2026.03.04

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
进程与SOCKET
进程与SOCKET

共6课时 | 0.4万人学习

Redis+MySQL数据库面试教程
Redis+MySQL数据库面试教程

共72课时 | 7.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号