文章目录

  • 1、RabbitMQ如何保证消息不丢失
    • 1.1 生产者确认机制
    • 1.2 消息持久化
    • 1.3 消费者确认机制
  • 2、RabbitMQ消息重复消费
  • 3、RabbitMQ延迟队列
    • 3.1 死信交换机
    • 3.2 TTL
    • 3.3 延迟队列插件
  • 4、处理RabbitMQ消息堆积
    • 4.1 惰性队列
  • 5、RabbitMQ的高可用
    • 5.1 普通集群
    • 5.2 镜像集群
    • 5.3 仲裁队列
  • 6、面试

MQ的使用场景:

  • 异步发送(验证码、短信、邮件)
  • MySQL和Redis,MySQL和ES的数据同步
  • 分布式事务(前篇提到的借呗和支付宝两个系统)
  • 削峰填谷

1、RabbitMQ如何保证消息不丢失

丢消息的几个可能点:

  • 消息未达到交换机
  • 消息未达到队列
  • RabbitMQ宕机,导致队列中的消息丢失
  • 消费者拿走消息后,没来得及消费就宕机了

【Java面试】十一、RabbitMQ相关插图
针对以上可能丢数据的点,分别解决如下:

1.1 生产者确认机制

RabbitMQ提供了publisher confirm机制,来保证消息往队列发的过程不丢失。消息发送完成后,会返回一个结果给发送者:

  • 如果消息成功到达队列,返回给生产者一个回执:ack publish-confirm
  • 如果消息发送失败,且是未到达交换机,返回:nack publish-confirm
  • 如果消息发送失败,且是到了交换机但未到达队列,返回:ack publish-return

【Java面试】十一、RabbitMQ相关插图(1)
消息发送失败后:

  • 回调方法,即时重发
  • 记录一个日志
  • 把这条笑死保存到数据库,然后定时再重发,直到发成功就删除表中的数据

1.2 消息持久化

以上,保证了消息发送的可靠性。消息到达队列后,MQ的消息默认放内存,宕机则队列中的消息丢失。 ⇒ 交换机、队列、消息数据持久化

  • 交换机持久化

【Java面试】十一、RabbitMQ相关插图(2)

  • 队列持久化

【Java面试】十一、RabbitMQ相关插图(3)

  • 消息持久化,SpringAMQP 中的的消息默认是持久的,可以通过 MessageProperties 中的 DeliveryMode来配置

【Java面试】十一、RabbitMQ相关插图(4)

1.3 消费者确认机制

RabbitMQ支持消费者确认机制:消费者处理消息成功后,给MQ发送ack回执,MQ收到ack回执才删除消息。具体,SpringAMQP有三种模式可选择:

  • manual:业务代码执行结束后,调用api,手动发送ack
  • auto:Spring检测Listener代码是否出现异常,无则自动发送ack,有则抛出异常并返回nack(常用)
  • none:关闭ack,MQ认为,消息你拿走了,就是消费成功了,你前脚拿走,MQ后脚删除消息

此外,选择auto时,还可以利用Spring的retry机制,在消费发生异常时,重试一定次数,仍然失败则扔到一个异常交换机里,后续人工处理

【Java面试】十一、RabbitMQ相关插图(5)

2、RabbitMQ消息重复消费

消费者确认机制选择了自动确认auto模式,设置了retry重试。此时,消费者消费完消息后,在发ack之前,网络抖动或者消费者服务挂了,ack没发出去。

消费者服务重新running后,就会重复消费这条消息。

【Java面试】十一、RabbitMQ相关插图(6)

解决方式:

  • 1)每条消息带一个唯一的业务ID,比如订单ID,消息消费前,判断这个ID是否已存在,存在则直接return
  • 2)按幂等性处理(考虑分布式锁、数据库锁等方案)

3、RabbitMQ延迟队列

进入队列的消息会被延迟消费。场景:

  • 超时订单:30分钟内支付
  • 限时优惠:商品优惠还剩2天10小时
  • 定时发布:明早八点发布

【Java面试】十一、RabbitMQ相关插图(7)
延迟队列 = 死信交换机 + TTL(过期时间)

3.1 死信交换机

队列中的消息,符合一条,即为死信:

  • 消费者使用basic.reject或 basic.nack声明消费失败,且消息的requeue参数设置为false(即消费者明确拒绝把它重新放回队列)
  • 过期消息,超时无人消费
  • 要投递的队列满了,最早的消息可能成为死信

成为死信的消息,可能被丢弃。此时,如果这个队列配置了dead-letter-exchange属性,指定了一个交换机,则死信被投递到这个交换机(即死信交换机,Dead Letter Exchange,DLX)

【Java面试】十一、RabbitMQ相关插图(8)

声明一个队列simple.queue,其死信交换机为dl.direct

【Java面试】十一、RabbitMQ相关插图(9)

3.2 TTL

Time-To-Live,存活时间,队列的消息,超过TTL还未被消费,成为死信。TTL超时分为:

  • 消息所在队列设置了存活时间
  • 消息本身设置了存活时间

【Java面试】十一、RabbitMQ相关插图(10)
如上,同时设置了队列存活时间和消息本身存活时间,自然以最短的为准。队列ttl.queue绑定了死信交换机dl.direct,dl.direct又关联了队列,最终死信会被绑定了死信队列的消费者处理 ⇒ 死信交换机 + TTL = 延迟队列

【Java面试】十一、RabbitMQ相关插图(11)
定时发布时,根据定时的时间戳设置其TTL,写发布逻辑,消费死信队列。时间一到,发布信息进入死信队列,实现定时发布。

3.3 延迟队列插件

在RabbitMQ安装DelayExchange插件:

https://www.rabbitmq.com/community-plugins.html

使用该插件时,正常声明一个交换机,并将其delayed属性设置为true,以下声明 + 消费 延迟队列的消息:

【Java面试】十一、RabbitMQ相关插图(12)

发消息时:setHeader添加x-delay头,指定超时时间

【Java面试】十一、RabbitMQ相关插图(13)

4、处理RabbitMQ消息堆积

生产快,消费慢,消息堆积,产生死信。解决方式:

  • 增加消费者实例
  • 消费者实例内开启线程池,加快处理
  • 扩大队列容量,提高堆积上限 ⇒ 惰性队列

4.1 惰性队列

特点:

  • 接收到的消息不再存内存,放磁盘
  • 消费者消费消息时,从磁盘读到内存
  • 支持百万条消息存储

声明队列时,调用方法,设置x-queue-mode为lazy,即为惰性队列:

【Java面试】十一、RabbitMQ相关插图(14)

@RabbitListener里的写法:

【Java面试】十一、RabbitMQ相关插图(15)

5、RabbitMQ的高可用

生产环境部署RabbitMQ集群,有两种种:

  • 普通集群
  • 镜像集群

5.1 普通集群

标注集群,特点:

  • 集群各个节点之间共享交换机、队列的元信息,但不包含队列中的消息(节点1有队列test.queue1,节点2、节点3里是队列test.queue1的引用,不包括队列中的消息)
  • 在集群节点3访问queue1队列,但节点3只有queue1的引用,实际在节点1,则会把数据传递到节点1处理
  • 某个节点宕机,则其上队列的消息丢失

【Java面试】十一、RabbitMQ相关插图(16)
丢东西,所以一般不采用普通集群

5.2 镜像集群

创建队列test.queue1的节点node1,为queue1队列的主节点,节点node2备份了queue1队列,为queue1队列的镜像节点。当然,节点node1,也可能是别的队列的镜像节点,相对概念。

【Java面试】十一、RabbitMQ相关插图(17)

  • 所有的操作,都是主节点完成,然后同步给镜像节点
  • 主节点宕机,镜像节点成为该队列的新的主节点

问题:主节点完成后,还没同步给镜像节点就宕机,会丢一点点数据 ⇒ 仲裁队列解决

5.3 仲裁队列

引入仲裁队列,做主从同步时,基于Raft协议,强一致。主节点宕机后,镜像节点利用仲裁队列来保证能正确复制。声明使用仲裁队列的写法:

【Java面试】十一、RabbitMQ相关插图(18)

6、面试

【Java面试】十一、RabbitMQ相关插图(19)
【Java面试】十一、RabbitMQ相关插图(20)

【Java面试】十一、RabbitMQ相关插图(21)

本站无任何商业行为
个人在线分享 » 【Java面试】十一、RabbitMQ相关
E-->