技术选型
特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafka |
---|---|---|---|---|
单机吞吐量 | 万级,比RocketMQ 、Kafka 低一个数量级 | 同 ActiveMQ | 10万级,支持高吞吐 | 10万级,高吞吐,一般结合大数据类的系统进行实时数据计算、日志采集等场景 |
topic 数量对吞吐量的影响 | topic 可以达到几百/几千的级别,吞吐量会有比较小幅度的下降,这是 RocketMQ 的一大优势,在同等机器下,可以支撑大量的 topic | topic从几十到几百个时候,吞吐量会大幅度下降,在同等机器下, Kafka 尽量保证 topic 数量不要过多,如果要支撑大规模的 topic ,需要增加更多的机器资源 | ||
时效性 | ms 级 | 微秒级,这是RabbitMQ的一大特点,延迟最低 | ms级 | 延迟在ms级以内 |
可用性 | 高,基于主从架构实现高可用 | 同 ActiveMQ | 非常高,分布式架构 | 非常高,分布式,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用 |
消息可靠性 | 有较低的概率丢失数据 | 基本不丢 | 经过参数优化配置,可以做到0丢失 | 同 RocketMQ |
功能支持 | MQ 领域的功能极其完备 | 基于 erlang 开发,并发能力很强,性能极好,延迟很低 | MQ 功能较为完善,还是分布式的,扩展性好 | 功能比较简单,主要支持简单的 MQ 功能,在大数据领域的实时计算以及日志采集被大规模使用 |
- 技术实力一般,技术挑战不是特别高,选择 RabbitMQ
- 基础架构研发实力较强,选择 RocketMQ
- 大数据领域的实时计算、日志采集,选择 Kafka 是业内标准
为什么使用消息队列
业务场景
解耦
一个系统或模块调用了多个系统或模块,互相之间的调用很复杂,维护起来很麻烦,且该调用时不需要同步调用接口的,用 MQ 给它异步化解耦。
异步
削峰
缺点
系统可用性降低
- 如何保证消息队列的高可用?
系统复杂度提高
-
如何保证消息没有重复消费?
-
怎么处理消息丢失的情况?
-
怎么保证消息传递的顺序性?
一致性问题
高可用性
RabbitMQ 的高可用性
基于主从(非分布式)
单机模式
普通集群模式(无高可用性)
特点
- 多个 RabbitMQ 实例
- 创建的 queue 只会放在一个 RabbitMQ 实例上,每个实例都会同步 queue 的元数据(queue的一些配置信息,通过元数据,可以找到 queue 所在实例)
- 根据queue 元数据从 queue 所在的实例拉取数据
缺点
- 没做到所谓的分布式,要么有数据拉去的开销,要么单实例性能瓶颈
- 存放 queue 的实例宕机了,导致无法拉取;如果开启了消息持久化,消息不一定会丢,得要等该实例恢复才能拉取数据
镜像集群模式(高可用性)
特点
-
queue 的元数据和消息都会存在于多个实例上,每个 RabbitMQ 节点都有这个 queue 的一个完整镜像,包含 queue 的全部数据
-
queue 更新,会自动同步到多个实例的 queue 上
-
如果节点宕机,还有其他节点有 queue 的完整数据
缺点
- 同步花销大,导致网络带宽压力和消耗很重
- 不是分布式的,没有扩展性,某个 queue 负载很重,新增机器没用,没有办法线性扩展 queue
Kafka 的高可用性
- 由多个 broker 组成,每个 broker 是一个节点
- 一个 topic 可以划分为多个 partition,每个 partition 可以存在于不同的 broker 上,每个 partition 就存放一部分数据
- 天然的分布式消息队列,一个 topic 的数据,是分散放在多个机器上的,每个机器就放一部分数据
Kafka 0.8 以前,没用HA机制,partition 没有备份(副本),broker宕机,该 topic 的 partition 的数据就丢失了
Kafka 0.8 以后,提供HA机制,replica(复制品)副本机制
- 每个 partition 的数据会同步到其他机器上,形成自己的多个 replica 副本
- 所有 replica 会选举一个 leader 出来,那么生产和消费都跟这个 leader 打交道,其他 replica 就是 follower
- 写数据,leader 负责同步数据到所有 follower 上
- 读数据,直接读 leader
- 均匀地将一个 partition 的所有 replica 发布到不同的机器上,提高容错性
- 如果某个 broker 宕机,partition 在其他 broker 存在副本
- 若宕机的 broker 由 某个 partition 的 leader,则 follower 会重新选举一个新的 leader 出来
- 写数据:生产者写 leader,leader 写到 disk,其他 follower 主动从 leader pull 数据,同步完成会发送 ack 给 leader,leader 收到所有 follower 的 ack 后会响应成功给生产者
- 消费:从 leader 读,只有一个消费被所有 follower 同步成功返回 ack 时,该消息才能被消费者读到
幂等性
保证消息不被重复消费
Kafka
-
每个消息写入都有一个 offset ,代表消息的序号
-
consumer 消费消息后,定时定期把自己消费过的消息的 offset 提交,表示消费过了
-
若是重启也继续上次消费到的 offset 继续消费
-
可能 consumer 消费消息后,没有提交 offset,重启之后,少数消息会再次消息一次
-
考虑重复消费之后,应该如何保证幂等性
- 入库,根据主键查询,已经存在则更新
- 写 Redis,天然幂等性
- 生产者发送数据附加全局唯一的 id,消费前根据 id 去检查是否消费过(如使用 Redis)
可靠性
- 如何处理消息丢失的问题?
RabbitMQ
Producer 丢失
事务机制
- 开启事务,如果消息没有成功被 RabbitMQ 接收到,则 回滚事务 Producer 会重试发送消息,成功接收再提交事务
- 同步阻塞,吞吐量下降,过度消耗性能
confirm 机制
- 每次写消息分配唯一 id,写入成功返回 ack,失败则回调 nack 接口,通知 Producer 接收失败
- Producer 可以定期超时重试
- 异步回调
RabbitMQ 丢失
- 开启持久化到 Disk
- 宕机恢复会主动读取之前存储的数据
- 创建 queue 设置为持久化
- 发送消息设置为持久化
- 结合 Producer 的 confirm 机制,完成持久化才返回 ack
Consumer 丢失
- 关闭 RabbitMQ 自动 ack
- Consumer 处理完消息主动调用 api
总结
Kafka
Consumer 丢失
- 消费到消息自动提交了 offset
- 关闭自动提交 offset ,处理完成手动提交 offset
- 处理完没提交 offset,保证重复消费的幂等性
Kafka 丢失
leader 宕机,其他 follower 刚好有些数据还没同步
- topic 设置
replication.factor
每个 partition 必须有至少2个副本 - kafka 服务端设置
min.insync.replicas
,leader 感知到至少有一个 follower 还跟自己保持联系,确保 leader 挂了还有一个 follower - producer 设置
acks=all
,要求每条数据必须写入所有 replica 之后,才认为写成功 - producer 设置
retries=MAX
,要求一旦写入失败,就无线重试
顺序性
顺序错乱的场景
RabbitMQ
一个 queue,多个 consumer
Kafka
-
一个 topic,多个 partition。producer 生产消息指定key,将该相关的数据都分发到同一个 partition ,保证这个 partition 的数据是有序的。
-
consumer 消费拉取数据是有序的,处理数据可能使用多线程并发,也可能导致顺序错乱
解决方案
RabbitMQ
- 拆分多个 queue,每个 queue 对应一个 consumer
- consumer 内部使用内存队列排队,分发给底层不同的 worker 来处理
Kafka
- 一个 topic,一个 partition,一个 consumer,内部单线程消费,吞吐量太低,一般不采用
- 写 N 个内存 queue,具有相同 key 的数据都到同一个内存 queue;对于 N 给线程,每个线程分别消费一个内存
延时&过期失效
-
如何解决消息队列的延时以及过期失效问题?
-
消息队列满了以后该怎么处理?
-
有几百万消息持续积压几小时,怎么解决?
消息积压
- 修复 consumer 的故障,确保恢复消费速度,将现有 consumer 停掉
- 新建 topic,partition是原来的10倍,临时建立好原先10倍的 queue 数量
- 临时分发数据的 consumer 程序,部署消费积压的数据,直接轮询写入临时创建的10倍数量的 queue
- 接着征用10倍的机器来部署 consumer,每一批 consumer 消费一个临时 queue 的数据。这种做法相当于是临时将 queue 资源和 consumer 资源扩大 10 倍,以正常的 10 倍速度来消费数据。
- 等快速消费完积压数据后,恢复原先部署的架构
消息过期
RabbitMQ 可以设置过期时间,积压超过 TTL 就会清理掉,手动补偿丢失的数据
消息溢出
消息积压导致 mq 都快写满了,要么写临时程序接入数据快速消费,要么利用空闲时间再补偿丢失的数据
架构设计
- 可伸缩性:快速扩容,增加吞吐量和容量,
broker -> topic -> partition
,每个 partition 放在一个机器,就存一部分数据。如果资源不够,给 topic 增加 partition ,然后做数据迁移,增加机器 - 持久化:磁盘顺序读写
- 可用性:
多副本 -> leader & follower -> broker
挂了重新选举 leader 既可对外服务 - 数据0丢失:replica、follower 个数,consumer 的 retry,ack