消息队列基础要点
本文不对 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 确认)