1、为什么在项目中要使用MQ?
关键词:解耦,异步,削锋
1)解耦。A系统开始调用B和C系统,如果采用普通远程调用(Feign接口)实现,以后A系统还需要调用D系统,修改A系统代码来实现对D系统的调用
如果改用MQ来实现A系统和B、C系统的调用,可以通过MQ的消息给BC系统通讯,以后扩展D系统时,只改变A系统发出的消息内容即可,调用D系统,而不需要A系统的代码。
2)异步。A系统调用B和C系统的业务方法时,需要采用同步调用(Feign),方法调用的总耗时时两个系统调用方法时长总和,如果B或C系统存在耗时业务,导致整个方法调用耗时过长,影响用户响应体验。
如果改用MQ实现系统间调用,生产者A系统,只需要把消息发送MQ,MQ接收消息后,立即响应,而无需要等待消费者的响应,这样大大缩短调用耗时,提高用户体验。
3)削锋。在类似秒杀的高并发业务中,如果直接把大量请求打到数据库,可能导致数据库崩溃。这时,可以在存入数据库之前把消息先存入MQ,通过MQ排队执行下单请求,这样降级对MySQL压力。
解耦:生产端和消费端不需要相互依赖
异步:生产端不需要等待消费端响应,直接返回,提高了响应时间和吞吐量
削峰:在类似秒杀的高并发业务中,如果直接把大量请求打到数据库,可能导致数据库崩溃。这时,可以在存入数据库之前把消息先存入MQ,通过MQ排队执行下单请求,这样降级对MySQL压力。
2、如何保证MQ消息不丢失?(MQ如何保证消息的可靠性?)
要保证消息不丢失就需要牺牲系统的性能(生产者的处理逻辑变复杂,MQ 的吞吐量降低,消费者消费速度下降等),
所以需要结合具体的业务场景来决定是不是需要百分百保证消息不丢失。通常而言,对于核心链路:如订单、交易等相关的业务,基本都需要保证保证消息百分百不丢失。
三个维度:生产者,MQ服务器,消费者
1)利用生产者确认机制保证可靠消息
首先,生产者发送消息给交换机,MQ会给生产者发送ACK确认,我们可以使用ConfirmCallback的回调函数接受ACK确认信息
在ConfirmCallback中,
判断ack为true,代表消息成功投递到交换机
判断ack为false,代表消息投递交换机失败,我们可能根据业务需求存储到失败消息表或者进行消息重投。
其次,MQ服务器创建的交换机,队列,消息都有持久化的特性。我建议三者都设置为持久化,这样在MQ服务器意外宕机或重启,消息都不会丢失。通常Spring整合RabbitMQ的API,三者都默认为持久化。
最后,消费者和MQ之间存在消息确认机制,可以利用确认机制保证消息可靠性。
在消费者的配置中,通常开启消费者确认auto自动确认模式(默认值),MQ只有在消费者执行业务逻辑成功后,才会发送ACK给MQ,MQ才会移除信息。如果失败,会重新放回MQ队列中。
3、如何保证消息不被重复消费?(如何保证消息消费的幂等性?)
解决消息重复消费,其实就是保证消息的消费幂等性。
第一句话:采用消息幂等性判断。(去重判断)
1)首先,我们应该给每条消息分配一个全局唯一的ID,标记消息的唯一性
2)
方案一:MySQL的唯一约束来去重(并发不高)
我们可以在消费方添加一张消息去重表,该表添加消息ID字段,且该字段设置为unique唯一,每次执行消费者逻辑前,往去重表插入当前消息ID,如果可以成功插入,代表没消费过,则正常执行消费逻辑。如果报错,代表消息重复消费了,则不执行消费逻辑。
方案二:Redis的key唯一性来去重(并发高)
我们每次执行消费者逻辑前,往当前消息ID存入Redis作为key(调用redisTemplate.opsForValue().setIfAbabsent(key,value)),根据返回布尔结果,如果为true,代表消费没有消费过,可以执行消费逻辑,如果返回false,代表消息重复执行了,则不能执行消费逻辑。
底层指令:setnx key value 1 代表key不存在 0 代表key存在
方案三:多版本(乐观锁)机制
给业务数据添加一个版本号,每次更新数据前,比如当前版本和消息中的版本是否一致,如果一致就更新数据并且版本号+1,如果不一致就不跟新。这有点类似乐观锁处理机制。
4、请问什么是死信队列?项目中一般用来做什么?
死信队列:
1)普通消息被拒绝了
2)普通消息设置了TTL过期时间且到期了
3)如果达到了队列的上限,后续的消息也会进行死信
死信队列的应用场景:
利用死信队列+TTL过期时间,完成延迟任务执行,如 下单后,不付款20分钟取消订单。
下单后,1分钟后自动发送催付款短信给用户
5、请问解决MQ消息堆积问题?
1)创建多个消费者同时消费
2)利用多线程并发处理消费任务
3)采用MQ集群
