Redis 核心技术与实战
蒋德钧
中科院计算所副研究员
81696 人已学习
新⼈⾸单¥68
登录后,你可以任选4讲全文学习
课程目录
已完结/共 53 讲
开篇词 (1讲)
实践篇 (28讲)
Redis 核心技术与实战
15
15
1.0x
00:00/00:00
登录|注册

15 | 消息队列的考验:Redis有哪些解决方案?

XPENDING和XACK命令
消费组形式的消息读取
XREAD和XREADGROUP命令
XADD命令
消息可靠性保证:BRPOPLPUSH命令
BRPOP命令
LPUSH和RPOP命令
消息可靠性保证
处理重复的消息
消息保序
Redis是否适合做消息队列的讨论
List和Streams实现消息队列的特点和区别
三大需求转换为消息队列的三大要求
基于Streams的消息队列解决方案
基于List的消息队列解决方案
消息队列的消息存取需求
每课一问
小结
Redis作为消息队列的解决方案
分布式系统必备基础软件:消息队列
互联网应用基本采用分布式系统架构设计
消息队列的考验:Redis有哪些解决方案?

该思维导图由 AI 生成,仅供参考

你好,我是蒋德钧。
现在的互联网应用基本上都是采用分布式系统架构进行设计的,而很多分布式系统必备的一个基础软件就是消息队列。
消息队列要能支持组件通信消息的快速读写,而 Redis 本身支持数据的高速访问,正好可以满足消息队列的读写性能需求。不过,除了性能,消息队列还有其他的要求,所以,很多人都很关心一个问题:“Redis 适合做消息队列吗?”
其实,这个问题的背后,隐含着两方面的核心问题:
消息队列的消息存取需求是什么?
Redis 如何实现消息队列的需求?
这节课,我们就来聊一聊消息队列的特征和 Redis 提供的消息队列方案。只有把这两方面的知识和实践经验串连起来,才能彻底理解基于 Redis 实现消息队列的技术实践。以后当你需要为分布式系统组件做消息队列选型时,就可以根据组件通信量和消息通信速度的要求,选择出适合的 Redis 消息队列方案了。
我们先来看下第一个问题:消息队列的消息读取有什么样的需求?

消息队列的消息存取需求

我先介绍一下消息队列存取消息的过程。在分布式系统中,当两个组件要基于消息队列进行通信时,一个组件会把要处理的数据以消息的形式传递给消息队列,然后,这个组件就可以继续执行其他操作了;远端的另一个组件从消息队列中把消息读取出来,再在本地进行处理。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

Redis作为消息队列的解决方案,能够满足消息保序、处理重复消息和消息可靠性保证的需求。在分布式系统中,消息队列的作用是支持组件间的快速通信,通过Redis的List和Streams两种数据类型实现消息队列的需求。消息队列的需求包括消息保序、处理重复消息和消息可靠性保证,而Redis能够满足这些需求。通过Redis实现消息队列,可以满足分布式系统组件通信量和消息通信速度的要求,为读者提供了选择适合的Redis消息队列方案的技术实践。 基于List的消息队列解决方案使用LPUSH和RPOP命令实现消息的写入和读取,但存在消费者不停调用RPOP命令导致性能损失的问题。为解决这一问题,Redis提供了BRPOP命令,实现阻塞式读取,节省CPU开销。另外,消费者程序需要对重复消息进行判断,保证消息的幂等性。List类型通过BRPOPLPUSH命令实现消息的可靠性保证,将消息插入备份List以便消费者程序重启后重新处理。 然而,List类型无法支持消费组的实现,导致消息积压给Redis内存带来压力。为解决这一问题,Redis 5.0版本引入了Streams数据类型,支持消息队列的三大需求,并且提供了消费组形式的消息读取。Streams数据类型的引入为解决List类型的不足提供了更合适的解决方案。 Streams是Redis专门为消息队列设计的数据类型,它提供了丰富的消息队列操作命令,如XADD、XREAD、XREADGROUP、XPENDING和XACK。Streams可以自动生成全局唯一ID,支持阻塞读取操作,并且可以创建消费组,实现消息的负载均衡。另外,Streams会自动留存未确认处理的消息,保证消费者在发生故障或宕机后仍能读取未处理的消息。 总的来说,Redis作为消息队列的解决方案,List和Streams两种数据类型各有特点,读者可以根据实际需求选择合适的消息队列方案。对于分布式系统中通信量不大的组件,Redis提供了高性能和可靠性,是一个不错的消息队列解决方案。Streams数据类型的引入为解决List类型的不足提供了更合适的解决方案,为读者提供了更多选择。 在实践中,读者需要根据业务层面的数据体量,以及对性能、可靠性、可扩展性的需求来选择合适的消息队列方案。如果消息通信量不大,Redis的高性能特性能够支持快速的消息读写,使其成为消息队列的一个良好解决方案。Redis的List和Streams数据

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Redis 核心技术与实战》
新⼈⾸单¥68
立即购买
登录 后留言

全部留言(56)

  • 最新
  • 精选
  • walle斌
    redis作为队列,我觉得最大的问题应该是消息积压的问题。。任何一个mq 都要应对 高流量的消息积压问题。

    作者回复: 是的。所以,如果应用场景中压力不大,Redis应用起来也相对简单,可以作为一个方案备选。但是如果消息压力大,还是会考虑专用的一些MQ,例如Kafka。

    2020-12-10
    6
    16
  • Kaito
    如果一个生产者发送给消息队列的消息,需要被多个消费者进行读取和处理,你会使用Redis的什么数据类型来解决这个问题? 这种情况下,只能使用Streams数据类型来解决。使用Streams数据类型,创建多个消费者组,就可以实现同时消费生产者的数据。每个消费者组内可以再挂多个消费者分担读取消息进行消费,消费完成后,各自向Redis发送XACK,标记自己的消费组已经消费到了哪个位置,而且消费组之间互不影响。 另外,老师在介绍使用List用作队列时,为了保证消息可靠性,使用BRPOPLPUSH命令把消息取出的同时,还把消息插入到备份队列中,从而防止消费者故障导致消息丢失。 这种情况下,还需要额外做一些工作,也就是维护这个备份队列:每次执行BRPOPLPUSH命令后,因为都会把消息插入一份到备份队列中,所以当消费者成功消费取出的消息后,最好把备份队列中的消息删除,防止备份队列存储过多无用的数据,导致内存浪费。 这篇文章主要是讲消息队列的使用,借这个机会,也顺便总结一下使用消息队列时的注意点: 在使用消息队列时,重点需要关注的是如何保证不丢消息? 那么下面就来分析一下,哪些情况下,会丢消息,以及如何解决? 1、生产者在发布消息时异常: a) 网络故障或其他问题导致发布失败(直接返回错误,消息根本没发出去) b) 网络抖动导致发布超时(可能发送数据包成功,但读取响应结果超时了,不知道结果如何) 情况a还好,消息根本没发出去,那么重新发一次就好了。但是情况b没办法知道到底有没有发布成功,所以也只能再发一次。所以这两种情况,生产者都需要重新发布消息,直到成功为止(一般设定一个最大重试次数,超过最大次数依旧失败的需要报警处理)。这就会导致消费者可能会收到重复消息的问题,所以消费者需要保证在收到重复消息时,依旧能保证业务的正确性(设计幂等逻辑),一般需要根据具体业务来做,例如使用消息的唯一ID,或者版本号配合业务逻辑来处理。 2、消费者在处理消息时异常: 也就是消费者把消息拿出来了,但是还没处理完,消费者就挂了。这种情况,需要消费者恢复时,依旧能处理之前没有消费成功的消息。使用List当作队列时,也就是利用老师文章所讲的备份队列来保证,代价是增加了维护这个备份队列的成本。而Streams则是采用ack的方式,消费成功后告知中间件,这种方式处理起来更优雅,成熟的队列中间件例如RabbitMQ、Kafka都是采用这种方式来保证消费者不丢消息的。 3、消息队列中间件丢失消息 上面2个层面都比较好处理,只要客户端和服务端配合好,就能保证生产者和消费者都不丢消息。但是,如果消息队列中间件本身就不可靠,也有可能会丢失消息,毕竟生产者和消费这都依赖它,如果它不可靠,那么生产者和消费者无论怎么做,都无法保证数据不丢失。 a) 在用Redis当作队列或存储数据时,是有可能丢失数据的:一个场景是,如果打开AOF并且是每秒写盘,因为这个写盘过程是异步的,Redis宕机时会丢失1秒的数据。而如果AOF改为同步写盘,那么写入性能会下降。另一个场景是,如果采用主从集群,如果写入量比较大,从库同步存在延迟,此时进行主从切换,也存在丢失数据的可能(从库还未同步完成主库发来的数据就被提成主库)。总的来说,Redis不保证严格的数据完整性和主从切换时的一致性。我们在使用Redis时需要注意。 b) 而采用RabbitMQ和Kafka这些专业的队列中间件时,就没有这个问题了。这些组件一般是部署一个集群,生产者在发布消息时,队列中间件一般会采用写多个节点+预写磁盘的方式保证消息的完整性,即便其中一个节点挂了,也能保证集群的数据不丢失。当然,为了做到这些,方案肯定比Redis设计的要复杂(毕竟是专们针对队列场景设计的)。 综上,Redis可以用作队列,而且性能很高,部署维护也很轻量,但缺点是无法严格保数据的完整性(个人认为这就是业界有争议要不要使用Redis当作队列的地方)。而使用专业的队列中间件,可以严格保证数据的完整性,但缺点是,部署维护成本高,用起来比较重。 所以我们需要根据具体情况进行选择,如果对于丢数据不敏感的业务,例如发短信、发通知的场景,可以采用Redis作队列。如果是金融相关的业务场景,例如交易、支付这类,建议还是使用专业的队列中间件。
    2020-09-11
    63
    584
  • pedro
    每次想回答问题,看到 Kaito 的留言,顿时就有一种“眼前有景道不得,崔颢题诗在上头”的感觉。
    2020-09-11
    11
    157
  • 注定非凡
    1,作者讲了什么? 如何使用redis实现消息队列的需求 2,作者是怎么把这事给说明白的? 1,将一个问题拆解为两个具体的小问题:消息队列应具备哪些特性,Redis能否实现这些特性 3,为了讲明白,作者讲了哪些要点?,有哪些亮点? 1,亮点1:作者首先将一个相对模糊的问题,拆解成为两个问题更精确的问题 2,要点1:消息队列读取需求有三点:消息保存,消息唯一,消息可靠性 3,要点2:消息还要确保有序,削峰平谷,消费性能有弹性 4,要点3:Redis的list和5.0后的Stream数据结构可以满足 5,要点4:消息队列的三大要求:消息数据有序存取,消息数据具有全局唯一编号,消息数据在消费完成后被删除 4,对于作者所讲,我有哪些发散性思考? 解决问题的前提是搞清楚问题是什么 开篇:“Redis适合做消息队列吗”?对于这样的问题,我本能的就会直接想:“用Redis做消息队列?应该可以吧,很多资料上都这么说的”。 这样的回答,证明我并没有思考,只是陈述了我所知道的一点东西。我总是渴求快速有个答案,没有在意答案是否正确,更没有思考该如何回答问题。 虽然我总会说要想解决问题,首先要搞清楚问题是什么。这个句话似乎有点白痴,自己遇到的问题,这个问题不就它本身吗?怎么还会需要搞清楚呢? 其实不然,我们遇到的往往未必是问题的真身,而只是问题的表面现象或衍生出的问题。其实一个好问题的本身,就是一个好答案,而一个好问题胜过无数好答案。 那什么才是真正的解答问题?这需要先回答如何回答这个问题,也就是要先搞清楚要从哪些方面解释问题,而不是铺陈信息。 老师开篇对问题的回答方法,就是个非常好的范例 他是怎么解答的呢? 他并没有立即回答行或不行(这是我们最常有的反应),而是向后退了一步,问这个问题真正的问题是啥呢?消息队列存取消息需要哪些特性?Redis如何实现这些特性? 这就将一个相对模糊的问题拆解为两个较清晰的小问题,分别作答,最终得到一篇很好的文章 5,将来在哪些场景里,我能够使用它? 6,留言区收获 1,Redis是否可以作为消息队列?如果可以,哪些场景适合使用Redis,而不是消息中间件? 答:这个问题应当进一步拆分为:消息队列读写消息有哪些需求和Redis如何实现这些需求。 首先消息队列的消息读写有三大需求:消息读写的有序性,消息数据的唯一性,消息消费后数据删除。 针对这三大需求,Redis的List和5.0后的Stream数据结构,可以支持。 对于List,POP和PUSH命令,还有阻塞式的,备份式的。 对于Stream,是Redis5.0后提供新的数据结构,专门用户消息队列,他可以生成全局唯一id,还有能够创建消费者组,多个消费者同时消费 Stream类型,使用了应答机制,消费者消费完毕后会给Streams发送XACK命令,否则消息将会保存Streams的内部队列中,使用XPENDING命令可以查看 就Redis的相关数据结构而言,是可以作为消息队列的。但也要注意使用它的场景,虽然它性能很高,部署维护也轻量,但缺点是无法严格保证数据的完整性。 他适用于消息量并不是非常巨大,数据不是非常重要,从而不必引入其他消息组件的场景,如发短信,站内信 2,如果一个生产者发送给消息队列的消息,需要被多个消费者进行读取和处理,Redis的什么数据类型可以解决这个问题? 答:如果要使用Redis实现这个需求,Redis的Streams数据类型可以实现。 Streams可以使用XADD向队列中写入消息,XGOURP创建消费者组,XREADGROUP已消费者组形式消费消息,XACK向消息队列确认处理完成,XPENDING查询已消费待确认的消息 3,如果使用Redis作为消息队列,有哪些事项需要注意? 答:消息的可靠性,重点需要关注的是如何保证不丢消息 4,在使用消息队列时,如何保证不丢消息? 答:消息丢失可能会发生在三个环节:生产者发布消息,消息者消费消息,消息中间件丢失消息 生产者丢失消息一般通过重试机制和全局唯一id来解决, 消费者消费消息一般通过ack方式问询上报消费进度, 消息中间件宕机,一般通过主从备份,分布式集群来解决
    2020-10-19
    6
    61
  • DKSky
    老师的文中说“我们希望启动多个消费者程序组成一个消费组,一起分担处理 List 中的消息。但是,List 类型并不支持消费组的实现。那么,还有没有更合适的解决方案呢?“ 这句话不太明白,如果多个消费者客户端使用brpop命令阻塞式的“抢”列表尾部的元素,这些消费者不属于同一个consumer group吗?
    2020-09-11
    5
    12
  • hoppo
    关于顺序消费的问题,其实要严格保证顺序的话,只有生产者-队列-消费者完全的一对一才行。就好比List这种。 如果使用Stream消费组的话,多个消费者一起操作的时候,是没法保证顺序性的。 专业的消息队列中间件一般都采用局部有序的方法:需要有序操作的部分通过路由发到一个队列中,队列-消费者采用一对一的方式。
    2020-11-15
    1
    10
  • licong
    redis的publish/subscribe也可以作为简单的MQ使用,老师的专栏好像没提到这个
    2021-09-13
    2
    9
  • 杨逸林
    1. 问题回答 每个 Stream 都可以挂多个消费组,每个消费组会有个游标 last_delivered_id 在 Stream 数组之上往前移动,表示当前消费组已经消费到哪条消息了。如果这是对的,那只需要开两个消费者组,消费者 1 在组1,消费者2在组2不就行了么。 这个 last_delivered_id 有点像 Kafka 的 offset。 2. 有点不同的观点 Kafka 其实已经考虑去掉 ZK 了,而且适合高吞吐量的业务,不过不适合流式处理。专业的事,还是教给专业的来做好点。 我之前用过 Redis 的 publish 和 subscribe 命令来做不同系统的数据同步。不过那个在用 Jedis 来订阅有坑,必须循环调用,不然会在一定时间后被 down 掉。
    2020-09-11
    5
  • Yipsen
    消费组消费时虽然分摊了压力,但是怎么保证顺序消费呢?如果要保证岂不是要上一条消息的消费者必须发出ack后,消费组的另一个成员消费者才能开始消费消息,那不就等于加锁来保证顺序了,这样是不是消费组分摊就形同虚设或者没有起到应有的作用了?所以负载处理+顺序消费redis stream是怎么保证的?
    2021-12-03
    4
  • Luke 💨
    不懂为啥要用这种模式实现消息队列,感觉有点牵强,而且大部分功能需要自己实现,redis内部不是有发布订阅模式吗,就是一种消息队列啊,两者有啥不一样吗?
    2020-11-21
    5
    4
收起评论
显示
设置
留言
56
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部