1. 什么是消息队列
你可以把消息队列理解为一个使用队列来通信的组件。它的本质,就是个转发器,包含发消息、存消息、消费消息的过程。
2. 消息队列有哪些使用场景。
为什么使用消息队列:
- 应用解耦
- 流量削峰
- 异步处理
- 消息通讯
2.1 应用解耦
举个常见业务场景:下单扣库存,用户下单后,订单系统去通知库存系统扣减。传统的做法就是订单系统直接调用库存系统如果库存系统无法访问,下单就会失败,订单和库存系统存在耦合关系
如果业务又接入一个营销积分服务,那订单下游系统要扩充,如果未来接入越来越多的下游系统,那订单系统代码需要经常修改如何解决这个问题呢?
可以引入消息队列:
- 订单系统:用户下单后,消息写入到消息队列,返回下单成功
- 库存系统:订阅下单消息,获取下单信息,进行库存扣减操作。
2.2 流量削峰
流量削峰也是消息队列的常用场景。我们做秒杀实现的时候,需要避免流量暴涨,打垮应用系统的风险。可以在应用前面加入消息队列。
假设秒杀系统每秒最多可以处理2k个请求,每秒却有5k的请求过来,可以引入消息队列,秒杀系统每秒从消息队列拉2k请求处理得了。
但不会出现消息积压的问题,原因如下:
- 首先秒杀活动不会每时每刻都那么多请求过来,高峰期过去后,积压的请求可以慢慢处理;
- 其次,如果消息队列长度超过最大数量,可以直接抛弃用户请求或跳转到错误页面;
2.3 异步处理
我们经常会遇到这样的业务场景:用户注册成功后,给它发个短信和发个邮件。
如果注册信息入库是30ms,发短信、邮件也是30ms,三个动作串行执行的话,会比较耗时,响应90ms:
如果采用并行执行的方式,可以减少响应时间。注册信息入库后,同时异步发短信和邮件。如何实现异步呢,用消息队列即可,就是说,注册信息入库成功后,写入到消息队列(这个一般比较快,如只需要3ms),然后异步读取发邮件和短信。
2.4 消息通讯
消息队列内置了高效的通信机制,可用于消息通讯。如实现点对点消息队列、聊天室等。
3. 消息队列如何解决消息丢失问题?
一个消息从生产者产生,到被消费者消费,如何保证MQ不丢失消息,主要为以下三个维度分析:
- 生产者保证不丢消息
- 存储端不丢消息
- 消费者不丢消息
3.1 生产者保证不丢消息
生产端如何保证不丢消息呢?确保生产的消息能到达存储端。
如果是RocketMQ消息中间件,Producer生产者提供了三种发送消息的方式,分别是:
- 同步发送
- 异步发送
- 单向发送
生产者要想发消息时保证消息不丢失,可以:
- 采用同步方式发送,send消息方法返回成功状态,就表示消息正常到达了存储端Broker。
- 如果send消息异常或者返回非成功状态,可以重试。
- 可以使用事务消息,RocketMQ的事务消息机制就是为了保证零丢失来设计的
3.2 存储端不丢消息
如何保证存储端的消息不丢失呢?确保消息持久化到磁盘。首先考虑到刷盘机制。
刷盘机制分同步刷盘和异步刷盘:
- 生产者消息发过来时,只有持久化到磁盘,RocketMQ的存储端Broker才返回一个成功的ACK响应,这就是同步刷盘。它保证消息不丢失,但是影响了性能。
- 异步刷盘的话,只要消息写入PageCache缓存,就返回一个成功的ACK响应。这样提高了MQ的性能,但是如果这时候机器断电了,就会丢失消息。
Broker一般是集群部署的,有master主节点和slave从节点。消息到Broker存储端,只有主节点和从节点都写入成功,才反馈成功的ack给生产者。这就是同步复制,它保证了消息不丢失,但是降低了系统的吞吐量。与之对应的就是异步复制,只要消息写入主节点成功,就返回成功的ack,它速度快,但是会有性能问题。
3.3 消费阶段不丢消息
消费者执行完业务逻辑,再反馈会Broker说消费成功,这样才可以保证消费阶段不丢消息。
4. 消息队列如何保证消息的顺序性。
消息的有序性,就是指可以按照消息的发送顺序来消费。
消息队列保证顺序性整体思路就是比如Kafka的全局有序消息,就是这种思想的体现: 就是生产者发消息时,1个Topic只能对应1个Partition,一个 Consumer,内部单线程消费,但是这样吞吐量太低,一般保证消息局部有序即可。在发消息的时候指定Partition Key,Kafka对其进行Hash计算,根据计算结果决定放入哪个Partition。这样Partition Key相同的消息会放在同一个Partition。然后多消费者单线程消费指定的Partition。
5.消息队列有可能发生重复消费,如何避免,如何做到幂等?
消息队列是可能发生重复消费的。
- 生产端为了保证消息的可靠性,它可能往MQ服务器重复发送消息,直到拿到成功的ACK。
- 再然后就是消费端,消费端消费消息一般是这个流程:拉取消息、业务逻辑处理、提交消费位移。假设业务逻辑处理完,事务提交了,但是需要更新消费位移时,消费者却挂了,这时候另一个消费者就会拉到重复消息了。
如何幂等处理重复消息呢?
幂等处理重复消息,简单来说,就是搞个本地表,带唯一业务标记的,利用主键或者唯一性索引,每次处理业务,先校验一下就好啦。又或者用redis缓存下业务标记,每次看下是否处理过了。
6. 如何处理消息队列的消息积压问题
消息积压是因为生产者的生产速度,大于消费者的消费速度。遇到消息积压问题时,我们需要先排查,是不是有bug产生了。
如果不是bug,可以优化一下消费的逻辑,比如之前是一条一条消息消费处理的话,可以确认是不是可以优为批量处理消息。如果还是慢,可以考虑水平扩容,增加Topic的队列数,和消费组机器的数量,提升整体消费能力。
如果是bug导致几百万消息持续积压几小时。有如何处理呢?需要解决bug,临时紧急扩容,大概思路如下:
- 先修复consumer消费者的问题,以确保其恢复消费速度,然后将现有consumer 都停掉。
- 新建一个 topic,partition 是原来的 10 倍,临时建立好原先10倍的queue 数量。
- 然后写一个临时的分发数据的 consumer 程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的 10 倍数量的 queue。
- 接着临时征用 10 倍的机器来部署 consumer,每一批 consumer 消费一个临时 queue 的数据。这种做法相当于是临时将 queue 资源和 consumer 资源扩大 10 倍,以正常的 10 倍速度来消费数据。
- 等快速消费完积压数据之后,得恢复原先部署的架构,重新用原先的 consumer 机器来消费消息。
7. 消息队列技术选型,Kafka还是RocketMQ,还是RabbitMQ
- RabbitMQ是开源的,比较稳定的支持,活跃度也高,但是不是Java语言开发的。
- 很多公司用RocketMQ,比较成熟,是阿里出品的。
- 如果是大数据领域的实时计算、日志采集等场景,用 Kafka 是业内标准的。