【Java面试】十一、RabbitMQ相关

文章目录

MQ的使用场景:

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

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

丢消息的几个可能点:

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

针对以上可能丢数据的点,分别解决如下:

1.1 生产者确认机制

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

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

消息发送失败后:

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

1.2 消息持久化

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

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

1.3 消费者确认机制

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

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

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

2、RabbitMQ消息重复消费

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

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

解决方式:

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

3、RabbitMQ延迟队列

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

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


延迟队列 = 死信交换机 + TTL(过期时间)

3.1 死信交换机

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

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

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

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

3.2 TTL

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

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

如上,同时设置了队列存活时间和消息本身存活时间,自然以最短的为准。队列ttl.queue绑定了死信交换机dl.direct,dl.direct又关联了队列,最终死信会被绑定了死信队列的消费者处理 ⇒ 死信交换机 + TTL = 延迟队列

定时发布时,根据定时的时间戳设置其TTL,写发布逻辑,消费死信队列。时间一到,发布信息进入死信队列,实现定时发布。

3.3 延迟队列插件

在RabbitMQ安装DelayExchange插件:

java 复制代码
https://www.rabbitmq.com/community-plugins.html

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

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

4、处理RabbitMQ消息堆积

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

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

4.1 惰性队列

特点:

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

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

@RabbitListener里的写法:

5、RabbitMQ的高可用

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

  • 普通集群
  • 镜像集群

5.1 普通集群

标注集群,特点:

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

丢东西,所以一般不采用普通集群

5.2 镜像集群

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

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

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

5.3 仲裁队列

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

6、面试


相关推荐
confiself13 分钟前
大模型系列——LLAMA-O1 复刻代码解读
java·开发语言
Wlq041517 分钟前
J2EE平台
java·java-ee
ZL不懂前端18 分钟前
Content Security Policy (CSP)
前端·javascript·面试
XiaoLeisj24 分钟前
【JavaEE初阶 — 多线程】Thread类的方法&线程生命周期
java·开发语言·java-ee
豪宇刘39 分钟前
SpringBoot+Shiro权限管理
java·spring boot·spring
Elaine20239143 分钟前
02多线程基础知识
java·多线程
gorgor在码农1 小时前
Redis 热key总结
java·redis·热key
测试界萧萧1 小时前
外包干了4年,技术退步太明显了。。。。。
自动化测试·软件测试·功能测试·程序人生·面试·职场和发展
百事老饼干1 小时前
Java[面试题]-真实面试
java·开发语言·面试
customer081 小时前
【开源免费】基于SpringBoot+Vue.JS医院管理系统(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·开源·intellij-idea