「这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战」
大家好!我是炭治,今天来说说消息队列常见的问题,对于新入门的同学,可以从这几个方面来入手研究。
1. MQ
1.1 MQ 应用场景、优缺点
应用场景:
- 解耦
- 异步
- 削峰
缺点:
- 系统可用性降低,外部依赖增多
- 系统复杂性提高,重复消费、丢消息、消息顺序消费...
- 一致性问题
1.2 技术选型
-
ActiveMQ
- 吞吐量:万级
- 时效性:ms
- 可用性:高,基于主从架构
- 消息可靠性:较低概率丢数据
-
RabbitMQ
- 吞吐量:万级
- 时效性:微秒
- 可用性:高,基于主从架构
- 消息可靠性:
-
RocketMQ
- 吞吐量:10万级,topic 可达几百、几千,吞吐量只有较小幅度下降
- 时效性:ms
- 可用性:非常高,分布式架构
- 消息可靠性:经过参数优化配置,可做到 0 丢失
-
Kafka
- 吞吐量:10万级,topic 从几十到百千,吞吐量会大幅下降
- 时效性:ms
- 可用性:非常高,kafka 本身是分布式的,一个数据多个副本
- 消息可靠性:经过参数优化配置,可做到 0 丢失
- 优点:提供较少的核心功能和超高的吞吐量,ms级的延迟,极高的可用性和可靠性,分布式儿可 任意扩展
- 缺点:有可能消息重复消费
1.3 如何保证高可用
-
RabbitMQ
模式:单机、普通集群、镜像集群
-
普通集群模式
多机多实例,每个机器启动一个。创建的 queue 只会放在一个实例上,每个实例都同步 queue 的元数据(queue 所在实例信息等)。
消费的时候,实际上如果连接到了另外一个实例,那么那个实例会从 queue 所在实例上拉取数据。
没做到所谓的分布式,就是普通集群,主要提高吞吐量,让集群中多个节点来服务某个 queue 读写, 没有所谓的高可用。
-
镜像集群模式
创建的 queue 无论元数据,还是 queue 里的消息都会存在多个实例上,写消息的时候,会自动同步 到多个实例 queue 里
坏处:1. 新能开销大,消息同步所有机器,网络带宽压力和消耗很重; 2. 没有扩展性可言,所有机器 数据同步,增加实例也没法解决某个 queue 的消息负载问题。
-
Kafka
基本架构:多个 broker 组成,每个 broker 是一个节点,创建一个 topic 可以划分为多个 partition,每个 partition 可以存在于不同的 broker 上,每个 partition 存放一部 分数据。
天然分布式消息队列,一个 topic 的数据是分散在多个机器上的,每个机器存放一部分数据。
-
提供 HA 机制
就是 replica 副本机制,每个 partition 的数据都会同步到其他机器上,形成多个 replica 副本,然后所有的 replica 会选举一个 leader 出来,其他的 replica 是 follower,写的 时候,leader 负责数据同步;读的时候,直接从 leader 读取。
消费的时候,只会从 leader 去读,但是只有一个消息被所有 follower 都同步成功返回 ack 的时候,消息才会被消费者读到。
1.4 如何保证消息可靠性传输
丢数据场景:1. MQ 自己弄丢的;2. 消费的时候弄丢了。
-
RabbitMQ
-
生产者丢数据
发数据的时候数据丢失(网络等原因),可以选用 RabbitMQ 的事务功能(channel.txSelect), 发送消息,如果没有被 MQ 接收到,生产者会收到异常报错,回滚事务(channel.txRollback), 重试,成功则提交事务(channel.txCommit)。事务机制是同步的,会降低吞吐量,比较耗性能。
一般开启 confirm 模式,在生产者设置开启,每次写消息就会分配一个唯一id,写 MQ 成功后 回传 ack 消息,失败则回调 nack 接口。异步
-
MQ 丢消息
开启 RabbitMQ 的持久化:创建 queue 设置持久化,发消息时将 deliveryMode 设置为 2,将消息持久化
持久化 + confirm, 持久化完成后在通知 ack
-
消费者丢数据
使用 RabbitMQ 提供的 ack 机制,关闭自动 ack,通过 api 自己调用 ack
-
-
Kafka
-
消费者丢数据
唯一可能导致丢消息的情况:消费到消息,消费者端自动提交 offset,让 Kafka 以为消费了, 但是此时系统挂了,消息就丢了。
解决:关闭 offset 改为自动提交,会有消息重复消费,需要保证幂等(刚处理完挂了,还没提交 offset,重启 后又消费一次),
-
MQ 丢消息
Kafka 某个 broker 宕机,从新选举 partition 的 leader 时,刚好有些 follower 的 数据没有同步,导致丢消息。
解决: topic 设置 raplication.factor 参数,值 > 1,要求每个 partition 至少 2 个副本;
服务端设置 min.insync.replicas 参数,值 > 1,要求一个 leader 至少感知到 1 个 follower;
producer 设置 ack=all,保证每条数据都写入 replica;
producer 设置 retries=MAX,失败无限重试。
-
生产者丢数据
按上面设置 ack=all,一定不会丢消息
-
1.5 如何保证消息顺序消费
顺序错乱场景:
- RabbitMQ: 一个 queue,多个 consumer
- Kafka:一个 topic,一个 partition, 一个 consumer,内部多线程处理
如何保证顺序:
-
RabbitMQ:
拆分多个 queue,每个 queue 一个 consumer; 或者一个 queue 对应一个 consumer,然后 consumer 内部使用内存队列做排队,分发到底层不同的 worker 处理。
-
Kafka: 一个 topic,一个 partition,一个 consumer,内部使用单线程消费,些 N 个内存队列,然后 N 个线程 分别消费一个内存队列即可
1.6 生产消息大量积压
修改原来 consumer 逻辑,不处理消息,只是将原来的消息转发到新的临时的多个 queue/partition,增加消费者节点, 相当于是临时扩大 queue 和 consumer 的资源,等积压消息消费完成后,再恢复原来架构。
1.7 MQ 架构设计
- 可伸缩性
- 持久化
- 可用性
- 数据 0 丢失