消息队列基础要点
本文不对 API 进行讨论,主要针对 RabbitMQ、Kafka,需要对这两个 MQ 有一定认识
本文主要讨论消息队列的简单原理,仅针对面试要点,不会过分深入
使用消息队列的常见场景
- 
下单微服务调用库存服务改库存
 - 
相关微服务调用日志微服务,进行统计、存储、通知
 - 
部分 RPC 可以转为使用 MQ
 
- 
解耦:某服务的关键数据发送数据到多个系统使用,且各系统不固定、不一定稳定
 - 
异步:某服务接受到一个请求,需要修改自己的库,同时需要在其他多个服务中写对应的多个库,且十分耗时,拖慢系统响应。一般要求用户响应低于200ms
 - 
削峰:短期内流量高峰
 
为什么使用(优缺点)
项目中有某个业务场景,该场景有某个技术挑战,如果不使用 MQ 将带来很多问题,但是在使用了 MQ 后,带来了很多好处。
- 
缺点
- 
引入外部依赖导致可用性下降,万一 MQ 挂了
 - 
复杂性提高,如何保证一致性(多个系统均成功?)、顺序性、不丢不重
 
 - 
 
各消息队列对比
| 特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafka | 
|---|---|---|---|---|
| 单机吞吐量 | 万级,吞吐量比 RocketMQ 和Kafka 低了ー个数量级 | 万级,吞吐量比 RocketMQ 和Kafka 低了ー个数量级 | 10万级,RocketMQ也是可以支撑高吞吐的一种MQ | 10万级,一般配合大数据系统使用,如实时运算、日志采集等 | 
| Topic 数量对吞吐量的影响 | - | - | 可达几百、几千级别,有小幅下降,同等机器下可以职称大量 | topic 在几十到几百个时,吞吐量大幅下降 | 
| 时效性 | ms级 | 微秒级(特点) | ms级 | ms级以内 | 
| 可用性 | 高,主从架构 | 高,主从架构 | 非常高,分布式架构 | 非常高,多副本,少数节点宕机不丢失数据,不影响可用性 | 
| 消息可靠性 | 有较低概率丢失数据 | - | 经过参数优化,可以实现0丢失 | 经过参数优化,可以实现0丢失 | 
| 功能支持 | MQ 领域的功能完备 | 基于 erlang,并发性能好,延时低,MQ功能完备 | MQ 功能较为完善,分布式,可拓展性好 | 功能较简单,支持简单的MQ功能,适合大数据使用 | 
| 优劣总结 | 成熟、强大,但可能丢数据。 社区、应用案例减少。有被淘汰的倾向  | 性能好、界面友好,社区相对活跃,互联网散户维护,一般不会凉。 但吞吐量受限。适合中小型公司,不关心源码,关注使用,依赖社区  | API 简单,性能好,大规模吞吐,分布式拓展方便,支持大规模的topic,社区尚可,阿里开源。 适合大型互联网公司,万一社区凉了,有实力进行基础架构、自行维护  | 仅提供较少的核心功能,提供超高吞吐量、可用性、分布式可拓展性。 社区活跃度很高,世界级的大数据行业标准  | 
如何保证高可用
RabbitMQ
三种模式
- 
单机模式
 - 
普通集群模式(主备集群),备机只存储元数据,代理消费请求。造成集群内大量数据传输,可用性无保障
 - 
镜像集群模式,从节点实际存储完整数据镜像,但不是分布式的,单机存不下所有消息时,可能遭遇瓶颈
 
Kafka
架构:每个节点是一个 broker,创建一个 topic 会创建多个 partition,每个 broker 存放多个 partition。partition 在集群中存有副本,多个副本中有一个 partition 会被选举为 leader,消费者消费时使用 leader,leader 向 follower 同步数据
避免重复消费(幂等性)
- 
Offset:生产者数据进入MQ后,会给每条数据分配一个 offset 表示数据编号,消费者消费数据后,需要提交 offset (Zookeeper)告知已消费的位置。
 - 
重复消费原因:消费者在消费后,提交 offset 前进程终止
 - 
保证幂等性:使用 Redis 保存已处理的唯一数据 ID
 
可靠性传输、数据丢失
- 
使用 MQ 传递关键消息时,有可能不允许消息丢失
 - 
丢消息原因
- 
生产者传输过程中丢失、MQ 内部出错
 - 
消息等待消费的过程中 MQ 终止
 - 
消费者处理消息过程中终止
 
 - 
 - 
应对方法
- 
生产者丢消息:生产者发送消息时开启事务,但是生产者吞吐量下降
 - 
生产者丢消息:生产者配置为 confirm 模式,要求 MQ 接受到消息后确认,使用回调、异步机制保证可靠性
 - 
RabbitMQ 丢数据:创建 Queue 时,设置为持久化,并把 deliveryMode 设置为2,将消息持久化。极端情况下(持久化期间终止)仍存在一定风险。
 - 
Kafka 丢数据:数据发到 leader 后宕机,新选举 leader 导致丢数据。 通过下述方法解决
- 
topic 设置
replication.factor大于1、服务端设置min.insync.replicas大于1(ISR 机制),确保一个 leader 至少有一个 follower 与自己保持联系 - 
生产者端设置
acks=all要求每条数据必须写入所有 replication 后才认为成功 - 
生产者端设置
retries=很大的值,只要失败就一直重试 
 - 
 - 
消费者丢数据(Kafka 为 Offset):只在配置了 autoAck 的情况下发送。关闭自动确认,手动发送 ack
 
 - 
 
消息的顺序性
- 
RabbitMQ 生产者:存在多个队列时,难以保证消息的顺序消费,需要生产者将需要顺序消费的消息发送到同一个队列中,确保出队的消息有序。
 - 
Kafka 生产者:为一系列需要顺序消费的消息分配同一个 key,按照 key 的 hash 进行分发,这些数据会据此发送到同一个 partition,partition 本身有序
 - 
消费者:一个 Queue 对应一个 消费者,多个消费者线程时仍可能混乱,需要在消费者内存中使用 Queue 进行再分发。
 
延时、队列满、积压
- 
大量消息积压,消费者故障,即使消费者恢复也需要很长时间才能完成消费
- 
首先修复 consumer 问题,并将现有的 consumer 都停掉
 - 
临时建立 10~20 倍于原架构的 queue 数量
 - 
构建临时分发数据的 consumer,部署并对积压的数据进行转发,轮询写入新建立的 queue 中
 - 
临时征用 10~20 倍于原架构的节点进行消费,每一倍的 consumer 消费一个 queue 的数据
 - 
上述方案可以达到日常 10~20 倍的消费速度
 
 - 
 - 
RabbitMQ 消息积压,超时清除导致消息丢失
- 线上环境不配置过期删除,如果配置了,只能手动进行补偿
 
 - 
大量消息积压,MQ 磁盘耗尽,情况紧急,没时间建立临时消费者集群
- 写一个消费者,迅速进行消费,不对消息进行任何处理。事后对丢失的消息进行手动补偿
 
 
如何实现 MQ
- 
支持扩容,分布式MQ
- 
参考 Kafka 的设计,broker - topic - partition
 - 
每个 partition 放到一个节点,存放 topic 的一部分数据
 - 
扩容时,为 topic 增加 partition
 
 - 
 - 
持久化,写磁盘
- 顺序写磁盘,Kafka 采用,比随机写磁盘快很多
 
 - 
可用性保证
- 
参考 Kafka,多副本、Leader & Follower 集群、broker
 - 
leader 挂了由 controller 重新推选 leader 即可
 
 - 
 - 
不丢失数据
- 参考上文 Kafka 应对消息丢失的方案(全部 ISR 确认)