高清思维导图见:https://gitee.com/ryan_july/clouding_img/raw/master/img/RabbitMQ 单机.png
RabbitMQ 单机
-
-
mandatory
-
true:如果 exchange 根据自身类型和消息 routeKey 无法找到一个符合条件的 queue,那么会调用 basic.return 方法将消息返还给生产者。
-
false:出现上述情形 broker 会直接将消息扔掉。
-
-
immediate
-
true:如果exchange在将消息 route 到 queue(s) 时发现对应的 queue 上没有消费者,那么这条消息不会放入队列中。当与消息 routeKey 关联的所有 queue(一个或多个) 都没有消费者时,该消息会通过basic.return 方法返还给生产者。
-
3.0 以后废弃。
-
immediate 废弃的原因,官方的解释是:该参数会影响镜像队列的性能,增加代码复杂性。建议通过 TTL(过期时间) 和 DLX(死信队列) 替代。
-
-
-
备份交换器(Alternate Exchange)
-
结构
-
使用方式
-
在声明交换器(Channel.exchangeDeclare)的时候,通过添加 alternate-exchange参数实现;
Map args = new HashMap<>();args.put("alternate-exchange", "ae"); channel.exchangeDeclare("ae", "fanout", true, false, null); channel.exchangeDeclare("exchange_demo", "direct", true, false, args); channel.basicPublish("exchange_demo", "normal", null , "hello world".getBytes()); channel.basicPublish("exchange_demo", "normal" + "_ae", null , "hello world ae".getBytes());
-
或者通过策略 Policy 的方式。
rabbitmqctl set_policy AE "^exchange_demo$" '{"alternate-exchange", "ae"}';
-
同时使用时,参数的优先更高。
-
-
本质上,备份交换器只是普通交换器,只是通过指定参数,将两个交换器进行一种存在“主次”的绑定。只能从主流向次,仅针对一次绑定而言。
-
ps:当然也可以双写绑定,你中有我,我中有你。只是注意,路由键注定能匹配到一个交换器的队列,否则依然会是丢失。因此也建议,将备份交换器的类型设置为 “fanout”。
-
特殊情况:请记住备份交换器如果出现特殊情况,不会抛异常,最终的结果必然是丢失消息
-
备份交换器不存在,客户端和 Broker 无异常,丢失消息;
-
备份交换器无绑定队列,同上;
-
备份交换器无匹配队列,同上;
-
如果和 mandatory 同时使用,mandatory 失效。其实结果就是丢失消息,因为是不会return了。
-
-
-
过期时间TTL
-
设置消息
同时使用时,以两者较小的为准。一旦消息超过 TTL,将会成为 死信。如果没有设置死信队列,将无法被消费者收到。
-
1.通过队列属性设置:将应用在队列中的所有消息。
-
在声明对了(Channel.queueDeclare)的时候,通过添加 x-message-ttl参数实现;
-
或者通过策略 Policy 的方式。单位是毫秒。
-
如果TTL 设置为 0,除非当时刚好可以将消息投递到消费者,否则将被丢失。和 immediate 很像,本质上都只在队列逗留一会。但是 immediate 会将消息返回到生产者。
-
-
2.通过消息本身单独设置。
-
在Channel.basicPublish的时候,通过加入 expiration 的参数设置。
-
通过队列设置的方式,一旦消息ttl到期,就会被队列删除。
-
消息单独设置的方式,即使消息过期,也不会马上删除,而是等到消息投递时进行检查再放弃投递。
-
因为第一种方式,队列的过期消息一定在队头,只要定期从队头开始扫描即可;而第二种方式就需要扫描全队。
-
-
-
设置队列
-
在声明队列(Channel.queueDeclare)的时候,通过添加 x-expires参数实现。单位依然是毫秒。但是不能设置为0。
-
概念:队列自动删除前处于未被使用的时间。
-
队列没有任何消费
-
队列没有被重新声明
-
过期时间段内没有被调用过 Basic.Get。即没有消费者主动拉取消息。
-
-
应用场景:可以应用于类似 RPC 的回复队列。在 RPC 中,很多队列会被创建,都却从未被使用。
-
RabbitMQ 保证过期时间到达后会将队列删除,但不保证及时性。服务器重启后,会重新计算过期时间。
-
-
-
高级队列
-
死信队列
-
死信
-
消息过期:消息存活时长超过设置的 TTL
-
消息被拒绝:消息被消费者拒绝消费(Basic.Reject/Basic.Nack),并且requeue = false(即无法再次入队)
-
队列达到最大长度:队列是消息存储的地方,队列没有多余的空间了。
-
-
DLX(Dead-Letter-Exchange),死信交换机:本质上也只是个普通交换机,不普通在于它可以通过队列属性,被关联到任意队列上。
-
死信队列:与死信交换机绑定的队列,接收被正常队列抛弃,被消费者抛弃消息的一个普通队列。
-
死信如何到达死信队列
-
原始队列必须设置属性来为其指定 DLX。
args.put("x-dead-letter-exchange", "test_dlx"); // 也可以指定死信在 DLX 的路由键,否则直接使用原队列的路由键 args.put("x-dead-letter-routing-key", "test_dlx_routing"); channel.queueDeclare(..., args);
-
可以指定死信在 DLX 的路由键,否则直接使用原队列的路由键
-
-
-
延迟队列
-
通过 DLX +TTL 实现
-
消费者消费死信队列的消息:定时消息到达没有消费者的普通队列,等待TTL 到期,消息被转发到死信队列
-
缺点:普通队列的消息过期顺序是按照队列头部顺序的过期的。如果队头的消息 A 是五分钟过期, 后面 B 是一分钟过期,那必须要 A 的过期触发后才能触发 B。导致 B 的过期不准时,所以尽量每个队列的消息过期时间都是一样的,不同过期时间可以设置不同队列
-
-
延迟插件:Exchange 设置 x-delayed-type 参数
-
-
优先级队列:高优先级的消息优先被消费
-
设置队列:创建队列设置 x-max-priority 属性(管理页面会展示 Pri 标志),优先级范围:0~10
-
设置消息属性:AMQP.BasicProperties.priority,优先级范围:0~队列优先级
-
-
RPC 实现:简单来说,消息有去有回
-
消费回复的关键:回调队列,让消费者将消息指定推送到回调队列,而生产者消费回调队列
-
设置方式:消息属性
-
AMQP.BasicProperties.replyTo:响应的回调队列
-
AMQP.BasicProperties.correlationId:消息回执ID,关联请求消息和响应消息。
-
-
-
-
消息传输保障
-
一般消息中间件的保障
-
At most once: 最多一次。消息可能会丢失,但绝不会重复传输。
-
At least once: 最少一次。消息绝不会丢失,但可能会重复传输。
-
Exactly once: 恰好一次。每条消息肯定会被传输一次且仅传输一次。
-
-
RabbitMQ 支持其中的"最多一次"和"最少一次"(就是消息可靠性保证了)。
-
-
消息可靠性
-
1.生产者确认(生产者到交换机,可搭配重试、告警)
确保消息从生产者发出,能够安全达到 Broker
-
事务机制和发送者确认机制是互斥的,不能同时打开。
-
事务机制
-
API
-
channel.txSelect:启动事务
-
channel.txCommit:提交事务。如果成功,说明消息已经到达 MQ。
-
channel.txRollback:回滚事务。如果出现 MQ 崩溃或其他原因,可以通过该方法回滚。
-
-
流程
-
1.客户端(生产者)发送给mq服务器Tx.Select 消息,告诉服务器开启事务
-
2. Broker 返回 Tx.Select-Ok 告知客户端事务模式已开启
-
3. 客户端(生产者)开始推送消息
-
3.客户端(生产者)发送Tx.Commit 消息提交事务(回滚是 Tx.Rollback)
-
4.mq服务器返回Tx.Commit-Ok 告知事务提交成功(回滚是 Tx.Rollback-Ok)
-
-
原理:实际就是通过额外的消息通信,来反馈消息到达 MQ。
-
由于相关API 会将信道设置为事务模式,所以后面两个 API 相当于取消设置。
-
整个流程会多四次消息通信,所以会损耗性能。
-
-
-
发送者确认机制
-
相比较事务机制,更加轻量级。
-
相比较事务发送完必须等待回应才能发送下一条信息,发送者确认机制是异步的,一旦发布一条消息之后,在等待确认返回的同时,是可以继续发送下一条消息。
-
生产者得到确认(nack 说明消息丢失,也是一种确认)时,再回调处理方法。这里可以是异步的。
-
对比发送消息的步骤,事务是发送 + commit/rollback;而发送者确认就只有发送,少了一次命令。
-
-
流程
-
1.设置信道为 confirm 模式
-
2.信道上的所有消息都会被指派 唯一 ID,从 1 开始。
-
3.消息被投递到所有匹配的队列(开启持久化的话,则确保写入磁盘),broker 返回带着唯一 ID 的 Basic.Ack 给生产者
-
注:
-
RabbitMQ 回传给生产者的确认消息中的delivery Tag 包含了确认消息的序号。
-
如果生产者设置 channel设置channel . basicAck 方法中的multiple 参数,表示到这个序号之前的所有消息都己经得到了处理.
-
-
-
API
-
channel.confirmSelect 方法(Confirm.Select 命令) :将信道设置为 confirm 模式, Broker 返回 Confirm.Select-OK 。
-
channel.WairFormConfirms(可设置超时或 Nack 抛异常):等待 Broker返回确认,前提是信道为 confirm 模式,否则抛异常。
-
-
-
-
2.备份交换器(Alternate Exchange) or mandatory 属性(交换机到队列)
-
3.消息持久化
-
交换机持久化
-
实现方式:声明交换器时,durable 参数设置 true。
-
效果:Broker 重启后,交换器元数据不丢失。相当于重启重建交换器。
-
-
队列持久化
-
实现方式:声明队列时,durable 参数设置 true。
-
效果:Broker 重启后,队列元数据不丢失。注意:如果队列没有持久化,即使消息设置持久化也是没有用的。持久化的消息会伴随着队列的丢失而丢失。
-
-
消息持久化
-
实现方式:消息投递属性(BasicProperties.deliveryMode = 2)。
-
前提是队列必须持久化。
-
注意
-
如果所有消息持久化,那么会影响吞吐量。毕竟写入磁盘的效率低。可以只持久化重要的消息。
-
即使消息持久化,也不一定代表数据不丢失。要注意消费者保证消费(手动 ack)。
-
-
持久化策略:并不一定是消息达到队列时同步写入,有一定延迟调用 fsync 写入磁盘。解决方案:镜像队列。
-
-
-
4.过期时间TTL+ 死信队列 (过期消息处理、无消费者处理)
-
5.消费者确认、消费幂等(队列到消费者)
-
autoAck
-
true:Broker 发送消息给消费者后,自动确认并删除
-
false:必须等消费者返回确认消息后,才会删除
-
-
一旦投递给消费者,就不在乎消息过期时间以及消费时长(不会超时),只在于消费者的连接是否断开
-
一旦连接断开,消息会重新入队,等待投递给消费者。
-
-
6.退回队列重试(消费者消费失败)
-
7.业务应用全局监控(避免消息意外丢失、处理异常)
-
异常告警,人工处理
-
业务数据对账,定时重试
-
-
8.单机故障问题(镜像队列)
-
-
消息分发
-
分发方式
-
当一个队列有多个消费者时,消息将以轮询的方式分发给消费者,一条消息只会发给一个消费者。
-
当消息生产速度大于消费速度时,易于增加消费者来加快消费速度。
-
但是无论消费者是否消费完成,broker 都会按照轮询规则持续分发消费者,而每个消费者的消费速度不一致的话,就容易失衡,使得整体应用吞吐量下降。
-
-
basicQos :限制信道上消费者持有最大未确认消息数量,解决消息消费能力失衡。
-
只适用推的消费方式。
-
流程
-
RabbitMQ 会保存一个消费者的列表,每发送一条消息都会为对应的消费者计数。
-
如果达到了所设定的上限,那么RabbitMQ 就不会向这个消费者再发送任何消息。
-
直到消费者确认消息,对应计数减 1,继续发送消息。
-
-
channel.basicQos(int prefecthCount)
-
prefecthCount:为 0 代表没有上限。
-
重载参数
-
global
-
prefectSize:消费者接受未确认消息的总体大小上限,单位为 B。0 代表没有上限。
-
-
缺点:需要额外维护和协调多个队列的消息分发,降低性能。
-
-
-
设置 basicQos 的参考:Some queuing theory: throughput, latency and bandwidth | RabbitMQ - Blog
-
-
消息顺序性
-
理论上的顺序性是有前提的。
-
不考虑消息丢失、网络故障等异常
-
不使用高级特性,比如延迟队列、优先级队列等
-
只有一个消费者(多个消费者可能消费速度不一致,详情可见消息分发)
-
最好只有一个生产者(没法保证所有生产者消息到达 Broker 的顺序,和全局发送顺序是一致的,自然也就没法保证消费顺序)
-
-
打破顺序性
-
生产者发送错序
-
事务机制下,发送消息出现异常导致回滚;重新补发在另外一个线程执行,那就出现错序了(因为原始发送线程可能已经再继续发送下一条消息了)。
-
发送者确认机制下,发生超时、中断或 Broker Nack,如果补发异步,也是同理。
-
-
队列错序
-
消息不同过期时间 + 死信队列,也就组成了延迟队列,那消费者消费延迟队列时,根本无法保证与发送顺序一致。
-
优先级队列
-
-
消费错序
-
多个消费者在消费消息时,消费失败同时要求重新入队。那重新入队的消息是无法保证发给原来的消费者,就算可以,也很难保证顺序性。
-
-
-
-
弃用QueueingConsumer(正经人谁用原生 API,所以了解一下就好了)
-
内存溢出问题:本地消息暂存使用 LinkedBlockingQueue。必须搭配 basicQos 使用。
-
QueueingConsumer 会拖累同一个Connection 下的所有信道,使其性能降低。
-
同步递归调用 QueueingConsumer 会产生死锁。
-
RabbitMQ 的自动连接恢复机制(automatic connection recovery)不支持QueueingConsumer 的这种形式。
-
QueueingConsumer 不是事件驱动的。
-