持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情
队列是具有两个主要操作的顺序数据结构:入队和出队。RabbitMQ中的队列是FIFO(先进先出)。一些队列因为一些特性,即消费者的优先级和重新排队,会影响消费者消费的顺序。
Classic经典队列
这个是RabbitMQ最经典的队列类型。在单机环境中,拥有比较高的消息可靠性。
我们在创建队列的时候,根据上图可以看到,经典队列可以选择是否持久化(Durability)以及是否自动删除(Auto delete)两个属性。
Durability:是否持久化,可选项为持久化(Durable)和 临时(Transient)。Durable相对消息安全性更高。但是同时需要有更多的IO操作,所以生产和消费消息的性能,相比Transient会比较低。Auto delete:是否自动删除,如果选择是,则消息会被其中一个消费者消费之后,队列会自动销毁,其他消费者也会断开连接。一般不会自动删除。
Quorum仲裁队列
仲裁队列,是RabbitMQ从3.8.0版本引入的新的队列类型,整个3.8.X版本,也是针对仲裁队列进行完善和优化。Quorum相比Classic在分布式环境下对消息的可靠性保障更高。官方文档中表示,未来会使用Quorum代替Classic。
Quorum是基于Raft一致性协议实现的一种新型的分布式消息队列,他实现了持久化,多备份的FIFO队列,主要就是针对RabbitMQ的镜像模式设计的。
简单理解就是Quorum队列中的消息需要有集群中多半节点同意确认后,才会写入到队列中。这种队列类似于RocketMQ当中的DLedger集群。这种方式可以保证消息在集群内部不会丢失。同时,Quorum是以牺牲很多高级队列特性为代价,来进一步保证消息在分布式环境下的高可靠。
从整体功能上来说,Quorum队列是在Classic经典队列的基础上做减法,因此对于RabbitMQ的长期使用者而言,其实是会影响使用体验的。他与普通队列的区别:
| 特性 | Classic | Quorum |
|---|---|---|
非持久化队列(Non-durable queues) | 支持 | 不支持 |
独占队列(Exclusivity) | 支持 | 不支持 |
每条消息的持久化(Per message persistence) | 每条消息 | 总是 |
会员变更(Membership changes) | 自动 | 手动 |
消息TTL(Message TTL) | 支持 | 支持(3.10版本开始) |
队列TTL(Queue TTL) | 支持 | 支持 |
队列长度限制(Queue length limits) | 支持 | 支持 |
懒加载(Lazy behaviour) | 支持 | 始终 |
消息优先级(Message priority) | 支持 | 不支持 |
消费者优先级(Consumer priority) | 支持 | 支持 |
死信交换(Dead letter exchanges) | 支持 | 支持 |
毒消息处理(Poison message handling) | 不支持 | 支持 |
全局Qos(Global QoS Prefetch) | 支持 | 不支持 |
从上图就能看到,Quorum大部分功能都是在Classic基础上做减法,比如Non-durable queues表示是非持久化的内存队列。Exclusivity表示独占队列,即表示队列只能由声明该队列的Connection连接来进行使用,包括队列创建、删除、收发消息等,并且独占队列会在声明该队列的Connection断开后自动删除。
其中有个特例就是这个Poison Message。所谓毒消息是指消息一直不能被消费者正常消费(可能是由于消费者失败或者消费逻辑有问题等),就会导致消息不断的重新入队,这样这些消息就成为了毒消息。这些读消息应该有保障机制进行标记并及时删除。
Quorum队列会持续跟踪消息的失败投递尝试次数,并记录在x-delivery-count这样一个头部参数中。然后,就可以通过设置 Delivery limit参数来定制一个毒消息的删除策略。当消息的重复投递次数超过了Delivery limit参数阈值时,RabbitMQ就会删除这些毒消息。当然,如果配置了死信队列的话,就会进入对应的死信队列。
Quorum队列更适合于 队列长期存在,并且对容错、数据安全方面的要求比低延迟、不持久等高级队列更能要求更严格的场景。例如 电商系统的订单,引入MQ后,处理速度可以慢一点,但是订单不能丢失。
也对应以下一些不适合使用的场景:
- 一些临时使用的队列:比如
transient临时队列,exclusive独占队列,或者经常会修改和删除的队列。 - 对消息低延迟要求高: 一致性算法会影响消息的延迟。
- 对数据安全性要求不高:
Quorum队列需要消费者手动通知或者生产者手动确认。 - 队列消息积压严重 : 如果队列中的消息很大,或者积压的消息很多,就不要使用
Quorum队列。Quorum队列当前会将所有消息始终保存在内存中,直到达到内存使用极限。
Stream队列
Stream队列是RabbitMQ自3.9.0版本开始引入的一种新的数据队列类型,也是目前官方最为推荐的队列类型。这种队列类型的消息是持久化到磁盘并且具备分布式备份的,更适合于消费者多,读消息非常频繁的场景
Stream队列的核心是以append-only只添加的日志来记录消息,整体来说,就是消息将以append-only的方式持久化到日志文件中,然后通过调整每个消费者的消费进度offset,来实现消息的多次分发。这种队列提供了RabbitMQ已有的其他队列类型不太好实现的四个特点:
- 大规模分发(
large fan-outs)
当想要向多个订阅者发送相同的消息时,以往的队列类型必须为每个消费者绑定一个专用的队列。如果消费者的数量很大,这就会导致性能低下。而Stream队列允许任意数量的消费者使用同一个队列的消息,从而消除绑定多个队列的需求。
- 消息回溯(
Replay/Time-travelling)
RabbitMQ已有的这些队列类型,在消费者处理完消息后,消息都会从队列中删除,因此,无法重新读取已经消费过的消息。而Stream队列允许用户在日志的任何一个连接点开始重新读取数据。
- 高吞吐性能(
Throughput Performance)
Stream队列的设计以性能为主要目标,对消息传递吞吐量的提升非常明显。
- 大日志(
Large logs)
RabbitMQ一直以来有一个让人诟病的地方,就是当队列中积累的消息过多时,性能下降会非常明显。但是Stream队列的设计目标就是以最小的内存开销高效地存储大量的数据。
整体上来说,RabbitMQ的Stream队列,其实有很多地方借鉴了其他MQ产品的优点,在保证消息可靠性的基础上,着力提高队列的消息吞吐量以及消息转发性能。因此,Stream也是在试图解决一个RabbitMQ一直以来,让人诟病的缺点,就是当队列中积累的消息过多时,性能下降会非常明显的问题。
但是,从整体功能上来讲,队列只不过是一个实现FIFO的数据结构而已,这种数据结构其实是越简单越好。而当前RabbitMQ区分出这么多种队列类型,其实极大的增加了应用层面的使用难度,应用层面必须有一些不同的机制兼容各种队列。所以,在未来版本中,RabbitMQ很可能还是会将这几种队列类型最终统一成一种类型。例如官方已经说明未来会使用Quorum队列类型替代经典队列,到那时,应用层很多工具就可以得到简化,比如不需要再设置durable和exclusive属性。虽然Quorum队列和Stream队列目前还没有合并的打算,但是在应用层面来看,他们两者是冲突的,是一种竞争关系,未来也很有可能最终统一保留成一种类型。