导语
本文从AMQP协议(Advanced Message Queuing Protocol,高级消息队列协议)、消息功能、消费模型、金融级用法及其他功能点对比等概念介绍对RabbitMQ做了科普, 希望对各位深入理解RabbitMQ有帮助。
需要框架源码的朋友可以看我个人简介联系我。推荐分布式架构源码
AMQP协议概念
AMQP协议自身定义了很多概念,下面先对这些概念进行剖析,会更侧重从每个概念实体的作用域、职责范围、从属关系等维度进行介绍。
Connection
-
对应底层一个AMQP-Client到RabbitMQ-Broker的一个TCP连接。
-
这边要考虑两个端点问题,在TCP连接建立完成后,如下图所示,连接的目标Broker就已经确定是集群中的一台了,由于是长连接,除非断连重建,否则对端节点不可变。
-
所以从这里可以看出RabbitMQ相比Pulsar、RocketMQ不一样的地方在于,其是一种服务端寻址模型,以Client的视角来看,想要连接任意Exchange、Queue,只要连上任意一台Broker就行。
Channel
-
信道,可以理解为一种逻辑连接,体现了多路复用的设计思路。
-
支持串行执行,包括收和发的指令,可以理解为一种半双工模式的“虚拟网络通道”。
-
所有Exchange、Queue、Binding的操作都是在Channel之上进行的。
Vhost
-
等价于一种租户隔离概念,不同Vhost下可以创建同名Exchange、Queue,这样可以进行业务隔离。
-
RabbitMQ的权限隔离和权限维度控制的机制是在Vhost级别的。
-
Rabbit官方原生的全局Policy控制在Vhost级别。
Exchange
-
一个虚拟实体,声明不同消息的路由策略,自身不存储消息。
-
一个路由器,基于消息头部的RoutingKey和Header将消息路由到符合条件的具体的Queue。
-
支持单播和广播。
Queue
-
消息存储实体,是消息底层存储的容器,类似Pulsar的Topic。
-
单订阅模式,其下的Consumer分别消费到一部分消息。
-
和存储关联,因此有容量上限、TTL等存储层的特性。
-
支持多消费和独占消费,取决于你订阅时设置的参数。由于它是存储消息系统的消息,所以内部基于一个消息位点控制持久化消费进度,记录最后被消费并Ack的位置。
-
面向Consumer。
Binding
-
衔接Exchange和Queue的桥梁,本质是一个规则的声明。
-
一个Exchange下可以有多个Binding。
-
一个Queue也可以被多个Binding关联。
-
一个Exchange到一个Queue也可以声明多个Binding。
消息功能
下面介绍RabbitMQ官方所提供的的开源原生功能,我们知道,AMQP协议可以看做成一种可编程式的消息队列协议,可以基于其提供的基础模型,通过自己的巧妙搭配组合,构造出多种多样的业务模型。
消息结构
# publishInfo
exchange: amq.direct
immediate: false
mandatory: false
routingKey: test
# headerBody
bodySize: 1024
properties:
- contentType:
- encoding:
- deliveryMode:
- priority:
- correlationId:
- replyTo:
- expiration:
- messageId:
- timestamp:
- type:
- userId:
- appId:
- clusterId:
- headers: {}
# contentBody
二进制消息体bytes
每个消息分为三个部分,在网络层面即三个独立数据帧:
-
PublishInfo: 消息路由声明信息,可以联想成写电子邮件时填写的目标邮箱、是否接收回执等前置声明。
-
HeaderBody: 消息头部,用于存储RabbitMQ自身事先定义的声明,可以联想层HTTP协议的Header一样,此处可以放置一些对业务透明的上下文信息用于提供某种功能,比如分布式链路追踪的TraceId。
-
ContentBody: 消息体,无差别二进制数据块,服务端不感知其是否压缩、是否加密等,只进行透明的存储和读取投递。
work queue 工作队列
-
它是一种模型简化,发送消息时指定Exchange为空,RoutingKey为QueueName,Broker以后会直接把这个消息发送至目标Queue,这样对用户来说相当于没Exchange,他认为是直接用Queue来消费,就比较简单。
-
工作队列只适用于单订阅的场景,因为Queue只适用于单订阅
Publish/Subscribe 发布订阅模式
Queue不支持多订阅,通过转换思路实现:
-
一个Fanout类型的Exchange:相当于多订阅场景的Topic。
-
多个不同的Queue:绑定到该Exchange,相当于多订阅场景下的Subscription。
-
多个Consumer消费同一个Queue:常规场景多订阅。
-
每个Consumer各自消费一个Queue:实例级别广播。
- 对齐RocketMQ、Pulsar的多订阅消费、广播消费
Routing 路由模式
-
路由模式是用Rabbit最常用的一种模式。
-
Producer发布了一个Exchange,这个Exchange的类型是Direct,在Message中指定RoutingKey,并设置一个非空的值,接下来声明一些Queue,这些 Queue在声明和绑定Exchange的时候,需要指定Binding,消息在路由的时候判断消息里的RoutingKey和BindingKey是不是equal,如果是对等的就可以路由过来。
-
类似tag过滤的消息分发场景。
- 对齐rocketmq、pulsar的tag过滤消费
Topic 通配符模式
-
路由模式的升级版,支持通配符匹配。
-
Exchange类型为Topic。
-
匹配规则不是正则表达式,是AMQP自己的语法。
Header模式
-
不常用,匹配规则不基于RoutingKey,而是基于HeaderBody.Properties.Headers中的键值对。
-
支持完全匹配所有键值对。
-
支持只匹配一个键值对。
RPC模式
-
RPC模式并不常用,基于回复队列。
-
生产者和消费者采用一问一答的模式。
-
等价于RPC的request-response模型。
消费模型
消费模型也是使用一个消息系统所需要特别关心的一环,在业务的使用过程中,更多地会关注一条消息从生产到投递至消费者整个过程中都经历了什么,整个消息的声明周期是如何闭环的?
下面主要从TDMQ RabbitMQ版的实现来剖析RabbitMQ协议的消息生命周期。
从消息的生命周期看待消费模型
-
已投递未Ack:Consumer独占,直到Consumer触发Ack或者Consumer断开。
-
Ack消息:标记已消费,位点前进。
-
Nack消息:底层操作等价于Ack,会根据配置转发到死信Exchange,否则丢弃。
-
Requeue:消息放回队头,待下次投递。
从内部核心组件看消费模型
-
Queue:负责存储原始消息数据,按序存储。
-
RedeliveryTracker:负责记录Consumer端Requeue的消息,并触发重新投递,标识投递次数。
-
Dispatcher:负责管理连接Queue的所有Consumer,负责消息的负载均衡、分发、进度管理等。
-
Limiter:QoS限流器,基于Unack数限流,而不是QPS,呼应上方消息生命周期。
-
Unack Tracker:跟踪当前Channel中已投递未Ack的消息。
从这张图可以获取那些信息?
-
一个Queue可以被不同Connection连接、被同一个Connection的不同Channel连接。
-
一个Channel中可以启动两个Consumer连接同一个Queue。
-
QoS限流作用域为Channel,即一个Channel中创建的多个Cconsumer享有相同的配额。
-
如果BasicQoS Global设置为true,那么同一个Channel中的Consumer用尽配额,该Channel下的所有Consumer全部阻塞,无法接收新消息。
-
Unack追踪器也是Channel作用域,故一个Channel关闭,被该Channel独占的所有未Ack消息全部回收到Queue级别的跟踪器,进行全局重投递。
金融级用法
-
消息确认:发送反馈,给予Producer发送成功的确认。
-
备选Exchange:发送成功的消息无法匹配任何Binding的场景。
-
消息回退:消息无法匹配任何Binding时退回到Producer。
-
重投递:网络错误、Consumer端宕机、业务处理偶发错误等场景,重试消费恢复。
-
死信Exchange:业务多次重试、长时间无法成功,放入死信,待人工处理或者下一步的自动化修正or告警系统。
功能点对比
经过上述说明,你应该能利用RabbitMQ的功能点,结合自己的业务场景组织一个相对合理的生产消费拓扑。 除了上面提到的功能点,RabbitMQ本身还提供了很多其他功能,下面主要列举一部分对比,可供参考和借鉴:
通道类
- 注意:QoS机制RabbitMQ的实现是和标准AMQP协议有出入的,我们选择对齐RabbitMQ而不是AMQP规范,我们也认为RabbitMQ的模式较合理