在阿里云栖开发者沙龙PHP技术专场上,掌阅资深后端工程师、掘金小测《Redis深度历险》作者钱文品为大家介绍了RabbitMQ的延时队列和镜像队列的原理与实践,重点比较了RabbitMQ提供的消息可靠与不可靠模式,同时介绍了生产环境下如何使用RabbitMQ实现集群间消息传输。

本次直播视频精彩回顾,戳这里!
直播回顾:https://yq.aliyun.com/live/965
PPT分享:https://yq.aliyun.com/download/3529

本文根据演讲视频以及PPT整理而成。

本文将主要围绕以下四个方面进行分享:

  1. RabbitMQ特性
  2. RabbitMQ中的消息不可靠问题及其解决方案
  3. 死信队列
  4. 生产环境下使用RabbitMQ应注意的事项

RabbitMQ特性

对于左边的Client Publisher而言,RabbitMQ Server是消息的接收者,也就是消费者;对于右边的Client Consumer而言,RabbitMQ Server是消息的发送者,也就是生产者。RabbitMQ Server将消息从Client Publisher传送给Client Consumer,扮演着消息中间商的角色。

RabbitMQ Server负责将Client Publisher传递来的消息持久化,延后地将消息传递给Client Consumer.这样,即使消费者挂掉,RabbitMQ Server也可以存储消息,当消费者重新工作时再将存储的消息传递过去,从而保证消息不丢失。RabbitMQ Server提供了堆积消息的能力。

另外,RabbitMQ Server还具有复制和广播消息的能力。具体来说,RabbitMQ Server可以将Client Publisher发布的消息分发给多个消费者,比如它能够将特定的消息按照特定的队列分发给特定的消费者。“特定”指不同消息具有不同的routing key属性,由上图实例,不同的消息生产者生产了具有不同routing key的消息,通过exchange路由器将不同的routing key消息投递到不同队列,从而分发给不同消费者。

RabbitMQ中的消息不可靠问题及其解决方案

消费端消息不可靠问题及其解决方案

实际上,RabbitMQ Server将消息投递给消费者,具有消息不可靠的特点。具体来说,RabbitMQ Server将消息投递给消费者时会调用套接字的write操作,而write操作的过程是不可靠性的。在write操作的过程中,Server需要将消息发送到套接字的缓存中,通过网卡转发到链路上,最终到达消费者所在的机器内核的套接字缓存中,由消费者使用套接字的read操作将消息读出来。

即使套接字的write操作成功也无法保证消息可靠,潜在的网络故障可能使消费者接收不到消息。机器宕机也可能使消息不可靠,即使消息字节流已经到达消费者所在机器,消费者所在机器的宕机也可能使消息无法被即时读取并处理。另外,即使消费者即时读取消息,内存消息队列中的所有消息也可能因为kill-9操作发生丢失。这些可能性都直接导致了消息不可靠。

因此,需要额外的措施为消息提供可靠保障。一种消息可靠性保障方式是,Server投递消息后并不立即将消息从Server删除,而是等到消费者接收、处理消息并返回Ack包给Server后,Server才删除该消息。如果消费者没有发送Ack包,那么Server将重新投递该消息。这个过程确保消息被消费者处理,保证了消息可靠。另外,假如消费者已处理消息并发送Ack包给Server,但由于网络故障等问题导致Ack包丢失时,那么Server同样会重新投递该消息,导致消息被重复处理。消息的重复处理通常由业务层面的技术手段来避免,比如在数据库层面添加主键约束等。另一种重复消息处理的避免方式是客户端对每条消息维护ID, 将被处理消息的ID记录在列表中,同时检查新到消息是否在该列表中。

RabbitMQ中的Auto Ack和Manual Ack对应着消息不可靠模式和消息可靠模式. Auto Ack即no ack,指消息投后即删除,对应消息不可靠传输。Manual Ack即手动Ack,消费者处理完消息后使用Ack包通知Server删除消息,对应消息可靠传输。
Auto Ack是RabbitMQ中最常用的模式,性能较好,但具有以下问题。当消息通过套接字write操作投递后,RabbitMQ Server立即删除该消息,该模式在遇到网络故障时容易发生消息丢失。另外,假如消费者处理消息的速率过低,可能导致消息在消费者recv buffer中大量堆积,从而导致Server端send buffer也堆积大量消息, Server端无法继续调用套接字write操作。这样,一段时间之后,Server可能强制关闭消息传输链接,导致消息不可传输。
尽管Auto Ack存在一定风险,目前许多公司仍在应用Auto Ack模式。使用Auto Ack模式时,开发者需要注意消费者和生产者的实例数量比例,使消息生产者产生消息的速率与消费者消费消息的速率大致持平。

Manual Ack是RabbitMQ 中更加智能的一种模式。Manual Ack在工作时会考虑消息消费者的消息接收能力,根据消费者的消息接受能力和当前接收到的Ack包自动调节分发消息的速率,保证消息分发可靠、不阻塞。具体来说,客户端通过PrefetchCount告知Server自身堆积消息的能力。
生产端消息不可靠问题及其解决方案

消息生产端同样存在消息的可靠性问题。从Client Publisher将消息传递给Server和从Server将消息传递给Client Consumer的过程是完全对等的,Server和Client Consumer间传递消息的可靠性问题在Client Publisher和Server间同样存在。

Client Publisher首先将消息写到套接字,再通过网络传递给Server的套接字buffer,最终由Server读取该消息。这一过程的潜在网络问题也可能使Server端接收不到消息。

另外,Server端本身也可能导致消息不可靠。Server端需要持久化消息,但出于性能开销的考虑,Server端并不在每次持久化消息时都刷盘。具体来说,Server端会对文件执行write操作,将脏数据写入操作系统的缓存中,而不是立即将数据写入磁盘。一般情况下,Server可能每几百毫秒执行一次fsync操作,通过fsync操作将文件的脏数据写入磁盘。由于Server具有宕机风险,那么每次Server宕机时,还未被fsync操作处理的数据就可能丢失,此过程类似于Redis AOF。

RabbitMQ通过生产者事务和生产者确认两个方法解决Server产生的数据不可靠问题。
生产者事务的基本原理是采用select和commit指令包裹publish,在消息生产者publish数据之前执行select操作,相当于begin transaction事务开始,在执行若干个publish操作后,再执行commit操作,相当于提交事务。根据tcp包的有序性,commit包成功接收意味着commit包之前的包也成功接收。因此,收到从Client Publisher传递过来的commit包意味着该commit包之前的所有publish包都已成功接收,即所有消息都成功接收。然而,commit包只有等到Server端的fsync操作执行完毕时才返回,因此生产者事务的效率较低,通常只在有批量publish操作时才使用生产者事务模式。也就是说,客户端将消息累计起来批量发送,以降低fsync操作带来的性能损失。此外,在进程中累计消息也存在风险,累计的消息可能由于进程挂掉而丢失。总的来说,生产者事务由于性能缺点不被RabbitMQ官方推荐。

另一种Server带来的数据不可靠问题的解决方案是生产者确认。生产者确认类似于消费端的Ack机制,生产者可能连续发送多条消息,Server将这些消息异步地通过fsync操作写入磁盘再异步地给生产者发送Ack包,告知生产者消息的接收成功。由于Ack包异步传输,不影响生产者端消息的正常发送。生产者确认模式下,Ack包批量发送,并且都携带有序号,以告知生产者该序号以前的所有消息都已正常落盘。尽管RabbitMQ推荐用户使用生产者确认模式,目前的RabbitMQ版本还未实现消息的重发机制,只实现了Ack包的批量发送,以通知Client Publisher哪些消息接收成功。当消息丢失时,Client Publisher端已publish的消息在进程挂掉时也可能丢失,而不是重新发送,因此生产者确认的作用也不明显。当然,生产者确认起到了降低消息发布速度的作用,减小了消息丢失的数量。

生产者确认中的消息重发可以通过以下几种方法实现。第一种方式在内存中累积还未收到Ack包的消息,收到Ack包后删除该消息,对于一段时间内还停留在内存中的消息,重发该消息。这种方式将未Ack消息存入内存,一旦消息生产者宕机,这些消息也会丢失。另一种方式将未收到Ack包消息存入磁盘,当收到Ack包后删除该消息,然而,磁盘存储依赖于fsync操作,降低了系统处理消息的性能。同时,这还会提高编程的复杂度,因为这要求发布消息时维护文件队列,还要求一个异步线程将文件队列中的消息发布到Server,带来了多线程和锁问题。还有一种方式将未Ack消息存入Redis,但当出现网络故障时,Redis也是不可靠的。目前提供的生产者确认中的消息重发方案都还存在问题,具体的方案选择依赖于实际场景和个人取舍。

死信队列

生产者确认中的消息重发可以通过以下几种方法实现。第一种方式在内存中累积还未收到Ack包的消息,收到Ack包后删除该消息,对于一段时间内还停留在内存中的消息,重发该消息。这种方式将未Ack消息存入内存,一旦消息生产者宕机,这些消息也会丢失。另一种方式将未收到Ack包消息存入磁盘,当收到Ack包后删除该消息,然而,磁盘存储依赖于fsync操作,降低了系统处理消息的性能。同时,这还会提高编程的复杂度,因为这要求发布消息时维护文件队列,还要求一个异步线程将文件队列中的消息发布到Server,带来了多线程和锁问题。还有一种方式将未Ack消息存入Redis,但当出现网络故障时,Redis也是不可靠的。目前提供的生产者确认中的消息重发方案都还存在问题,具体的方案选择依赖于实际场景和个人取舍。
三、死信队列

死信队列使用了RabbitMQ中的一种特殊队列属性,即x-message-ttl属性,表示队列中消息的构建时间。假如用户在声明队列时定义队列的x-message-ttl属性,此后所有进入该队列的消息都将持有构建时间,到达构建时间的消息将被删除。如果还为队列配置了回收站属性,那么即使构建时间到达,RabbitMQ也不会立即删除这些消息,而是将这些过期消息丢入回收站,即死信队列。

死信队列的工作方式如上图。Client Publisher将消息投递给路由器,也就是exchange,再由exchange将消息投递给队列,由队列生成该消息的构建时间,到达构建时间的消息将过期,同时进入死信队列。过期消息进入死信队列的方式和进入普通队列的方式基本一致,即先投递给exchange路由器,再由exchange投递消息。消费者消费死信队列,得到的消息是延后的消息,延迟的时间长度即构建时间。目前,死信队列存在的问题是,一个队列只能设置一个构建时间,消息的过期时间不够灵活,不能满足一些特殊场景的需求,比如动态的重试时间。

死信队列的另一个使用场景是Retry Later,即在一段时间后才重新处理此前处理失败的消息,这时可能用到双重死信。具体来说,死信队列不仅可以接收过期消息,还可以接收被reject的消息,即消费端拒绝处理或处理过程发生异常的消息,Reject操作具有requeue参数,当requeue设为true时被reject消息会重新进入消息队列并被重新投递,当requeue设为false时被reject消息将进入死信队列。假如死信队列持有构建时间,那么到达构建消息的消息将重新投递给原有队列,实现Retry Later。双重死信在使用过程中需注意消息处理的死循环问题,因为消息可能无限循环地进入死信队列。

生产环境下使用RabbitMQ应注意的事项

生产环境下,RabbitMQ通过使用集群模式。集群模式下,只有元信息分布在所有节点中。元信息指队列信息,路由器信息等,队列中的信息只存储在一个节点中,因此,单个节点宕机会导致所有节点都不可用。另外,RabbitMQ的所有节点间存在转发机制,即允许节点转发其他目标节点的消息处理请求,这样客户端只需连接到任意一个节点就可以实现其消息转发需求。

队列的高可用依赖于RabbitMQ的镜像队列,即在其他节点上备份某节点的消息内容。这样,当消息所在主节点宕机时,其他镜像节点可以替代主节点完成消息传递任务。

通常情况下,镜像节点是默默无闻的,客户端无需感知镜像节点的存在。只有当主节点宕机时,镜像节点才发挥作用。镜像队列的配置如下:

  • Ha-mode具有三个选项,all指将所有队列的信息存入所有节点,这种模式最安全,但也最浪费存储空间;exactly指由用户精确指定每个队列的复制数,当ha-mode设置为exactly,ha-params设置为2时表示“一主一从”,这种模式是官方推荐的;nodes指由用户指定副本所在的节点,这种模式极少被使用。
  • x-queue-master-locator用于设置存储队列主节点的RabbitMQ节点。min-master指将队列主节点设置在队列数量最少的RabbitMQ节点,client-local指将队列主节点设置在当前客户端所在的RabbitMQ节点,random即随机选择节点。
  • Ha-sync-mode用于镜像节点代替宕机主节点并创建新节点以弥补缺失节点时,设置新节点上数据的同步策略。automatic指自动地将新主节点上数据全部同步给新节点,manual指不同步新主节点上的老数据,只同步新产生的数据。由于节点间数据同步需要耗费时间,长时间的数据同步可能会影响服务的稳定性,但通常情况下RabbitMQ的节点堆积的数据量并不大,因此RabbitMQ官方推荐使用Automatic进行数据同步。
  • Ha-sync-batch-size指节点间批量同步的数据量。
  • Ha-promote-on-shutdown表示主动停止主节点的服务时,其他节点如何替代主节点。Always指其他节点总是能顺利地替代主节点,when-synced要求与原主节点数据完全一致的节点才能替代主节点。
  • Ha-promote-on-failure表示异常情况下其他节点如何替代主节点,always和when-synced的含义与Ha-promote-on-shutdown中一致。

许多公司为RabbitMQ集群设置了内存模式,认为内存模式无需落盘,能够提升系统性能。但实际上,RabbitMQ官方文档指出,内存模式无法提升系统性能,它只提升了产生元信息数据的速度,即Ram Node指将元信息存入内存,可以提升元信息的创建速度,而不是消息数据的性能。这是使用RabbitMQ时的一个常见误区。

原文链接
本文为云栖社区原创内容,未经允许不得转载。

RabbitMQ 的延时队列和镜像队列原理与实战相关推荐

  1. RabbitMQ之惰性队列与镜像队列

    文章目录 1.惰性队列 1.1.使用场景 1.2.定义 1.3.队列模式 1.4.工作流程 1.5.总结 2.镜像队列 2.1.消息流转过程 2.2.负载均衡 2.3.消息的可靠性 2.4.GM协议 ...

  2. RabbitMQ集群搭建、镜像队列、实现高可用负载均衡、Federation Exchange、Federation Queue、Shovel

    1.环境准备 IP地址 主机名 192.168.56.20 conch01 192.168.56.21 conch02 192.168.56.22 conch03 2.安装 rabbitmq集群 1. ...

  3. 【深入理解RabbitMQ原理】RabbitMQ 普通队列与镜像队列 底层原理

    RabbitMQ 底层实现原理 普通MQ的结构 MQ内部大致又可以分为两部分:amqueue和backing queue, amqqueue负责实现amqp协议规定的mq的基本逻辑, backing ...

  4. RabbitMQ镜像队列实现原理

    一.镜像队列使用 1.镜像队列作用 ​ RabbitMQ默认集群模式,并不包管队列的高可用性,尽管队列信息,交换机.绑定这些可以复制到集群里的任何一个节点,然则队列内容不会复制,固然该模式解决一项目组 ...

  5. RabbitMQ 高可用之镜像队列

    如果RabbitMQ集群只有一个broker节点,那么该节点的失效将导致整个服务临时性的不可用,并且可能会导致message的丢失(尤其是在非持久化message存储于非持久化queue中的时候).可 ...

  6. Dockerfile 定制 Rabbitmq 集成延时队列的镜像

    下载 延时队列插件 官方提供的一些插件 延时队列插件 下载适合的版本 构建 Dockerfile 新建一个文件夹 newDockerImage,放入插件文件并新建文件名为Dockerfile的文件 D ...

  7. RabbitMQ镜像队列原理分析

    对于RabbitMQ的节点来说,有单节点模式和集群模式两种,其中集群模式又分为普通集群模式和镜像队列集群模式,在<RabbitMQ集群架构搭建与高可用性实现>文中,介绍了RabbitMQ的 ...

  8. Rabbitmq集群,镜像队列和分布式原理

    前言 基于前两次的分享会,结合rabbitmq相关知识,做一个小结.说明一致性的设计思想,在此说明相关的基础理论. CAP定理: 在计算机科学里,CAP定理又被称作布鲁尔定理(Brewer theor ...

  9. RabbitMQ之镜像队列

    欢迎支持笔者新作:<深入理解Kafka:核心设计与实践原理>和<RabbitMQ实战指南>,同时欢迎关注笔者的微信公众号:朱小厮的博客. 欢迎跳转到本文的原文链接:https: ...

最新文章

  1. python语言格式化输出_Python字符串格式化输出
  2. 浅谈Opencl四大模型之Memory Model
  3. LeetCode-402:移除k位数字
  4. 树莓派系统的安装、初步配置与远程访问
  5. Springboot 内嵌 Tomcat 版本查看
  6. WinForm教程(一)App.config等配置文件
  7. win7计算机系统减肥,win7系统精简瘦身的操作方法
  8. 锁相环(PLL)专题一:鉴相器PD
  9. 视觉SLAM笔记(64) 八叉树地图
  10. ABR算法研究综述 | A Survey on Bitrate Adaptation Schemes for Streaming Media Over HTTP(IEEE COMST‘18)阅读笔记
  11. php黑名单绕过,文件上传漏洞之黑名单检测绕过
  12. 1665. 计算数字
  13. iPhone白苹果,刷机降级,无限重启,无限恢复模式,无限DFU模式,错误4013,错误3014...
  14. 【转帖】视频聊天网站的研究、发展以及趋势
  15. ArcGIS绘制地球
  16. 文字图片滚动代码-无缝滚动,强!!!
  17. RPC框架的路由策略
  18. Win平台必装软件清单
  19. 一位逝去的华为青年胡新宇:天堂里不再有加班
  20. C6678 SRIO

热门文章

  1. python调用系统_python 执行系统命令
  2. vb ftp linux,VB FTP上传和下载模块
  3. 【前端知识学习】HTML5 学习笔记
  4. mysql8支持myISAM_mysql菜鸟手迹8--mysql存储引擎之MyISAM
  5. pdf在线翻译_如何免费快速地翻译pdf英文文档,并保留很好的格式?
  6. linux 多域名访问数据库,Linux下虚拟域名的实现
  7. python import io_Python——python3的io读写
  8. 这群女性被称为“哈佛计算机”,她们画出了百年前第一幅恒星地图
  9. 成都女孩弃港大全额奖学金,将去北大读马克思,“我对党史和马克思很有感情”!...
  10. 不拘一格!清华将致力于培养顶尖数学家