AMQP协议之RabbitMQ
2010-08-26 08:41:04 阿炯

RabbitMQ 是一个实现了AMQP协议的消息服务器。RabbitMQ 是由 LShift 提供的一个 Advanced Message Queuing Protocol(AMQP) 的开源实现,由以高性能、健壮以及可伸缩性出名的 Erlang 写成,因此也是继承了这些优点。采用ErLang语言开发并在MPL协议下授权。

它是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件),由Erlang语言编写的,而集群和故障转移是构建在开放电信平台框架上的。AMQP:Advanced Message Queue,高级消息队列协议。它是应用层协议的一个开放标准,为面向消息的中间件设计,基于此协议的客户端与消息中间件可传递消息,并不受产品、开发语言等条件的限制。RabbitMQ 是实现了 AMQP 协议的开源消息代理软件,起源于金融系统。


AMQP 里主要要说两个组件:Exchange 和 Queue(在 AMQP 1.0 里还会有变动),如下图所示,绿色的 X 就是 Exchange ,红色的是 Queue ,这两者都在 Server 端,又称作 Broker ,这部分是 RabbitMQ 实现的,而蓝色的则是客户端,通常有 Producer 和 Consumer 两种类型。Rabbit科技有限公司开发了RabbitMQ,并提供对其的支持。起初,Rabbit科技是LSHIFT和CohesiveFT在2007年成立的合资企业,2010年4月被VMware旗下的SpringSource收购。2013年5月RabbitMQ成为GoPivotal的一部分。

构架示意图:





AMQP具有如下的特性:
可靠性(Reliablity):使用了一些机制来保证可靠性,比如持久化、传输确认、发布确认。
灵活的路由(Flexible Routing):在消息进入队列之前,通过Exchange来路由消息。对于典型的路由功能,已经提供了一些内置的Exchange来实现。针对更复杂的路由功能,可以将多个Exchange绑定在一起,也通过插件机制实现自己的Exchange。
消息集群(Clustering):多个RabbitMQ服务器可以组成一个集群,形成一个逻辑Broker。
高可用(Highly Avaliable Queues):队列可以在集群中的机器上进行镜像,使得在部分节点出问题的情况下队列仍然可用。
多种协议(Multi-protocol):支持多种消息队列协议,如STOMP、MQTT等。
多种语言客户端(Many Clients):几乎支持所有常用语言,比如Java、.NET、Perl等。
管理界面(Management UI):提供了易用的用户界面,使得用户可以监控和管理消息Broker的许多方面。
跟踪机制(Tracing):如果消息异常,RabbitMQ提供了消息的跟踪机制,使用者可以找出发生了什么。
插件机制(Plugin System):提供了许多插件,来从多方面进行扩展,也可以编辑自己的插件。

RabbitMQ中的消息都只能存储在Queue中,生产者产生消息并最终投递到Queue中,消费者可以从Queue中获取消息并消费。消息的发送引入了 Exchange(交换机)的概念,消息的发送首先到达交换机上,然后再根据既定的路由规则,由交换机将消息路由到不同的 Queue(队列)中,再由不同的消费者去消费。


大致的流程就是这样,所以要确保消息发送的可靠性,主要从两方面去确认:
消息成功到达 Exchange
消息成功到达 Queue

如果能确认这两步,那么我们就可以认为消息发送成功了。如果这两步中任一步骤出现问题,那么消息就没有成功送达,此时我们可能要通过重试等方式去重新发送消息,多次重试之后,如果消息还是不能到达,则可能就需要人工介入了。

经过上面的分析,我们可以确认,要确保消息成功发送,我们只需要做好三件事就可以了:
确认消息到达 Exchange。
确认消息到达 Queue。
开启定时任务,定时投递那些发送失败的消息。

上面提出的三个步骤,第三步需要我们自己实现,前两步 RabbitMQ 则有现成的解决方案。如何确保消息成功到达 RabbitMQ?RabbitMQ 给出了两种方案:
开启事务机制
发送方确认机制

当我们开启事务模式之后,RabbitMQ 生产者发送消息会多出四个步骤:
客户端发出请求,将信道设置为事务模式。
服务端给出回复,同意将信道设置为事务模式。
客户端发送消息与提交事务。
服务端给出响应,确认事务提交。

上面的步骤,除了第三步是本来就有的,其它几个步骤都是推演出来的。所以能看到,事务模式其实效率有点低,这并非一个最佳解决方案。可以想想什么项目会用到消息中间件?一般来说都是一些高并发的项目,这个时候并发性能尤为重要。所以RabbitMQ 还提供了发送方确认机制(publisher confirm)来确保消息发送成功,这种方式在性能上要远远高于事务模式。

基于 Erlang 语言开发实现RabbitMQ单机性能表现不错,横向拓展能力较弱,可用于吞吐量在万级的系统当中。下面列出其消息模式。支持简单模式、工作队列模式、发布/订阅模式、路由模式、主题模式和 RPC 模式。


简单模式


队列模式


发布订阅模式


路由模式


主题模式


RPC 模式

以上所有模式实际上都及基于消息队列来实现的,发布订阅模式和主体模式,也是通过队列来实现的,对交换器绑定后再通过路由规则来分发消息到队列中,也就是 BindingKey 和 RoutingKey,由于 RoutingKey 不能重复,也就意味着队列收到的消息不能一样,而每条消息只会发送给订阅列表里的一个消费者,从而就是没有消费者组的概念,无法做到真正的发布订阅。带着这个理解看 RabbitMQ 架构就会比较清晰了。


RabbitMQ 架构


上图是单机的架构,那么集群架构是怎么样的呢?


HA-Proxy 一款提供高可用性、负载均衡以及基于 TCP 和 HTTP 应用的代理软件,主要是做负载均衡的 7 层,也可以做 4 层负载均衡。Keepalived 是集群管理中保证集群高可用的一个服务软件,其功能类似于 Heartbeat,用来防止单点故障。虽然是高可用方案,但总体来说横向扩展能力较弱。


特性与经验

具有丰富的特性:
消息可靠性保证,RabbitMQ 通过发送确认保证消息发送可靠、通过集群化、消息持久化、镜像队列的方式保证消息在集群的可靠、通过消费确认保证消息消费的可靠性。
提供了多种类型的 exchange,消息发送到集群后通过exchange路由到具体的queue中。
RabbitMQ 提供了完善的管理后台和管理 API,通过管理API可以快速与自建监控系统整合,提供了多种语言的客户端。

实践中发现的问题:
为保障业务高可用使用多套集群进行物理隔离,多套集群无统一平台进行管理。
原生RabbitMQ客户端使用集群地址连接,使用多套集群时业务需要关心集群地址,使用混乱。
原生RabbitMQ仅有简单的用户名/密码验证,不对使用的业务应用方进行鉴权,不同业务容易混用exchange/queue信息,造成业务应用使用异常。
使用的业务应用方较多,无平台维护消息发送方、消费方的关联信息,多个版本迭代后无法确定对接方。
客户端无限流,业务突发异常流量冲击甚至击垮集群。
客户端无异常消息重发策略,需要使用方实现。
集群出现内存溢出等造成集群阻塞时无法快速自动转移到其它可用集群。
使用镜像队列,队列的master节点会落在具体某个节点上,在集群队列数较多时,容易出现节点负载不均衡的情况。
RabbitMQ无队列自动平衡能力,在队列较多时容易出现集群节点负载不均问题。

集群脑裂问题处理

RabbitMQ官方提供了三种集群脑裂恢复策略:
(1)、ignore:忽略脑裂问题不处理,在出现脑裂时需要进行人为干预才可恢复。由于需要人为干预,可能会造成部分消息丢失,在网络非常可靠的情况可以使用。
(2)、pause_minority:节点在与超过半数集群节点失联时将会自动暂停,直到检测到与集群超半数节点的通信恢复。极端情况下集群内所有节点均暂停,造成集群不可用。
(3)、autoheal:少数派节点将自动重启,此策略主要用于优先保证服务的可用性,而不是数据的可靠性,因为重启节点上的消息会丢失。

集群高可用方案

RabbitMQ采用集群化部署,并且因为集群脑裂恢复策略采用pause_minority模式,每个集群要求至少3个节点。
推荐使用5或7节点部署高可用集群,并且控制集群队列数量。
集群队列均为镜像队列,确保消息存在备份,避免节点异常导致消息丢失。
exchange、queue、消息均设置为持久化,避免节点异常重启消息丢失。
队列均设置为lazy queues,减少节点内存使用的波动。

相关端口信息

集群间通信端口,4369与25672(Erlang distribution),Erlang使用PORT Mapper Daemon(epmd)解析集群中的节点名称。节点必须能够相互访问,端口映射器守护进程才能运行群集,在 Erlang 集群中相当于 DNS 的作用。Epmd 是 Erlang Port Mapper Daemon 的缩写。

管理控制台端口,适用于RabbitMQ版本3.x及以后的端口为15672:HTTP API客户端和rabbitmqadmin (仅当启用管理插件时),而端口55672适用于RabbitMQ 3.x之前的版本。
    
集群对外服务端口,只需要5672,对于任何需要使用消息队列的服务器。

集群间必须在35197、4369和5672上相互开放。


最新版本:3.6
该版本引入动态调度基于策略的控制镜像和联合,提升用户友好的集群特性,支持每个消息的 TTL 控制,包含 web-STOMP 和 MQTT 插件,还有很多其他的小改进和 bug 修复。新特性包括联合队列,增强策略,消费者优先级,堵塞连接通知,认证失败通知等以及大量Bug修正。重大更新如下:
最低要求 Erlang 版本是 R16B03 用于支持所有协议的 plain ("just TCP") 连接,要求 17.5 用于 TLS 连接,推荐 18.x。
.NET 客户端要求 .NET 4.5.
默认订阅 TTL in MQTT 设置为 24 小时

最新版本:3.7
RabbitMQ 3.7.27 于2020年7月发布了,这是一个维护版本,包含了来自 3.8.x 的一些反向移植。此版本不再支持 Erlang/OTP 20.3,现在在节点启动时检查 Erlang 21.3+是一项硬性要求。此前 3.8.1 中,针对 Core Server、CLI 工具、管理插件以及 STOMP 插件进行了一些 bug 修复和性能增强。例如在 Core Server 中集群节点升级问题:如果将新版本立即部署到所有集群节点,而不是分别为每个节点先进行滚动升级然后重新启动,则滚动集群升级可能会失败。针对 CLI 工具增加了一个新命令rabbitmq-diagnostics consume_event_stream,通过它可以更轻松地使用内部命令流。这对于故障的排除和审核很有用。更多详情请查看更新说明

最新版本:3.8
RabbitMQ 3.8.0 正式发布了。RabbitMQ 是由 LShift 提供的一个 Advanced Message Queuing Protocol (AMQP) 的开源实现,由以高性能、健壮以及可伸缩性出名的 Erlang 编写而成,因此它也是继承了这些优点。该版本包含了在数据安全、复制、可观察性和升级易用性方面的几个主要改进,内容如下:
Quorum Queues
内置 Prometheus 支持,并带有一组 Grafana 仪表板支持
Feature Flags
Single Active Consumer
使用 OAuth 2.0(JWT)令牌的新身份验证和授权后端

Core Server
Quorum Queues 建立在  Raft consensus algorithm 之上,用于数据安全、更可预测的故障恢复以及并行复制
Feature Flag 允许混合版本集群和更安全的滚动升级
Single Active Consumer 使运行一组消费者进行冗余操作成为可能

内部 API 变化
amqQueue 是一个新的公共 API 模块,用来访问队列状态
Authn 和 authz 函数现在可以访问其他的(例如,特定于协议的)上下文信息
备份队列接口现在公开一个用于去重复消息的函数

Usability
新的样式配置格式
最大消息大小限制现在是可配置的

CLI Tools
更加友好的 rabbitmq-diagnostics status 输出
新的命令 help
新工具 rabbitmq-queues,显示 RAFT 状态度量和管理存储仲裁队列副本的节点的命令

Usability
rabbitmq-diagnostics cipher_suites 现在默认使用 OpenSSL 密码套件格

Management Plugin
支持 Quorum Queue
支持 Single Active Consumer
支持更多 TLS 选项
OAuth 2.0 支持 UAA 的单一标志
HTTP Auth 身份验证现在可以通过 UAA 禁用 OAuth2.0 单点登录

引入了一种新的节点操作模式,称为维护模式。当将节点放下进行维护并处于维护模式时,该节点将无法用于服务客户端流量,并且将尝试尽可能实际地转移其职责使其安全。这涉及以下步骤:
挂起所有客户端连接侦听器(不接受新的客户端连接)
关闭所有现有的客户端连接:应用程序应重新连接到其它节点并恢复
所有经典镜像队列的主副本托管在传输目标节点上
所有仲裁队列的主要副本托管在传输目标节点上,并阻止它们参与随后触发的 Raft 选举
将节点标记为关闭以进行维护
此时,由于节点已经转移了大部分职责,因此节点关闭的破坏性将最小

不管队列类型和使用的队列主定位器策略如何,都不会考虑将处于维护模式的节点用于新的主队列副本放置。处于维护模式的节点应在短时间内关闭、升级或重新配置,然后重新启动。节点不应长时间在此模式下运行。操作员可以使用一系列新的 CLI 命令显式打开和关闭该模式。详情查看发行说明

当用户从3.7.15或较旧版本升级到此版本时,必须格外小心。因为较旧的 RabbitMQ CLI 工具可能与 Erlang 22+ 不兼容,因此用户需要同时升级 RabbitMQ 与 Erlang。Direct reply-to 在 Erlang 22.3 上出现了异常。运行在 Erlang 23 上的节点不受影响。关于异步 I/O 线程的警告被替换成了 Dirty I/O schedulers 警告。这使得信息与现代 Erlang 版本中的设置保持一致。此版本更新内容很多,详情请见发行说明

RabbitMQ 3.8.16 已于2021年5月发布,Erlang 新版本支持:
这个版本引入了对 Erlang 24 的支持,并放弃了对 Erlang 22 的支持。Erlang 24 为许多 RabbitMQ 安装和工作负载提供了显著的实际吞吐量改进,预计它将于 2021 年 5 月推出。同时请参见配置最新的 Erlang 版本,以了解如何配置最新版本的 Erlang 23.3。
在 3.8.15 版本中,这个插件被无意中排除在发行版之外了,现在已经被添加回来了。
消息轮询的 HTTP API 端点在轮询超时的情况下泄露了它使用的临时连接。请注意这个端点不建议在 QA 环境之外使用。
Cuttlefish 已经从 2.6.0 升级到了 3.0.0;Lager 已从 3.8.2 升级到 3.9.1。

最新版本:3.9
RabbitMQ 3.9.0 正式版于2021年7月末发布,该版本更新内容如下:
Streams是 RabbitMQ 中一种新的持久化和复制的数据结构(" queue 类型"),它是一种具有非破坏性消费者语义的 append-only 日志模型。它们可以作为常规 AMQP 0.9.1 队列使用,也可以通过新的二进制协议插件和相关客户端使用,Streams 可以实现以前不可能或不实际的消息传递模式。Erlang 24 为许多工作负载提供了 20%-50% 的吞吐量提升。支持最新的运行时版本还允许 RabbitMQ 用由运行时提供的库取代一些外部依赖。Erlang 24 现在默认用于社区 RabbitMQ Docker 镜像。

RabbitMQ Cluster Operator 可自动配置、管理和操作在 Kubernetes 上运行的 RabbitMQ 集群。该 Operator 不仅是针对 3.9 版本的,也可用于最新的 3.8.x 版本系列。Messaging Topology Operator 使得将 RabbitMQ 资源(虚拟主机、用户、权限、拓扑结构、策略等)定义为 Kubernetes 对象成为可能。该 Operator 可用于 3.8.x 版本系列。从 Lager 切换到新的 Erlang Logger API 进行日志记录引入了一个 JSON 结构化日志的选项。Erlang/OTP 的兼容性说明:此版本需要 Erlang 23.2 或更高版本,建议使用 Erlang 24。RabbitMQ 3.9.0 节点可以与 3.8.x 节点一起运行, 3.9.x的特定功能只有在集群中的所有节点升级到 3.9.0 或该系列中的任何其他补丁版本时才能实现。

错误修复
powershell.exe 现在可以在没有配置文件的情况下运行;
队列索引恢复现在可以在恢复过程中强制关闭节点后继续进行;
纠正 num_acceptors.ssl 应用的配置值不正确的问题;
纠正了如果在rabbitmqctl add_vhost命令中没有指定 -description,-tags 标志会被忽略的问题。

更多详情可查看此处

最新版本:3.11
RabbitMQ 3.11 是一个新功能版本,包括几个新功能和优化,完成一些功能标志,且将最低要求的版本提高到 Erlang 25 ,以实现 ARM64 CPU 的奇偶校验。3.11.0 要求在升级之前启用 3.8.x 版本系列中的所有功能标志。如果未启用功能标志,3.11 及更高版本的节点将拒绝启动。
更有效的连接跟踪,这意味着在无法避免高连接流失的环境中减少 CPU 负载。
使用默认交换的发布者的吞吐量增加了 10% ,达到 20%。
添加虚拟主机后,现在可以将其配置为默认队列类型。
当节点发生故障时,持久的经典队列在某些情况下可能会 “丢失”。
在节点启动早期 DNS 主机名解析不可用的 Kubernetes 上执行滚动重启时,节点可能会卡住。例如,流行的默认缓存设置 CoreDNS 就是这种情况。
更强大的 Windows 可用磁盘空间监控功能。
RabbitMQ 现在支持分区流(超级流)。超级流是一种通过将大流划分为较小流来进行横向扩展的方法。
流协议客户端和 RabbitMQ 节点现在具有交换其功能(支持的命令集)的机制。
允许应用程序检查某些流元数据的新流协议命令。
对流的单一活动消费者支持。
消费者现在可以访问他们提交的偏移信息。
OAuth 2 AuthN/AuthZ 后端插件
OAuth 2 插件现在支持更多身份提供者和 OpenID Connect。
OAuth 2 插件现在支持 Rich Authorization Requests。
概述页面上的一些计数器已移至 RabbitMQ 3.9 中引入的全局计数器。
管理 UI OAuth 2 集成支持更多身份提供者和 OpenID Connect。
由于内部使用了更高效的 JSON 序列化程序库,呈现大型结果集的 HTTP API 响应现在更高效。与 v3.10.x 相比,此类查询的峰值内存占用也降低了两位数。
可以列出和关闭特定用户的连接的新端点。
一种配置身份验证超时的方法,很像 RabbitMQ 支持的其他一些协议。
依赖变化:ra 升级到 2.3.0;osiris 升级到 1.3.0;prometheus 升级到 4.9.0;jsx 被替换为 thoas
更多兼容性问题可查看发行公告


官方主页:http://www.rabbitmq.com/

该文章最后由 阿炯 于 2023-05-06 20:36:01 更新,目前是第 4 版。