1.为什么在项目中使用MQ
1)解耦:
A系统开始调用B和C系统,如果采用普通远程调用(Feigin接口实现),以后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的压力.
2.如果保证MQ消息不丢失(如果保证消费的可靠输出?)
就拿RabbitMQ来举例吧:
RabbitMQ针对消息传递过程中可能发生问题的各个地方,给出了针对性的解决方案:(四种情况)
- 生产者发送消息时可能因为网络问题导致消息没有到达交换机:
- RabbitMQ提供了publisher confirm机制
- 生产者发送消息后,可以编写ConfirmCallback函数
- 消息成功到达交换机后,RabbitMQ会调用ConfirmCallback通知消息的发送者,返回ACK
- 消息如果未到达交换机,RabbitMQ也会调用ConfirmCallback通知消息的发送者,返回NACK
- 消息超时未发送成功也会抛出异常
- RabbitMQ提供了publisher confirm机制
- 消息到达交换机后,如果未能到达队列,也会导致消息丢失:
- RabbitMQ提供了publisher return机制
- 生产者可以定义ReturnCallback函数
- 消息到达交换机,未到达队列,RabbitMQ会调用ReturnCallback通知发送者,告知失败原因
- RabbitMQ提供了publisher return机制
- 消息到达队列后,MQ宕机也可能导致丢失消息:
- RabbitMQ提供了持久化功能,集群的主从备份功能
- 消息持久化,RabbitMQ会将交换机、队列、消息持久化到磁盘,宕机重启可以恢复消息
- 镜像集群,仲裁队列,都可以提供主从备份功能,主节点宕机,从节点会自动切换为主,数据依然在
- RabbitMQ提供了持久化功能,集群的主从备份功能
- 消息投递给消费者后,如果消费者处理不当,也可能导致消息丢失
- SpringAMQP基于RabbitMQ提供了消费者确认机制、消费者重试机制,消费者失败处理策略:
- 消费者的确认机制:
- 消费者处理消息成功,未出现异常时,Spring返回ACK给RabbitMQ,消息才被移除
- 消费者处理消息失败,抛出异常,宕机,Spring返回NACK或者不返回结果,消息不被异常
- 消费者重试机制:
- 默认情况下,消费者处理失败时,消息会再次回到MQ队列,然后投递给其它消费者。Spring提供的消费者重试机制,则是在处理失败后不返回NACK,而是直接在消费者本地重试。多次重试都失败后,则按照消费者失败处理策略来处理消息。避免了消息频繁入队带来的额外压力。
- 消费者失败策略:
- 当消费者多次本地重试失败时,消息默认会丢弃。
- Spring提供了Republish策略,在多次重试都失败,耗尽重试次数后,将消息重新投递给指定的异常交换机,并且会携带上异常栈信息,帮助定位问题。
3.如果保证消息不被重复消费?(如果保证消息队列的幂等性)
首先呢,不被重复消费其实就是如何实现消息去重的判断;去重就要给每条消息设置一个全局的唯一的id标识,可以利用雪花算法子类的;
那么如何实现去重呢?首先说说我第一时间想到的方案:
方案一:利用MySQL建立一张表,将每条消息的id设置为unique,每次消费消息前都去带着id对这张表插入数据,如果说插入成功了,就表明在表中这条数据是没有的,如果是插入失败则表明该条消息已经存在;然后不走消费消息的逻辑就好了,直接结束;但是呢这种方法有个缺点就是:只适用于低并发量的情况,在高并发场景中,会造成数据库压力过大;
方案二:其实方案二的大致原理也是和方案一类似,但是作为储存的数据库换成了Redis;这里利用到了Redis的全局锁,也就是setnx那个指令,在只有当消息不存在的时候才能将key set进去,至于数据的key值就能用当前消息的唯一id,然后使用redisTemplate的setIfAbabsent方法,在执行消费逻辑前将这个key存入,如果返回true则表示该消息是唯一的,false则被消费过,直接跳过消费逻辑即可;用redis的优势就是比mysql承载的并发量可以很高;4.请问RabbitMQ如果保证高可用?
搭建MQ集群
在RabbitMQ3.8之前,在集群的基础上,创建镜像集群队列,可以让一个队列分配多个镜像副本,以作备份,这样在主队列失效后,镜像副本会自动提升为主队列,继续工作.
在RabbitMQ3.8之后,直接创建仲裁队列,仲裁队列包含镜像队列所有功能,而且比镜像队列更加可靠,在主从数据同步过程也不会丢失数据5.请问什么是死信队列?项目中一般用来做什么?
死信队列一般在下列条件下使用:
1) 普通消息被拒绝(消费者多次重试后失败)
2) 普通消息设置了过期时间到期
3) 如果队列容量达到上限,后续消息也会进行死信
死信队列应用场景
1) 我们在消费者重试次数耗尽后,给MQ发reject请求,让失败消息转入死信队列,通过死信队列完成兜底操作(记录数据库或通过运维人员)
2) 利用死信队列 + TTL过期时间,完成延迟任务执行,如下单后不付款20min后取消订单6. 如何解决MQ消息堆积问题?
1) 创建多个消费者同时消费
2) 利用多线程并发处理消费任务
3) 扩大队列容积,比如创建惰性队列(惰性队列优先把消息存储在磁盘,而非内存,这样就可以存储更多消息了)
4) 采用MQ集群7.RabbitMQ和Kafka的区别?
RabbitMQ:
优势:
1) 支持语言非常广
2) 稳定性好,采用Erlang语言开发
3) 吞吐量不算低,万级
4) RabbitMQ官方提供7中消息发送模式,开发者轻松选择合适的模式进行开发即可
缺点:
采用Erlang,太小众,研究源码很难
Kafka:
优势:
1) 高吞吐量,百万级
2) 稳定性好,采用zookeeper进行注册(Zookeeper采用CP模式,高一致模式)
3) 可以应用在大数据处理领域
缺点:
耦合zk,依赖zookeeper进行注册8.如何保证Kafka的消息是有序的?
1)kafka每个partition(分区)中的消息在写入时都是有序的,但是多个分区的消息是无序的.如果要保证一个topic消息是有序的,需要将partition数量设置为1
2)尽量采用同步发送方式9.为什么Kafka那么快
1)利用Partition(分区)实现并行处理
2)顺序写磁盘
3)充分利用Page Cache(减少磁盘擦写)
4)利用零拷贝技术(mmap),减少CPU上下文切换成本
5)消息的批处理
6)消息数据压缩10.介绍一下生产者向Kafka发送消息的执行流程?
1)从Kafka集群中查询到主题的主分区
1.1,Producer确定发送消息的主题
1.2,Producer发送消息key给kafka(如果没有key的话,随机选择一个分区来发送)
1.3,Kafka到指定的topic内,使用key进行hash运算,得到哈希值,利用哈希值%分区总数 = 分区下标
1.4,Kafka根据分区下标找到分区下的主分区
2)向主分区发送消息
kafka会在批处理中累计多条记录,一次批量发送给Kafka
3)主分区数据同步到从分区(主从同步)
4)所有从分区同步结束后会发送ACK给主分区
5)主分区在接收到消息后,也会给Producer发送ACK
- 消费者的确认机制:
- SpringAMQP基于RabbitMQ提供了消费者确认机制、消费者重试机制,消费者失败处理策略:
