目录
MQ应用场景?
这一部分可以根据自己的实际使用场景进行分析。
比如:订单模块下单功能:支付成功后,给用户发送短信,就采取了MQ的方式。
这样达到了应用解耦(把订单模块与短信拆分)、异步处理(采用并行执行的方式,可以减少响应时间)。
除此之外,还可以在秒杀中使用MQ,进行流量削峰。
假设秒杀系统每秒最多可以处理2k个请求,每秒却有5k的请求过来,可以引入消息队列,秒杀系统每秒从消息队列拉2k请求处理得了。
解决秒杀出现消息积压的问题,
-
首先秒杀活动不会每时每刻都那么多请求过来,高峰期过去后,积压的请求可以慢慢处理;
-
其次,如果消息队列长度超过最大数量,可以直接抛弃用户请求或跳转到错误页面;
消息队列技术选型?1
在4.5的版本中,RocketMQ有一个集群模式叫做:Dleger模式。当主节点失活时,能够自动重新触发选举。
MQ如何保证消息不重复消费?确保幂等性?
幂等处理重复消息:
- 表,带唯一业务标记的,利用主键或者唯一性索引,每次处理业务,先校验一下就好啦;
- mysql 插⼊业务id作为主键,主键是唯⼀的,所以⼀次只能插⼊⼀条
- 用redis缓存下业务标记,每次看下是否处理过了。
- 分布式锁来进行处理
MQ如何保证消息的可靠发送?
相关:如何保证数据一致性,事务消息如何实现?
消息的发送是从 生产者 -> MQ -> 消费者。所以,就从这三个方面去考虑。
生产者:确保消息能够发送到MQ里面。
RabbitMQ
生产者同步在发送消息后,开启MQ里面的消息回执(ConfirmCallback)。如果MQ接收到了消息,会发送一个布尔类型的ack。如果为true,表示消息已经被MQ进行接受。
RocketMQ
之前的版本,在同步模式中,发送失败后可以重试,设置重试次数。默认3次。
producer.setRetryTimesWhenSendFailed(10);
在 4.3之后的版本,增加了事务消息去确保生产方的可靠发送。采用了2PC,即两阶段提交,prepared->commit / rollback。
我们一般是重写了事务监听器类。生产方绑定这个事务监听器,在发送消息后,此时事务消息处于prepared状态,在这个状态中,对消费方是不可见的。事务监听器判断了事务消息的状态后,对消息修改为 Commit 或 Rollback。Commit 就是直接发送给消费者,Rollback就会再去在时间段内再去check消息状态。在一定次数的check以后,会把消息丢弃。
MQ :确保消息已经持久化到磁盘中。
刷盘机制分同步刷盘和异步刷盘;
- 生产者消息发过来时,只有持久化到磁盘,RocketMQ的存储端Broker才返回一个成功的ACK响应,这就是同步刷盘。它保证消息不丢失,但是影响了性能。
- 异步刷盘的话,只要消息写入PageCache缓存,就返回一个成功的ACK响应。这样提高了MQ的性能,但是如果这时候机器断电了,就会丢失消息。
Broker一般是集群部署的,有master主节点和slave从节点。消息到Broker存储端,只有主节点和从节点都写入成功,才反馈成功的ack给生产者。这就是同步复制,它保证了消息不丢失,但是降低了系统的吞吐量。与之对应的就是异步复制,只要消息写入主节点成功,就返回成功的ack,它速度快,但是会有性能问题。
消费:
RabbitMQ
消息消费时,出现异常,就把消息使用死信队列进行处理。如果死信处理的时候出现异常,就人为进行处理。
RocketMQ
在消费者分组中,让一个消费者没有消息消费成功标识返回,那么就会发送给消费者组中的其他消费者。
从这里看出,MQ并不能做到100%的消息可靠。因此,如商品购买模块为例子,核心的流程(购买,支付)放到一个微服务,这样方便出现异常时事务回滚;用户购买之后,给用户发个短信(短信)就可以使用MQ进行处理。
MQ如何保证消息的顺序性?
RabbitMQ是不支持顺序消息的!
如图所示,RabbitMQ保证消息的顺序性,就是拆分多个 queue,每个 queue 对应一个 consumer(消费者),就是多一些 queue 而已,确实是麻烦点;或者就一个 queue 但是对应一个 consumer,然后这个 consumer 内部用内存队列做排队,然后分发给底层不同的 worker 来处理。2
RocketMQ
局部顺序:消费方消费MessageQueue里面的消息肯定是一致的。
全局顺序:一个broker只有一个MessageQueue,消息的消费肯定是顺序的。
MQ的消息积压问题
这个问题大概会从两个层面影响:
- 代码层面的问题:是不是有bug产生
- 实际上的业务场景:业务生产者正常生产消息大于消费者消费消息
- 优化一下消费的逻辑,比如之前是一条一条消息消费处理的话,我们可以确认是不是可以优为批量处理消息。
- 考虑水平扩容,增加Topic的队列数,和消费组机器的数量,提升整体消费能力。
- 如果是突发的情况:可以采取临时扩容:
- 先修复consumer消费者的问题,以确保其恢复消费速度,然后将现有consumer 都停掉。
- 新建一个 topic,partition 是原来的 10 倍,临时建立好原先10倍的queue 数量。
- 然后写一个临时的分发数据的 consumer 程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的 10 倍数量的 queue。
- 接着临时征用 10 倍的机器来部署 consumer,每一批 consumer 消费一个临时 queue 的数据。这种做法相当于是临时将 queue 资源和 consumer 资源扩大 10 倍,以正常的 10 倍速度来消费数据。
- 等快速消费完积压数据之后,得恢复原先部署的架构,重新用原先的 consumer 机器来消费消息。1
消息死信的原因
RabbitMQ
消息被拒(Basic.Reject /Basic.Nack) 且 requeue = false。
消息TTL过期。
队列满了,无法再添加。
RocketMQ
消息消费失败到达一定次数(默认为16次)后,则进入了死信队列中。
如果让你写一个消息队列,该如何进行架构设计
- RocketMq常见面试题: https://blog.csdn.net/qq_42877546/article/details/125425061
《消息队列经典十连问》,原文链接:https://juejin.cn/post/70673222605115228230 ↩︎ ↩︎
《RabbitMQ系列(六)如何保证消息的顺序性、消息不丢失、不被重复消费》,原文链接:https://blog.csdn.net/weixin_45498465/article/details/105708875 ↩︎