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中记录业务标识
  • 每次处理前先进行校验,确保同一消息不会被重复处理

消息丢失问题解决

要保证消息不丢失,需要保证三个环节的可靠性:

  1. 消息生产阶段:生产者需处理返回值和异常,确认收到MQ的ACK后才认为发送成功,否则进行重试
  2. 消息存储阶段:通过集群部署,多副本机制确保即使某个节点故障,数据也不会丢失
  3. 消息消费阶段:消费者需在消息完全处理后才回复ACK,而不是收到消息就回复

使用消息队列需注意的其他问题

消息可靠性保证

  1. 消息持久化:确保消息在系统崩溃后不丢失,如RabbitMQ中设置durable=true
  2. 消息确认机制:消费者成功处理后才向MQ发送确认
  3. 消息重试策略:消费失败时设置合理的重试次数和间隔

消息顺序性保证

  1. 场景识别:明确业务中哪些消息需要保证顺序,如金融交易的转账操作
  2. 消息队列支持:如Kafka通过将消息发送到同一分区保证分区内顺序
  3. 消费者处理:避免并发处理可能打乱顺序的消息

幂等写保证

实现幂等性的核心方案:

  1. 唯一标识:为每个请求生成全局唯一ID,服务端校验是否已处理
  2. 数据库事务+乐观锁:通过版本号或状态字段控制并发更新
  3. 数据库唯一约束:利用唯一索引防止重复数据写入
  4. 分布式锁:保证同一时刻仅有一个请求执行关键操作
  5. 消息去重:消费前检查消息ID是否已处理过

消息积压处理

消息积压原因:生产速度快于消费速度

处理方法:

  1. 排查是否有bug导致消费变慢
  2. 优化消费逻辑,如改为批量处理
  3. 水平扩容,增加队列数和消费者数量

紧急情况下:

  1. 修复消费者问题
  2. 新建临时topic,分区数是原来的10倍
  3. 写临时程序将积压数据分发到多个队列
  4. 临时扩容10倍消费者快速消费
  5. 消费完后恢复原架构

RocketMQ

为什么选择RocketMQ?

  1. 开发语言优势:使用Java开发,比Erlang开发的RabbitMQ更容易阅读和排查问题
  2. 社区活跃:阿里巴巴内部大量使用,经受过生产环境考验
  3. 特性丰富:提供顺序消息、事务消息、消息过滤、定时消息等高级特性

RocketMQ延时消息原理

broker接收到延时消息后,会将消息存入延时Topic的队列中,然后ScheduleMessageService中的定时任务会检查队列中哪些消息已到设定时间,将其转发到原始Topic,被各自的consumer消费。

RocketMQ分布式事务处理

RocketMQ提供的是最终一致性的分布式事务实现,流程如下:

  1. A服务发送Half Message到broker
  2. Half Message发送成功后,执行本地事务
  3. 本地事务执行结果有三种情况:
    • 成功:发送commit到broker,消息变为可消费状态
    • 失败:发送rollback到broker,删除半消息
    • 未响应:broker会回查事务状态,根据结果决定提交或回滚

RocketMQ消息顺序保证

RocketMQ采用局部顺序一致性机制,实现单个队列中消息严格有序:

  1. 生产者将需要顺序处理的消息发送到同一个MessageQueue
  2. 消费者通过加锁保证同一MessageQueue只能被同一个Consumer消费
  3. 不同业务的消息可以发送到不同队列并发消费,提高处理速度

Kafka

Kafka特点

  1. 高吞吐量、低延迟:每秒处理几十万条消息,延迟低至毫秒级
  2. 可扩展性:支持集群热扩展
  3. 持久性、可靠性:消息持久化到磁盘,支持数据备份
  4. 容错性:允许部分节点失败(副本数n允许n-1个节点失败)
  5. 高并发:支持数千客户端同时读写

Kafka为什么这么快?

  1. 顺序写入优化:减少磁盘寻道时间
  2. 批量处理技术:减少网络开销和磁盘I/O操作
  3. 零拷贝技术:直接将数据从磁盘发送到网络套接字
  4. 压缩技术:减少网络传输数据量

Kafka消费模型

Kafka采用拉取模型(pull),由消费者控制消息消费速率和顺序:

  • 消费者自己记录消费状态(offset)
  • 消费者可以按任意顺序消费消息(如重置到旧的offset或跳到最新位置)
  • 消费者组(consumer group)模式下,同一分区在同一时间只能由组内一个消费者读取

Kafka如何保证顺序性

Kafka保证同一分区内消息有序,具体实现:

  1. 生产者将需要顺序处理的消息发送到同一分区(通过指定相同的Key)
  2. 消费者使用单线程消费同一分区的消息

RabbitMQ

RabbitMQ核心特性

  1. 持久化机制:支持消息、队列和交换器持久化,防止服务器重启导致消息丢失
  2. 消息确认机制:提供生产者确认和消费者确认机制
  3. 镜像队列:将队列内容复制到多节点,提高可用性
  4. 多种交换器类型:直连交换器、扇形交换器、主题交换器和头部交换器

RabbitMQ底层架构

  1. 核心组件:生产者、消费者、RabbitMQ服务器
  2. 交换机:根据routing key和绑定规则将消息路由到队列
  3. 持久化:支持消息和队列持久化到磁盘
  4. 确认机制:消费者处理完消息后发送确认,未确认的消息会重新入队
  5. 高可用性:通过集群模式和镜像队列提高可用性和负载均衡

消息队列对比与选型建议

  • 高吞吐量场景:选择Kafka或RocketMQ
  • 复杂路由需求:选择RabbitMQ
  • 大量主题支持:选择RabbitMQ(百万级)或RocketMQ(千级)
  • 消息回溯需求:选择Kafka(按offset回溯)或RocketMQ(按时间回溯)
  • 分布式事务支持:选择RocketMQ
  • 顺序消息需求:所有MQ都支持,但实现方式不同

根据业务场景的特点和需求侧重点,合理选择适合的消息队列实现。