《RabbitMQ 实战指南》单机能力思维导图

高清思维导图见: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 不是事件驱动的。

猜你喜欢

转载自blog.csdn.net/jiangxiayouyu/article/details/116895811