MQ
MQ
消息队列总结
什么是消息队列?
消息队列本质上是一个通过队列来进行通信的组件,它的核心功能就是作为一个转发器,包括发消息、存消息、消费消息的过程。最简单的消息队列模型如下:
消息队列(简称MQ, Message Queue)即指消息中间件,目前业界主流的开源消息中间件包括:RabbitMQ、RocketMQ和Kafka。
消息队列如何选型?
下表是主流消息队列的不同维度对比:
特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafka |
---|---|---|---|---|
单机吞吐量 | 万级 | 万级 | 10万级 | 10万级 |
时效性 | 毫秒级 | 微秒级 | 毫秒级 | 毫秒级 |
可用性 | 高(主从) | 高(主从) | 非常高(分布式) | 非常高(分布式) |
消息重复 | 至少一次 | 至少一次 | 至少一次 | 最多一次/至少一次/最多一次 |
消息顺序性 | 有序 | 有序 | 有序 | 分区有序 |
支持主题数 | 千级 | 百万级 | 千级 | 百级,多了性能严重下滑 |
消息回溯 | 不支持 | 不支持 | 支持(按时间回溯) | 支持(按offset回溯) |
管理界面 | 普通 | 普通 | 完善 | 普通 |
选型时需根据业务场景结合上述特性进行选择:
- 如果需要支持超大型秒杀活动等高吞吐量场景,应优先选择Kafka和RocketMQ
- 如果是公司中台需要支持大量主题,RocketMQ或百万级主题的RabbitMQ更合适
- 如果是金融类业务,注重稳定性和安全性,分布式部署的Kafka和RocketMQ更有优势
- 时效性方面,虽然RabbitMQ宣传微秒级延迟,但实际上在大多数业务场景中,毫秒和微秒的差别几乎无法感知
消息队列使用场景
1. 解耦
将系统间通过网络直接调用改为通过MQ进行异步通信。即使某系统宕机,也只是消息在MQ中积压,不会对其他系统造成直接影响,降低了系统间的耦合度。
2. 异步
将一个操作涉及的多个步骤异步化处理。例如创建订单后,添加客户轨迹、更新库存、修改客户状态等操作可以通过MQ异步执行,提高系统响应速度和用户体验。
3. 削峰
系统访问流量在高峰期可能远超系统处理能力。比如系统平时每秒处理100个请求没压力,但抢购活动时突增到每秒5000个请求,而系统每秒只能处理2000个。通过MQ可以将请求缓冲,按系统最大能力消费,保证系统稳定性。
消息重复消费问题解决
生产端为保证消息发送成功可能会重复推送,导致重复消息。虽然成熟的MQ框架会尽量避免存储重复消息,但消费端无法从根本上解决这个问题。
解决方案主要是在业务层面保证幂等性:
- 对已消费成功的消息,在本地数据库或Redis中记录业务标识
- 每次处理前先进行校验,确保同一消息不会被重复处理
消息丢失问题解决
要保证消息不丢失,需要保证三个环节的可靠性:
- 消息生产阶段:生产者需处理返回值和异常,确认收到MQ的ACK后才认为发送成功,否则进行重试
- 消息存储阶段:通过集群部署,多副本机制确保即使某个节点故障,数据也不会丢失
- 消息消费阶段:消费者需在消息完全处理后才回复ACK,而不是收到消息就回复
使用消息队列需注意的其他问题
消息可靠性保证
- 消息持久化:确保消息在系统崩溃后不丢失,如RabbitMQ中设置durable=true
- 消息确认机制:消费者成功处理后才向MQ发送确认
- 消息重试策略:消费失败时设置合理的重试次数和间隔
消息顺序性保证
- 场景识别:明确业务中哪些消息需要保证顺序,如金融交易的转账操作
- 消息队列支持:如Kafka通过将消息发送到同一分区保证分区内顺序
- 消费者处理:避免并发处理可能打乱顺序的消息
幂等写保证
实现幂等性的核心方案:
- 唯一标识:为每个请求生成全局唯一ID,服务端校验是否已处理
- 数据库事务+乐观锁:通过版本号或状态字段控制并发更新
- 数据库唯一约束:利用唯一索引防止重复数据写入
- 分布式锁:保证同一时刻仅有一个请求执行关键操作
- 消息去重:消费前检查消息ID是否已处理过
消息积压处理
消息积压原因:生产速度快于消费速度
处理方法:
- 排查是否有bug导致消费变慢
- 优化消费逻辑,如改为批量处理
- 水平扩容,增加队列数和消费者数量
紧急情况下:
- 修复消费者问题
- 新建临时topic,分区数是原来的10倍
- 写临时程序将积压数据分发到多个队列
- 临时扩容10倍消费者快速消费
- 消费完后恢复原架构
RocketMQ
为什么选择RocketMQ?
- 开发语言优势:使用Java开发,比Erlang开发的RabbitMQ更容易阅读和排查问题
- 社区活跃:阿里巴巴内部大量使用,经受过生产环境考验
- 特性丰富:提供顺序消息、事务消息、消息过滤、定时消息等高级特性
RocketMQ延时消息原理
broker接收到延时消息后,会将消息存入延时Topic的队列中,然后ScheduleMessageService中的定时任务会检查队列中哪些消息已到设定时间,将其转发到原始Topic,被各自的consumer消费。
RocketMQ分布式事务处理
RocketMQ提供的是最终一致性的分布式事务实现,流程如下:
- A服务发送Half Message到broker
- Half Message发送成功后,执行本地事务
- 本地事务执行结果有三种情况:
- 成功:发送commit到broker,消息变为可消费状态
- 失败:发送rollback到broker,删除半消息
- 未响应:broker会回查事务状态,根据结果决定提交或回滚
RocketMQ消息顺序保证
RocketMQ采用局部顺序一致性机制,实现单个队列中消息严格有序:
- 生产者将需要顺序处理的消息发送到同一个MessageQueue
- 消费者通过加锁保证同一MessageQueue只能被同一个Consumer消费
- 不同业务的消息可以发送到不同队列并发消费,提高处理速度
Kafka
Kafka特点
- 高吞吐量、低延迟:每秒处理几十万条消息,延迟低至毫秒级
- 可扩展性:支持集群热扩展
- 持久性、可靠性:消息持久化到磁盘,支持数据备份
- 容错性:允许部分节点失败(副本数n允许n-1个节点失败)
- 高并发:支持数千客户端同时读写
Kafka为什么这么快?
- 顺序写入优化:减少磁盘寻道时间
- 批量处理技术:减少网络开销和磁盘I/O操作
- 零拷贝技术:直接将数据从磁盘发送到网络套接字
- 压缩技术:减少网络传输数据量
Kafka消费模型
Kafka采用拉取模型(pull),由消费者控制消息消费速率和顺序:
- 消费者自己记录消费状态(offset)
- 消费者可以按任意顺序消费消息(如重置到旧的offset或跳到最新位置)
- 消费者组(consumer group)模式下,同一分区在同一时间只能由组内一个消费者读取
Kafka如何保证顺序性
Kafka保证同一分区内消息有序,具体实现:
- 生产者将需要顺序处理的消息发送到同一分区(通过指定相同的Key)
- 消费者使用单线程消费同一分区的消息
RabbitMQ
RabbitMQ核心特性
- 持久化机制:支持消息、队列和交换器持久化,防止服务器重启导致消息丢失
- 消息确认机制:提供生产者确认和消费者确认机制
- 镜像队列:将队列内容复制到多节点,提高可用性
- 多种交换器类型:直连交换器、扇形交换器、主题交换器和头部交换器
RabbitMQ底层架构
- 核心组件:生产者、消费者、RabbitMQ服务器
- 交换机:根据routing key和绑定规则将消息路由到队列
- 持久化:支持消息和队列持久化到磁盘
- 确认机制:消费者处理完消息后发送确认,未确认的消息会重新入队
- 高可用性:通过集群模式和镜像队列提高可用性和负载均衡
消息队列对比与选型建议
- 高吞吐量场景:选择Kafka或RocketMQ
- 复杂路由需求:选择RabbitMQ
- 大量主题支持:选择RabbitMQ(百万级)或RocketMQ(千级)
- 消息回溯需求:选择Kafka(按offset回溯)或RocketMQ(按时间回溯)
- 分布式事务支持:选择RocketMQ
- 顺序消息需求:所有MQ都支持,但实现方式不同
根据业务场景的特点和需求侧重点,合理选择适合的消息队列实现。