一、历史背景与发展历程
1.1 消息队列诞生的历史背景
分布式系统的通信困境
早期单体应用的问题:
传统单体应用架构:
- 所有功能模块在同一个进程中
- 模块间直接调用函数
- 数据库是唯一的外部依赖
问题:
- 耦合度高:一个模块的修改可能影响整个系统
- 扩展性差:无法针对单个模块扩容
- 可用性低:任何一个模块崩溃,整个系统不可用
- 开发效率低:团队协作困难,部署复杂
微服务架构的兴起:
2010年代,微服务架构逐渐流行:
- 按业务功能拆分为独立服务
- 服务间通过网络通信
- 每个服务独立部署、独立扩展
新的挑战:
- 服务间如何可靠通信?
- 如何应对服务暂时不可用?
- 如何处理流量峰值?
- 如何保证数据最终一致性?
同步调用的局限性:
HTTP/RPC 同步调用的问题:
场景:电商下单流程
1. 订单服务 → 库存服务(扣减库存)
2. 订单服务 → 支付服务(创建支付单)
3. 订单服务 → 物流服务(创建物流单)
4. 订单服务 → 积分服务(赠送积分)
5. 订单服务 → 通知服务(发送短信/邮件)
问题:
- 串行调用,响应时间累加(如果每个服务200ms,总共1秒)
- 任何一个服务故障,整个下单失败
- 流量峰值时,所有服务都承受压力
- 服务间紧耦合,修改一个服务可能影响其他服务
消息队列的解决方案
核心理念:异步解耦:
使用消息队列重构下单流程:
1. 订单服务:
- 创建订单(持久化到数据库)
- 发送"订单创建"消息到MQ
- 立即返回成功(响应时间:50ms)
2. 后续处理(异步):
- 库存服务:订阅消息,扣减库存
- 支付服务:订阅消息,创建支付单
- 物流服务:订阅消息,创建物流单
- 积分服务:订阅消息,赠送积分
- 通知服务:订阅消息,发送通知
优势:
- 快速响应:用户不需要等待所有服务处理完成
- 高可用:任何一个服务故障,不影响订单创建
- 削峰填谷:MQ缓冲流量峰值,服务按自己的速度消费
- 解耦:服务间无需直接依赖,通过消息通信
1.2 消息队列解决的核心问题
问题1:系统解耦
紧耦合架构的维护噩梦:
场景:用户注册后的后续操作
紧耦合方式:
UserService.register(user) {
userRepository.save(user);
emailService.sendWelcomeEmail(user); // 发送欢迎邮件
smsService.sendVerificationCode(user); // 发送验证码
pointsService.grantPoints(user, 100); // 赠送积分
crmService.syncUserInfo(user); // 同步到CRM系统
analyticsService.trackRegistration(user); // 数据分析
}
问题:
1. 新增功能需要修改UserService代码
- 例如:加入会员等级系统,需要修改register方法
2. 任何一个服务异常,注册流程失败
- 例如:CRM系统维护,导致无法注册新用户
3. 性能受制于最慢的服务
- 如果Analytics服务响应慢,用户注册变慢
4. 测试复杂:需要准备所有依赖服务的测试环境
消息队列解耦方案:
UserService.register(user) {
userRepository.save(user);
messageQueue.publish("user.registered", user); // 只需发送消息
}
// 各个服务独立订阅
EmailService: 订阅 "user.registered" → 发送欢迎邮件
SmsService: 订阅 "user.registered" → 发送验证码
PointsService: 订阅 "user.registered" → 赠送积分
CrmService: 订阅 "user.registered" → 同步用户信息
AnalyticsService: 订阅 "user.registered" → 记录注册事件
优势:
- 新增功能:只需新增订阅者,无需修改UserService
- 容错性:任何订阅者故障,不影响用户注册
- 性能:用户注册只需等待save + publish,毫秒级响应
- 可测试性:UserService只需测试消息是否正确发送
问题2:异步处理
同步阻塞的性能瓶颈:
场景:视频上传处理
同步方式:
1. 用户上传视频(2GB)
2. 服务端接收上传(耗时:1分钟)
3. 转码为多种格式(耗时:10分钟)
- 1080P
- 720P
- 480P
- 移动端格式
4. 生成缩略图(耗时:30秒)
5. 提取字幕(耗时:5分钟)
6. AI内容审核(耗时:2分钟)
7. 返回成功
总耗时:18分30秒
用户体验:上传后要等待近20分钟才能看到结果,期间界面卡死
消息队列异步方案:
异步方式:
1. 用户上传视频(2GB)
2. 服务端接收上传并持久化(耗时:1分钟)
3. 发送消息到MQ:
- "video.uploaded" → 转码队列
- "video.uploaded" → 缩略图队列
- "video.uploaded" → 字幕提取队列
- "video.uploaded" → 审核队列
4. 立即返回:"上传成功,处理中..."
后台异步处理:
- 转码服务:并发处理,10分钟完成
- 缩略图服务:并发处理,30秒完成
- 字幕服务:并发处理,5分钟完成
- 审核服务:并发处理,2分钟完成
用户体验:
- 上传完成后立即得到反馈(1分钟)
- 可以继续浏览其他内容
- 收到推送通知:"您的视频已处理完成"
问题3:流量削峰
流量峰值的系统崩溃风险:
场景:电商促销活动(双11、618)
流量特征:
- 平时:1000 QPS
- 促销开始瞬间:100,000 QPS(100倍)
- 持续高峰:30分钟
传统架构问题:
1. 数据库连接池耗尽:
- 最大连接数:1000
- 100,000请求同时到达
- 99%的请求等待连接超时
2. 服务器资源耗尽:
- CPU飙升到100%
- 内存不足导致OOM
- 服务响应变慢甚至崩溃
3. 雪崩效应:
- 订单服务崩溃 → 用户疯狂重试
- 库存服务崩溃 → 订单服务调用失败
- 整个系统瘫痪
应对方案:
- 扩容10倍服务器(成本高,只用几个小时)
- 限流(用户体验差,大量请求被拒绝)
消息队列削峰填谷:
使用MQ的削峰方案:
流量高峰:
1. 用户点击秒杀(100,000 QPS)
2. 接口快速将请求放入MQ(毫秒级)
3. 返回:"排队中,请稍候..."
后端处理:
- MQ中的消息:100,000条(峰值瞬间堆积)
- 消费者以恒定速率消费:1000 QPS
- 100,000条消息处理完成:约100秒
效果:
- 用户体验:虽然不是秒级响应,但系统稳定,100秒后一定有结果
- 系统稳定:不需要扩容,按平时的容量稳定处理
- 成本优化:无需为短暂峰值投入大量硬件
比喻:
- 没有MQ:水龙头全开,水管爆裂
- 有MQ:水龙头全开,蓄水池缓冲,水管稳定出水
问题4:最终一致性
分布式事务的困境:
场景:电商下单,涉及3个服务
业务要求:
- 订单服务:创建订单
- 库存服务:扣减库存
- 积分服务:扣减积分
要么全部成功,要么全部失败(事务)
分布式事务方案的问题:
1. 两阶段提交(2PC):
- 性能差:需要等待所有服务
- 阻塞:协调者故障,所有参与者阻塞
- 单点故障:协调者是单点
2. 三阶段提交(3PC):
- 复杂度高
- 仍有一致性问题
3. TCC(Try-Confirm-Cancel):
- 编码复杂:需要实现Try、Confirm、Cancel三个接口
- 业务侵入性强
消息队列最终一致性方案:
基于消息的最终一致性:
1. 订单服务(事务):
- 创建订单
- 保存本地消息表:"订单创建"消息
- 提交数据库事务
2. 定时任务:
- 扫描本地消息表
- 发送消息到MQ
- 更新消息状态为"已发送"
3. 库存服务:
- 订阅"订单创建"消息
- 扣减库存
- 发送ACK
4. 积分服务:
- 订阅"订单创建"消息
- 扣减积分
- 发送ACK
特点:
- 最终一致性:可能有短暂的不一致,但最终会一致
- 高性能:无需同步等待
- 高可用:某个服务故障,消息会重试
- 编码简单:只需发送和接收消息
权衡:
- 优点:性能好、可用性高、编码简单
- 缺点:不是强一致性,有短暂延迟(通常秒级)
- 适用:大部分业务场景(订单、支付、库存等)
- 不适用:金融转账等强一致性场景
1.3 发展历程中的重要里程碑
1980s-1990s:早期消息中间件
IBM MQ(1993年):
- 全名:IBM MQSeries(后改名为WebSphere MQ、IBM MQ)
- 第一个商业化的消息队列产品
- 特点:
- 基于队列模型(Queue)
- 支持事务
- 高可靠性
- 企业级功能
- 应用:
- 银行、证券、电信等对可靠性要求极高的行业
- 至今仍在使用
- 问题:
- 商业软件,授权费用高
- 配置复杂,学习曲线陡峭
- 性能不是最优
Microsoft Message Queue (MSMQ, 1997年):
- Windows平台的消息队列
- 与Windows系统深度集成
- 主要用于Windows生态
- 局限性:跨平台能力弱
2000s:开源消息队列兴起
JMS规范(2001年):
- Java Message Service
- Sun公司制定的Java消息服务API规范
- 定义了消息传递的标准接口
- 实现:
- ActiveMQ(Apache,2004年)
- IBM MQ
- Oracle AQ
ActiveMQ(2004年):
- Apache基金会开源项目
- 第一个流行的开源消息队列
- 特点:
- 支持JMS规范
- 支持多种协议(OpenWire、AMQP、STOMP、MQTT)
- 支持多种语言客户端
- 功能丰富
- 问题:
- 性能一般
- 并发能力有限
- 后期发展缓慢
ZeroMQ(2007年):
- 轻量级消息库(而非消息队列服务器)
- 特点:
- 嵌入式,无需独立部署
- 极高性能(微秒级延迟)
- 支持多种消息模式
- 局限:
- 无消息持久化
- 无高可用机制
- 需要自己实现很多功能
2007-2010:AMQP协议与RabbitMQ
AMQP协议(2007年):
- Advanced Message Queuing Protocol
- 由JP Morgan、Cisco等公司联合制定
- 目标:消息队列的标准协议(类似HTTP之于Web)
- 特性:
- 跨语言、跨平台
- 定义了消息格式、路由规则、可靠性保证
- 版本:AMQP 0.9.1、AMQP 1.0
RabbitMQ(2007年):
- 基于Erlang开发
- 实现AMQP协议
- 特点:
- 高可靠性(Erlang天生的容错能力)
- 灵活的路由(Exchange + Binding)
- 集群和高可用
- 管理界面友好
- 插件机制丰富
- 应用:
- 中小规模消息传递
- 任务队列(Celery等框架默认使用)
- 微服务间通信
- 问题:
- 性能不如Kafka(吞吐量:万级/秒)
- 不适合大数据场景
2011-2015:大数据时代的消息队列
Kafka(2011年,LinkedIn开源):
- 为大数据场景设计
- 特点:
- 超高吞吐量(百万级/秒)
- 分布式、可扩展
- 持久化存储(磁盘)
- 消息回溯(可重复消费)
- 设计理念:
- 消息队列 + 分布式日志
- 顺序写磁盘(比随机写内存还快)
- 零拷贝技术
- 批量处理
- 应用场景:
- 日志收集(每天TB级)
- 实时数据流(Flink、Spark Streaming)
- 事件溯源
- CQRS架构
- 里程碑:
- 2011:LinkedIn开源
- 2014:加入Apache基金会
- 2017:Kafka Streams(流处理)
- 2019:Kafka成为Apache顶级项目
RocketMQ(2012年,阿里巴巴开源):
- 参考Kafka设计,针对电商场景优化
- 特点:
- 高性能(十万级/秒)
- 低延迟(毫秒级)
- 顺序消息
- 事务消息
- 延迟消息
- 应用:
- 阿里巴巴内部:双11核心链路
- 适合电商、金融等业务场景
- 里程碑:
- 2012:阿里内部开发
- 2016:开源
- 2017:加入Apache孵化器
- 2019:Apache顶级项目
2015-至今:云原生与多样化发展
Pulsar(2016年,Yahoo开源):
- 下一代消息队列
- 特点:
- 存算分离(计算层 + 存储层)
- 多租户
- 地理复制
- 分层存储(热数据SSD、冷数据S3)
- 统一队列和流模型
- 创新点:
- BookKeeper存储(高可靠、高性能)
- Schema Registry(消息格式管理)
- Functions(内置流处理)
- 对比Kafka:
- 优势:多租户、分层存储、更灵活
- 劣势:社区规模小、生态不如Kafka成熟
NATS(2016年):
- 云原生消息系统
- 特点:
- 轻量级(Go语言实现)
- 极简设计
- 高性能(百万级/秒)
- 支持Kubernetes
- 适用:
- 微服务通信
- IoT场景
- Serverless
云厂商托管服务:
- AWS SQS/SNS(2006/2010)
- Azure Service Bus(2012)
- Google Cloud Pub/Sub(2015)
- 阿里云MQ、腾讯云CMQ/TDMQ
- 优势:
- 免运维
- 弹性伸缩
- 按需付费
- 劣势:
- 厂商锁定
- 成本可能较高(大规模)
1.4 目前在业界的地位和影响力
市场占有率与采用情况
主流消息队列对比:
| 产品 | 发布年份 | 语言 | 吞吐量 | 延迟 | 适用场景 | 市场份额 |
|---|---|---|---|---|---|---|
| Kafka | 2011 | Scala/Java | 百万级/秒 | 毫秒级 | 大数据、日志、流处理 | 60% |
| RabbitMQ | 2007 | Erlang | 万级/秒 | 微秒级 | 任务队列、微服务 | 25% |
| RocketMQ | 2012 | Java | 十万级/秒 | 毫秒级 | 电商、金融 | 10% |
| Pulsar | 2016 | Java | 百万级/秒 | 毫秒级 | 云原生、多租户 | 3% |
| ActiveMQ | 2004 | Java | 万级/秒 | 毫秒级 | 传统企业 | 2% |
Kafka的主导地位:
- LinkedIn、Netflix、Uber、Airbnb等头部公司使用
- 大数据生态的标准组件(与Flink、Spark深度集成)
- 日志收集的事实标准(替代Flume)
- 实时数据管道的首选
RabbitMQ的稳定地位:
- 开发者友好:学习曲线平缓
- Python生态:Celery默认使用RabbitMQ
- 中小型项目首选
- 微服务架构中的常见选择
RocketMQ的特色定位:
- 阿里巴巴背书:双11考验
- 中国企业首选(特别是电商、金融)
- 功能丰富:事务消息、顺序消息
- 适合业务消息(而非大数据日志)
典型应用场景统计
场景分布:
-
异步解耦(40%):
- 用户注册后的后续操作
- 订单创建后的流程编排
- 数据同步(如MySQL → ES)
-
流量削峰(25%):
- 秒杀、抢购
- 活动促销
- 流量突发场景
-
日志收集(20%):
- 应用日志汇总
- 访问日志分析
- 系统监控数据
-
消息通知(10%):
- 邮件/短信发送
- 推送通知
- 告警通知
-
数据同步(5%):
- 跨系统数据同步
- 缓存更新
- 搜索引擎索引
行业应用案例
电商行业:
-
阿里巴巴:
- RocketMQ处理双11交易链路
- Kafka处理日志和数据流
- 每天处理万亿级消息
-
京东:
- 自研消息队列JMQ
- 基于Kafka改造
- 支撑618大促
金融行业:
-
银行核心系统:
- 传统:IBM MQ
- 现代化改造:RocketMQ(事务消息)
-
证券交易:
- 低延迟要求:Kafka(优化配置)
- 可靠性要求:RocketMQ
互联网行业:
-
LinkedIn:
- Kafka创始公司
- 每天处理7万亿消息
- 用于活动流、日志、指标
-
Netflix:
- 全球最大视频流公司
- Kafka处理实时事件
- 每天500TB数据
-
Uber:
- Kafka处理位置数据
- 实时匹配乘客和司机
- 每秒数百万事件
物联网(IoT):
-
智能家居:
- MQTT协议 + 消息队列
- 设备状态上报
- 指令下发
-
车联网:
- 车辆位置、状态数据
- 实时数据流分析
- 每辆车每秒数百条消息
1.5 未来发展趋势和方向
趋势1:云原生与Serverless
Serverless消息队列:
- AWS SQS:完全托管,按需付费
- Google Cloud Pub/Sub:自动扩缩容
- Azure Service Bus:无需管理基础设施
Kubernetes原生支持:
- Kafka on K8s:Strimzi Operator
- RabbitMQ on K8s:RabbitMQ Cluster Operator
- Pulsar on K8s:原生支持
未来方向:
- 更轻量级:资源占用更少
- 更易运维:自动化部署、监控、扩容
- 更低成本:按使用量付费,无需预留资源
趋势2:流处理一体化
消息队列 + 流处理融合:
- Kafka Streams:Kafka内置流处理
- Pulsar Functions:Pulsar内置计算
- Flink on Kafka:深度集成
实时数据管道:
数据源 → Kafka → 流处理(Flink) → 数据仓库/实时大屏
未来方向:
- 统一批处理和流处理(Lambda架构 → Kappa架构)
- 更低延迟(毫秒级 → 微秒级)
- 更强大的流处理能力(SQL、CEP)
趋势3:智能化与自动化
自适应配置:
- 自动调整分区数
- 自动扩缩容
- 自动故障恢复
AI辅助运维:
- 异常检测:预测消息堆积
- 性能优化:自动调整参数
- 容量规划:智能推荐集群配置
自动化治理:
- Schema演进自动管理
- 消息格式验证
- 数据质量监控
趋势4:多协议与互操作性
协议融合:
- MQTT(物联网) + Kafka
- AMQP + Kafka(Kafka支持AMQP协议)
- HTTP/2 + gRPC
生态互通:
- Kafka Connect:与各种系统集成
- Change Data Capture (CDC):数据库变更捕获
- Schema Registry:跨系统消息格式管理
趋势5:边缘计算与IoT
边缘消息队列:
- 轻量级MQ运行在边缘设备
- 断网情况下本地缓存
- 网络恢复后同步到云端
5G与IoT:
- 海量设备连接(百亿级)
- 超低延迟(毫秒级)
- 边云协同
挑战与机遇
技术挑战:
- 性能:更高吞吐、更低延迟
- 可靠性:99.999%可用性
- 一致性:强一致性与性能的平衡
- 复杂性:功能越来越多,学习成本增加
商业挑战:
- 开源与云服务的竞争
- 商业化路径(Confluent、StreamNative)
- 成本优化
发展机遇:
- 微服务架构普及:消息队列成为标配
- 实时数据需求增长:从批处理到流处理
- IoT爆发:海量设备数据传输
- 边缘计算:新的应用场景
二、核心概念与设计理念
2.1 消息队列的核心设计理念
设计理念1:解耦
传统紧耦合的问题:
场景:订单支付成功后的处理
紧耦合代码:
class OrderService {
void paymentSuccess(Order order) {
// 更新订单状态
updateOrderStatus(order.getId(), "PAID");
// 发送邮件
emailService.sendPaymentSuccessEmail(order);
// 发送短信
smsService.sendPaymentSuccessSMS(order);
// 赠送积分
pointsService.grantPoints(order.getUserId(), order.getAmount() * 0.01);
// 更新库存
inventoryService.decrementStock(order.getProductId(), order.getQuantity());
// 生成发货单
shippingService.createShippingOrder(order);
// 同步到数据仓库
dataWarehouseService.syncOrder(order);
}
}
问题:
1. 修改困难:新增一个处理步骤,必须修改OrderService代码
2. 测试困难:测试OrderService需要所有依赖服务都可用
3. 性能问题:串行执行,总响应时间 = 各步骤时间之和
4. 可用性问题:任何一个服务故障,整个流程失败
5. 扩展性问题:无法针对单个步骤独立扩容
消息队列解耦方案:
订单服务:
class OrderService {
void paymentSuccess(Order order) {
// 1. 更新订单状态
updateOrderStatus(order.getId(), "PAID");
// 2. 发布事件
messageQueue.publish("order.payment.success", order);
// 完成!响应时间:几十毫秒
}
}
邮件服务(订阅者):
messageQueue.subscribe("order.payment.success", (order) -> {
emailService.sendPaymentSuccessEmail(order);
});
短信服务(订阅者):
messageQueue.subscribe("order.payment.success", (order) -> {
smsService.sendPaymentSuccessSMS(order);
});
积分服务(订阅者):
messageQueue.subscribe("order.payment.success", (order) -> {
pointsService.grantPoints(order.getUserId(), order.getAmount() * 0.01);
});
// ... 其他订阅者
优势:
1. 修改简单:新增处理步骤,只需新增订阅者,无需改OrderService
2. 测试简单:OrderService只需验证消息是否正确发送
3. 性能好:OrderService快速返回,后续异步处理
4. 高可用:某个订阅者故障,不影响订单支付流程
5. 易扩展:可以针对单个订阅者独立扩容
设计理念2:异步
同步调用的性能瓶颈:
场景:社交平台发帖
同步方式:
1. 用户发布帖子
2. 保存帖子内容(100ms)
3. 内容审核(500ms)
4. 生成缩略图(200ms)
5. 推送给粉丝(300ms)
6. 更新推荐算法(400ms)
7. 数据分析记录(100ms)
总耗时:1.6秒
用户体验:
- 点击发布后,等待1.6秒才看到"发布成功"
- 界面卡顿,用户焦虑
- 高并发时,响应时间更长
消息队列异步方案:
异步方式:
1. 用户发布帖子
2. 保存帖子内容(100ms)
3. 发送消息到MQ(5ms)
4. 立即返回"发布成功"
总耗时:105ms
后台异步处理(并发):
- 审核服务:收到消息,开始审核(500ms)
- 图片服务:收到消息,生成缩略图(200ms)
- 推送服务:收到消息,推送给粉丝(300ms)
- 推荐服务:收到消息,更新算法(400ms)
- 分析服务:收到消息,记录数据(100ms)
实际完成时间:约500ms(最慢的审核服务)
用户体验:
- 点击发布后,105ms看到"发布成功"
- 界面流畅,无等待感
- 后续处理在后台完成,用户无感知
异步带来的复杂性:
问题:
1. 一致性:消息发送成功,但处理失败怎么办?
2. 顺序性:消息乱序怎么办?
3. 重复消费:消息被处理多次怎么办?
4. 监控难度:如何知道异步任务的执行状态?
解决方案:
1. 一致性:本地消息表 + 定时任务重试
2. 顺序性:分区消费 + 顺序队列
3. 重复消费:幂等性设计(唯一ID、数据库唯一约束)
4. 监控:消息追踪、链路追踪(如Jaeger)
设计理念3:削峰填谷
流量峰值的应对策略:
场景:抽奖活动
流量特征:
- 活动开始前:10 QPS
- 活动开始瞬间:50,000 QPS(5000倍)
- 持续高峰:5分钟
- 回落至:100 QPS
传统架构(直接处理):
- 服务器配置:支持10 QPS
- 峰值到来:服务器CPU 100%,内存耗尽
- 大量请求超时,用户体验极差
- 服务崩溃,雪崩效应
扩容方案:
- 扩容到5000倍(500台服务器)
- 成本:假设每台1000元/月,500台 = 50万元/月
- 只用5分钟,剩余时间闲置
- 成本/收益比极低
限流方案:
- 限制QPS为10
- 99.98%的请求被拒绝
- 用户体验极差,大量投诉
消息队列削峰方案:
架构:
用户请求 → 快速写入MQ → 消费者恒定速率处理
峰值时:
1. 50,000请求/秒快速写入MQ(几乎无延迟)
2. 返回用户:"请求已提交,排队中..."
3. MQ中积压:50,000 × 300秒 = 1500万条消息
消费处理:
- 消费者数量:10个
- 每个消费者速率:10 QPS
- 总处理能力:100 QPS
- 处理完1500万消息需要:约42小时
优化后:
- 增加消费者到100个
- 总处理能力:1000 QPS
- 处理完需要:约4小时
用户体验:
- 立即得到反馈:"排队中,预计4小时后开奖"
- 4小时后收到通知:"您已中奖"或"很遗憾未中奖"
- 可接受的等待时间
成本优化:
- 无需扩容服务器
- MQ存储成本极低(相对于服务器)
- 100个消费者可以用现有服务器
设计理念4:可靠性
消息可靠性的三个阶段:
生产者 → Broker → 消费者
每个阶段都可能丢失消息:
1. 生产者 → Broker:
- 网络故障,消息发送失败
- Broker宕机,消息未持久化
2. Broker:
- 磁盘损坏,消息丢失
- Broker宕机且未复制
3. Broker → 消费者:
- 消费者获取消息后崩溃,未处理
- 消费者处理失败,未重试
生产者可靠性保证:
问题:如何确保消息发送成功?
方案1:同步确认
producer.send(message);
boolean success = producer.waitForAck(); // 等待Broker确认
if (!success) {
// 重试或记录日志
}
优点:简单,可靠
缺点:性能差,每次发送都阻塞等待
方案2:异步确认
producer.send(message, new Callback() {
void onSuccess() {
// 发送成功
}
void onFailure(Exception e) {
// 发送失败,重试
retryQueue.add(message);
}
});
优点:性能好,非阻塞
缺点:复杂度高,需要处理回调
方案3:本地消息表
@Transactional
void createOrder(Order order) {
// 1. 保存订单
orderRepository.save(order);
// 2. 保存本地消息
localMessageRepository.save(new Message("order.created", order));
// 3. 提交事务
}
// 定时任务:扫描未发送的消息
@Scheduled(fixedDelay = 1000)
void sendPendingMessages() {
List<Message> messages = localMessageRepository.findPending();
for (Message msg : messages) {
producer.send(msg);
msg.setStatus("SENT");
}
}
优点:
- 消息和业务操作在同一个事务
- 保证消息一定发送
- 可重试
缺点:
- 需要额外的消息表
- 定时任务有延迟
Broker可靠性保证:
问题:如何防止Broker丢失消息?
方案1:消息持久化
配置:
- 消息持久化到磁盘
- 刷盘策略:同步刷盘(安全)vs 异步刷盘(性能)
同步刷盘:
- 消息写入磁盘后,才返回ACK
- 优点:不丢消息
- 缺点:性能差(磁盘IO是瓶颈)
异步刷盘:
- 消息写入操作系统缓存,立即返回ACK
- 定期批量刷盘(如1秒一次)
- 优点:性能好
- 缺点:可能丢失最后1秒的消息
选择:
- 金融、支付:同步刷盘
- 日志、监控:异步刷盘
方案2:副本机制
配置:
- 消息复制到多个Broker
- 副本数:通常3副本
工作流程:
1. 生产者发送消息到Leader
2. Leader写入本地日志
3. Leader复制到所有Follower
4. 所有Follower确认后,Leader返回ACK
容错能力:
- 3副本可以容忍2个Broker故障
- 自动选举新Leader
权衡:
- 副本越多,越可靠,但性能越差
- 通常3副本足够
消费者可靠性保证:
问题:如何确保消息被正确处理?
错误方式:自动ACK
channel.basicConsume("queue", true, (msg) -> {
processMessage(msg); // 如果这里抛异常,消息已被ACK,丢失了!
});
正确方式:手动ACK
channel.basicConsume("queue", false, (msg) -> {
try {
processMessage(msg);
channel.ack(msg); // 处理成功后才ACK
} catch (BusinessException e) {
// 业务异常,拒绝消息,不重试
channel.reject(msg, false);
} catch (Exception e) {
// 系统异常,拒绝消息并重新入队
channel.reject(msg, true);
}
});
重试策略:
1. 立即重试:可能立即再次失败
2. 延迟重试:等待一段时间后重试(指数退避)
- 第1次:1秒后
- 第2次:2秒后
- 第3次:4秒后
- ...
3. 死信队列:重试N次后,进入死信队列,人工处理
幂等性:
由于可能重试,消费者必须保证幂等性:
- 使用唯一ID去重(Redis SETNX)
- 数据库唯一约束
- 业务逻辑天然幂等(如SET操作)
2.2 消息队列的架构设计
核心组件1:Producer(生产者)
生产者的职责:
1. 消息创建:
- 业务数据序列化(JSON、Protobuf等)
- 添加元数据(时间戳、消息ID等)
2. 消息发送:
- 选择目标(Topic、Exchange、Queue)
- 指定路由规则(Routing Key、Partition Key)
- 发送到Broker
3. 可靠性保证:
- 等待确认(同步/异步)
- 失败重试
- 本地缓存(断网时)
4. 性能优化:
- 批量发送
- 压缩
- 异步发送
生产者的关键配置:
// Kafka 生产者配置
Properties props = new Properties();
// 基础配置
props.put("bootstrap.servers", "localhost:9092"); // Broker地址
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
// 可靠性配置
props.put("acks", "all"); // 等待所有副本确认
// acks=0: 不等待,最快但可能丢消息
// acks=1: 等待Leader确认
// acks=all: 等待所有副本确认,最可靠但最慢
props.put("retries", 3); // 失败重试次数
props.put("retry.backoff.ms", 100); // 重试间隔
// 性能配置
props.put("batch.size", 16384); // 批量大小16KB
props.put("linger.ms", 10); // 等待10ms,凑够一批再发送
props.put("compression.type", "lz4"); // 压缩算法
// 幂等性配置
props.put("enable.idempotence", true); // 启用幂等性,防止重复
KafkaProducer<String, String> producer = new KafkaProducer<>(props);
批量发送优化:
单条发送 vs 批量发送:
单条发送:
for (int i = 0; i < 10000; i++) {
producer.send(new Message("hello" + i));
}
// 10000次网络往返
// 耗时:假设每次1ms,总共10秒
批量发送:
List<Message> batch = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
batch.add(new Message("hello" + i));
if (batch.size() == 100) {
producer.sendBatch(batch); // 100条一批
batch.clear();
}
}
// 100次网络往返
// 耗时:100ms
性能提升:100倍!
注意:
- 批量大小:太小(如10条)提升有限,太大(如10000条)内存占用高
- 推荐:100-1000条/批,或1-16MB/批
核心组件2:Broker(消息服务器)
Broker的职责:
1. 消息存储:
- 持久化到磁盘
- 索引管理
- 过期消息清理
2. 消息路由:
- 根据规则将消息路由到队列
- 支持多种路由策略(Direct、Topic、Fanout等)
3. 集群管理:
- 节点间通信
- Leader选举
- 副本同步
4. 客户端管理:
- 连接管理
- 权限验证
- 流量控制
存储架构:
Kafka的存储设计:
Topic
└── Partition 0
├── 00000000000000000000.log (数据文件)
├── 00000000000000000000.index (索引文件)
└── 00000000000000000000.timeindex (时间索引)
└── Partition 1
├── 00000000000000000000.log
├── 00000000000000000000.index
└── 00000000000000000000.timeindex
└── Partition 2
└── ...
特点:
1. 顺序写:消息追加到日志末尾,利用磁盘顺序写性能(400MB/s)
2. 分段存储:每个Partition拆分为多个Segment(1GB一段)
3. 索引加速:稀疏索引,快速定位消息位置
4. 零拷贝:sendfile系统调用,减少数据拷贝
副本机制:
Kafka副本架构:
Topic: orders, Partition 0, Replication=3
Broker 1 (Leader):
- orders-0-leader (可读可写)
Broker 2 (Follower):
- orders-0-follower-1 (只读,同步Leader数据)
Broker 3 (Follower):
- orders-0-follower-2 (只读,同步Leader数据)
写入流程:
1. 生产者发送消息到Leader
2. Leader写入本地日志
3. Leader同步到Follower
4. 所有Follower确认后,Leader提交
5. Leader返回ACK给生产者
故障恢复:
- Broker 1(Leader)宕机
- Zookeeper检测到故障
- 从Follower中选举新Leader(Broker 2)
- 生产者和消费者自动切换到新Leader
ISR(In-Sync Replicas):
- 与Leader保持同步的副本集合
- 只有ISR中的副本才能被选为Leader
- 落后太多的副本会被移出ISR
核心组件3:Consumer(消费者)
消费者的职责:
1. 消息拉取:
- 从Broker拉取消息(Pull模式)
- 或Broker推送消息(Push模式)
2. 消息处理:
- 反序列化
- 业务逻辑处理
3. 位置管理:
- 记录消费位置(Offset)
- 提交Offset到Broker
4. 失败处理:
- 重试
- 死信队列
消费模型:
1. 点对点模型(Queue):
生产者 → Queue → 消费者1
→ 消费者2 (竞争消费)
→ 消费者3
特点:
- 一条消息只被一个消费者消费
- 消费后消息从队列删除
- 多个消费者负载均衡
适用:
- 任务分发
- 负载均衡
2. 发布订阅模型(Topic):
生产者 → Topic → 订阅者1(独立消费)
→ 订阅者2(独立消费)
→ 订阅者3(独立消费)
特点:
- 一条消息被多个订阅者消费
- 每个订阅者维护自己的消费位置
- 消息不会被删除(直到过期)
适用:
- 事件广播
- 日志收集
- 数据同步
消费者组(Consumer Group):
Kafka的消费者组设计:
Topic: orders (3个Partition)
Consumer Group 1:
- Consumer A: 消费 Partition 0
- Consumer B: 消费 Partition 1
- Consumer C: 消费 Partition 2
Consumer Group 2:
- Consumer D: 消费 Partition 0, 1, 2
特点:
1. 同一个Consumer Group内:
- 一个Partition只能被一个Consumer消费
- 实现负载均衡
2. 不同Consumer Group间:
- 独立消费,互不影响
- 实现发布订阅
3. 分区分配策略:
- Range:按Partition范围分配
- RoundRobin:轮询分配
- Sticky:尽量保持之前的分配,减少Rebalance
Rebalance(重平衡):
- 触发条件:Consumer加入/退出,Partition增加
- 过程:停止消费 → 重新分配Partition → 恢复消费
- 问题:Rebalance期间,整个Consumer Group停止消费
- 优化:减少Rebalance频率,缩短Rebalance时间
Offset管理:
Offset(偏移量):表示消费者在Partition中的消费位置
自动提交 vs 手动提交:
1. 自动提交:
props.put("enable.auto.commit", "true");
props.put("auto.commit.interval.ms", "5000"); // 每5秒自动提交
优点:简单,无需手动管理
缺点:可能丢消息(消息处理失败,但Offset已提交)
2. 手动提交:
props.put("enable.auto.commit", "false");
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
try {
processMessage(record.value());
consumer.commitSync(); // 同步提交
// 或 consumer.commitAsync(); // 异步提交
} catch (Exception e) {
// 处理失败,不提交Offset,下次重新消费
}
}
}
优点:精确控制,不丢消息
缺点:需要手动管理,复杂度高
最佳实践:
- 重要业务:手动提交
- 非关键日志:自动提交
核心组件4:Topic & Partition
Topic(主题):
定义:消息的分类,类似数据库的表
特点:
- 逻辑概念,实际数据存储在Partition中
- 支持多个生产者和消费者
- 可配置副本数、保留时间等
示例:
- orders: 订单相关消息
- user_events: 用户行为事件
- logs: 应用日志
Partition(分区):
定义:Topic的物理分片,实际存储单元
作用:
1. 水平扩展:
- 单个Topic可以分布在多个Broker
- 突破单机存储和性能限制
2. 并行处理:
- 多个Partition可以并行读写
- Consumer可以并行消费
3. 有序性:
- 单个Partition内消息有序
- 不同Partition间无序
分区策略:
1. 轮询(Round-Robin):
- 消息依次发送到各个Partition
- 负载均衡
2. 按Key分区:
- 相同Key的消息发送到同一Partition
- 保证相同Key的消息有序
示例:
// 用户ID作为Key,同一用户的消息在同一Partition,保证顺序
producer.send(new ProducerRecord<>("user_events", userId, message));
3. 自定义分区器:
class CustomPartitioner implements Partitioner {
int partition(String topic, Object key, byte[] keyBytes,
Object value, byte[] valueBytes, Cluster cluster) {
// 自定义分区逻辑
if (key == null) return 0;
return Math.abs(key.hashCode()) % cluster.partitionCountForTopic(topic);
}
}
分区数选择:
- 太少:无法充分利用集群资源
- 太多:管理开销大,Rebalance慢
- 推荐:节点数 × (1-3)
例如:10个Broker,建议10-30个Partition
2.3 消息队列与同类技术的核心差异
消息队列 vs RPC(远程过程调用)
RPC的特点:
同步调用:
OrderService:
Order order = createOrder();
Payment payment = paymentService.createPayment(order); // 同步等待
Shipping shipping = shippingService.createShipping(order); // 同步等待
return order;
特点:
- 强耦合:调用方需要知道被调用方的接口
- 同步阻塞:等待远程服务返回
- 实时性:立即得到结果
适用场景:
- 需要立即得到结果:如查询余额、验证密码
- 强一致性要求:如转账
- 调用链路短:2-3层
消息队列的特点:
异步解耦:
OrderService:
Order order = createOrder();
messageQueue.send("order.created", order); // 异步发送,立即返回
return order;
PaymentService:
messageQueue.subscribe("order.created", (order) -> {
createPayment(order);
});
ShippingService:
messageQueue.subscribe("order.created", (order) -> {
createShipping(order);
});
特点:
- 松耦合:通过消息通信,服务间无直接依赖
- 异步非阻塞:发送后立即返回
- 最终一致性:不保证实时
适用场景:
- 不需要立即结果:如发送邮件、生成报表
- 解耦:服务间独立演进
- 流量削峰:缓冲突发流量
对比总结:
| 维度 | RPC | 消息队列 |
|---|---|---|
| 调用方式 | 同步 | 异步 |
| 耦合度 | 强耦合 | 松耦合 |
| 性能 | 取决于被调用方 | 快速返回 |
| 可用性 | 被调用方故障则失败 | 被调用方故障不影响发送方 |
| 一致性 | 强一致性 | 最终一致性 |
| 适用场景 | 查询、验证、需要结果 | 通知、异步处理、解耦 |
混合使用:
实际业务中,两者经常混合使用:
示例:电商下单
1. 同步RPC:
- 检查库存(需要立即知道是否有货)
- 创建订单(需要立即返回订单号)
2. 异步消息队列:
- 发送订单确认邮件
- 推送给物流系统
- 更新用户积分
- 数据分析
原则:
- 需要结果或强一致性:用RPC
- 不需要结果或可接受最终一致性:用消息队列
消息队列 vs 数据库
数据库存储消息的方案:
-- 消息表
CREATE TABLE messages (
id BIGINT PRIMARY KEY,
topic VARCHAR(100),
content TEXT,
status VARCHAR(20), -- PENDING, PROCESSING, DONE
created_at TIMESTAMP
);
-- 生产者:插入消息
INSERT INTO messages (topic, content, status) VALUES ('order.created', '...', 'PENDING');
-- 消费者:轮询查询
SELECT * FROM messages WHERE status = 'PENDING' LIMIT 100;
问题:
1. 性能差:
- 每次轮询都要查询数据库
- SELECT查询走索引,但仍比消息队列慢
- 大量消息时,表会很大,查询慢
2. 并发问题:
- 多个消费者可能取到同一条消息
- 需要加锁或乐观锁,复杂且影响性能
3. 消息堆积:
- 消息未及时消费,表越来越大
- 需要定期清理已消费消息
4. 功能缺失:
- 无顺序保证
- 无重试机制
- 无死信队列
- 无流量控制
专业消息队列的优势:
1. 高性能:
- 内存队列 + 磁盘持久化
- 批量操作
- 零拷贝技术
- 性能:十万到百万QPS
2. 高可用:
- 副本机制
- 自动故障转移
- 集群部署
3. 丰富功能:
- 消息路由
- 消息重试
- 死信队列
- 延迟消息
- 优先级队列
4. 运维友好:
- 监控界面
- 性能指标
- 告警机制
何时用数据库,何时用消息队列:
使用数据库的场景:
- 消息量小(如每秒几条)
- 需要复杂查询(如按时间范围、多条件过滤)
- 需要持久化很长时间(如几个月、几年)
- 已有数据库基础设施,不想引入新组件
使用消息队列的场景:
- 消息量大(如每秒成百上千)
- 不需要复杂查询
- 保留时间短(如几天、几周)
- 需要高性能、高可用
- 需要专业功能(如顺序、重试、延迟)
实际选择:
- 小项目、简单场景:数据库也能满足
- 中大型项目、高并发:必须用消息队列
消息队列 vs 事件总线(Event Bus)
事件总线(进程内):
// Guava EventBus示例
EventBus eventBus = new EventBus();
// 订阅
eventBus.register(new Object() {
@Subscribe
public void handleOrderCreated(OrderCreatedEvent event) {
System.out.println("Order created: " + event.getOrderId());
}
});
// 发布
eventBus.post(new OrderCreatedEvent(123));
特点:
- 进程内通信:只能在同一个JVM内
- 内存传递:极快,纳秒级
- 无持久化:进程重启,事件丢失
- 无分布式:无法跨服务
适用:
- 单体应用内的模块解耦
- 性能要求极高,可接受丢失
消息队列(分布式):
// Kafka示例
producer.send(new ProducerRecord<>("order.created", order));
consumer.subscribe(Arrays.asList("order.created"));
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
System.out.println("Order created: " + record.value());
}
}
特点:
- 分布式通信:跨进程、跨服务器
- 持久化:消息存储在磁盘
- 高可用:集群、副本
- 性能:毫秒级
适用:
- 微服务间通信
- 分布式系统
- 需要可靠性和持久化
对比:
| 维度 | 事件总线 | 消息队列 |
|---|---|---|
| 范围 | 进程内 | 跨进程、分布式 |
| 性能 | 纳秒级 | 毫秒级 |
| 可靠性 | 不保证(内存) | 高可靠(持久化) |
| 功能 | 简单 | 丰富(重试、死信等) |
| 复杂度 | 低 | 高(需要部署维护) |
| 适用 | 单体应用 | 分布式系统 |
三、基础知识
3.1 RabbitMQ 安装部署
单机安装(开发环境)
Docker 安装(推荐):
# 1. 拉取镜像(带管理界面)
docker pull rabbitmq:3.12-management
# 2. 运行容器
docker run -d \
--name rabbitmq \
-p 5672:5672 \ # AMQP端口
-p 15672:15672 \ # 管理界面端口
-e RABBITMQ_DEFAULT_USER=admin \
-e RABBITMQ_DEFAULT_PASS=admin123 \
rabbitmq:3.12-management
# 3. 验证
# 访问管理界面:http://localhost:15672
# 用户名:admin,密码:admin123
# 4. 查看日志
docker logs -f rabbitmq
Linux 安装:
# Ubuntu/Debian
# 1. 添加仓库
echo "deb https://dl.cloudsmith.io/public/rabbitmq/rabbitmq-server/deb/ubuntu jammy main" | sudo tee /etc/apt/sources.list.d/rabbitmq.list
# 2. 导入GPG密钥
curl -1sLf 'https://dl.cloudsmith.io/public/rabbitmq/rabbitmq-server/gpg.E495BB49CC4BBE5B.key' | sudo apt-key add -
# 3. 安装
sudo apt update
sudo apt install rabbitmq-server
# 4. 启动
sudo systemctl start rabbitmq-server
sudo systemctl enable rabbitmq-server
# 5. 启用管理插件
sudo rabbitmq-plugins enable rabbitmq_management
# 6. 创建用户
sudo rabbitmqctl add_user admin admin123
sudo rabbitmqctl set_user_tags admin administrator
sudo rabbitmqctl set_permissions -p / admin ".*" ".*" ".*"
配置文件:
# /etc/rabbitmq/rabbitmq.conf
# 监听地址
listeners.tcp.default = 5672
# 管理插件
management.tcp.port = 15672
# 内存限制(40%物理内存)
vm_memory_high_watermark.relative = 0.4
# 磁盘限制(50GB)
disk_free_limit.absolute = 50GB
# 心跳间隔
heartbeat = 60
# 日志级别
log.console.level = info
集群部署(生产环境)
集群架构:
节点1(Disk): rabbitmq-1 (192.168.1.101)
节点2(Disk): rabbitmq-2 (192.168.1.102)
节点3(RAM): rabbitmq-3 (192.168.1.103)
特点:
- Disk节点:元数据持久化到磁盘,重启不丢失
- RAM节点:元数据只在内存,性能更好但重启丢失
- 建议:至少2个Disk节点
集群搭建:
# 节点1
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl start_app
# 节点2加入集群
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl join_cluster rabbit@rabbitmq-1
rabbitmqctl start_app
# 节点3加入集群(RAM节点)
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl join_cluster --ram rabbit@rabbitmq-1
rabbitmqctl start_app
# 查看集群状态
rabbitmqctl cluster_status
# 输出:
{running_nodes,[rabbit@rabbitmq-3,rabbit@rabbitmq-2,rabbit@rabbitmq-1]},
{cluster_name,<<"rabbit@rabbitmq-1">>},
{partitions,[]},
{alarms,[{rabbit@rabbitmq-3,[]},{rabbit@rabbitmq-2,[]},{rabbit@rabbitmq-1,[]}]}
镜像队列(高可用):
# 设置所有队列镜像到所有节点
rabbitmqctl set_policy ha-all "^" '{"ha-mode":"all"}'
# 设置队列镜像到2个节点
rabbitmqctl set_policy ha-two "^" '{"ha-mode":"exactly","ha-params":2}'
# 设置特定队列镜像
rabbitmqctl set_policy ha-orders "^order\\." '{"ha-mode":"all"}'
3.2 基本配置和核心概念
Exchange(交换机)类型
1. Direct Exchange(直连交换机):
// 场景:日志分级处理
// 1. 声明交换机
channel.exchangeDeclare("logs_direct", BuiltinExchangeType.DIRECT, true);
// 2. 声明队列
channel.queueDeclare("error_queue", true, false, false, null);
channel.queueDeclare("info_queue", true, false, false, null);
// 3. 绑定(完全匹配routing key)
channel.queueBind("error_queue", "logs_direct", "error");
channel.queueBind("info_queue", "logs_direct", "info");
// 4. 发送消息
channel.basicPublish("logs_direct", "error", null, "数据库连接失败".getBytes());
// 只有error_queue会收到
channel.basicPublish("logs_direct", "info", null, "用户登录".getBytes());
// 只有info_queue会收到
适用场景:
- 日志分级
- 任务分发(按类型)
- 严格路由规则
2. Topic Exchange(主题交换机):
// 场景:订单事件处理
// 通配符:
// * : 匹配一个单词
// # : 匹配零个或多个单词
channel.exchangeDeclare("order_events", BuiltinExchangeType.TOPIC, true);
// 队列1:处理所有创建事件
channel.queueBind("create_queue", "order_events", "order.*.created");
// 匹配:order.payment.created, order.shipping.created
// 不匹配:order.created, order.payment.success
// 队列2:处理所有订单事件
channel.queueBind("all_order_queue", "order_events", "order.#");
// 匹配:order.created, order.payment.created, order.payment.success.confirmed
// 发送消息
channel.basicPublish("order_events", "order.payment.created", null, order.toBytes());
// create_queue 和 all_order_queue 都会收到
适用场景:
- 灵活的路由规则
- 多级分类(如:地区.城市.店铺)
- 订阅特定模式的事件
3. Fanout Exchange(扇出交换机):
// 场景:广播消息
channel.exchangeDeclare("notifications", BuiltinExchangeType.FANOUT, true);
// 多个队列绑定到同一个Exchange
channel.queueBind("email_queue", "notifications", ""); // routing key忽略
channel.queueBind("sms_queue", "notifications", "");
channel.queueBind("push_queue", "notifications", "");
// 发送消息
channel.basicPublish("notifications", "", null, "系统维护通知".getBytes());
// 所有队列(email, sms, push)都会收到
适用场景:
- 广播通知
- 缓存更新(广播到所有缓存节点)
- 实时数据同步
4. Headers Exchange(头部交换机):
// 场景:复杂路由规则
// 声明交换机
channel.exchangeDeclare("reports", BuiltinExchangeType.HEADERS, true);
// 绑定规则
Map<String, Object> headers = new HashMap<>();
headers.put("x-match", "all"); // all:所有属性都匹配, any:任一属性匹配
headers.put("format", "pdf");
headers.put("type", "report");
channel.queueBind("pdf_report_queue", "reports", "", headers);
// 发送消息
AMQP.BasicProperties props = new AMQP.BasicProperties.Builder()
.headers(Map.of("format", "pdf", "type", "report"))
.build();
channel.basicPublish("reports", "", props, reportData);
适用场景:
- 多维度路由
- 复杂匹配规则
- 不适合用routing key表达的场景
注意:性能不如Direct和Topic,少用
Queue(队列)属性
队列声明:
Map<String, Object> args = new HashMap<>();
// 1. 持久化
boolean durable = true; // 队列持久化(Broker重启后仍存在)
// 2. 独占
boolean exclusive = false; // false:可被多个连接使用, true:只能当前连接使用
// 3. 自动删除
boolean autoDelete = false; // false:不自动删除, true:最后一个消费者断开后删除
// 4. 消息TTL(Time To Live)
args.put("x-message-ttl", 60000); // 消息60秒后过期
// 5. 队列TTL
args.put("x-expires", 300000); // 队列300秒无人使用后自动删除
// 6. 队列最大长度
args.put("x-max-length", 10000); // 最多10000条消息
// 7. 队列最大大小
args.put("x-max-length-bytes", 104857600); // 最大100MB
// 8. 溢出行为
args.put("x-overflow", "reject-publish"); // 超出后拒绝新消息
// 或 "drop-head" - 删除最旧的消息
// 9. 死信交换机
args.put("x-dead-letter-exchange", "dlx_exchange");
args.put("x-dead-letter-routing-key", "dlx");
// 10. 优先级
args.put("x-max-priority", 10); // 支持0-10的优先级
channel.queueDeclare("my_queue", durable, exclusive, autoDelete, args);
Message(消息)属性
// 消息属性
AMQP.BasicProperties props = new AMQP.BasicProperties.Builder()
// 1. 持久化
.deliveryMode(2) // 1:不持久化, 2:持久化到磁盘
// 2. 优先级
.priority(5) // 0-10,需要队列支持优先级
// 3. 消息ID
.messageId(UUID.randomUUID().toString())
// 4. 时间戳
.timestamp(new Date())
// 5. 过期时间
.expiration("60000") // 60秒后过期(字符串!)
// 6. 内容类型
.contentType("application/json")
.contentEncoding("UTF-8")
// 7. 关联ID(用于RPC)
.correlationId("abc-123")
// 8. 回复队列(用于RPC)
.replyTo("rpc_reply_queue")
// 9. 自定义头部
.headers(Map.of(
"source", "order-service",
"retry", 0
))
// 10. 应用ID
.appId("my-application")
.build();
channel.basicPublish("exchange", "routing.key", props, message.getBytes());
3.3 Java 客户端基础使用
生产者代码示例
import com.rabbitmq.client.*;
public class Producer {
private static final String EXCHANGE_NAME = "orders";
private static final String ROUTING_KEY = "order.created";
public static void main(String[] args) throws Exception {
// 1. 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setPort(5672);
factory.setUsername("admin");
factory.setPassword("admin123");
factory.setVirtualHost("/");
// 连接池配置
factory.setAutomaticRecoveryEnabled(true); // 自动重连
factory.setNetworkRecoveryInterval(5000); // 重连间隔5秒
// 2. 创建连接
try (Connection connection = factory.newConnection();
Channel channel = connection.createChannel()) {
// 3. 声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT, true);
// 4. 构造消息
String message = "{\"orderId\":123,\"userId\":456,\"amount\":99.99}";
AMQP.BasicProperties props = new AMQP.BasicProperties.Builder()
.deliveryMode(2) // 持久化
.contentType("application/json")
.messageId(UUID.randomUUID().toString())
.timestamp(new Date())
.build();
// 5. 发送消息
channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, props, message.getBytes("UTF-8"));
System.out.println("消息发送成功: " + message);
}
}
}
消费者代码示例
import com.rabbitmq.client.*;
public class Consumer {
private static final String QUEUE_NAME = "order_queue";
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setPort(5672);
factory.setUsername("admin");
factory.setPassword("admin123");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// 1. 声明队列
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
// 2. 设置QoS(限流)
channel.basicQos(10); // 每次最多10条未确认消息
// 3. 创建消费者
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println("收到消息: " + message);
try {
// 处理业务逻辑
processMessage(message);
// 手动ACK
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
} catch (BusinessException e) {
// 业务异常,拒绝消息,不重试
System.err.println("业务处理失败: " + e.getMessage());
channel.basicReject(delivery.getEnvelope().getDeliveryTag(), false);
} catch (Exception e) {
// 系统异常,拒绝消息并重新入队
System.err.println("系统异常,重新入队: " + e.getMessage());
channel.basicNack(delivery.getEnvelope().getDeliveryTag(), false, true);
}
};
// 4. 开始消费(关闭自动ACK)
channel.basicConsume(QUEUE_NAME, false, deliverCallback, consumerTag -> {});
System.out.println("等待消息...");
}
private static void processMessage(String message) throws Exception {
// 业务处理逻辑
Thread.sleep(100); // 模拟处理耗时
}
}
Spring Boot 集成
1. 添加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2. 配置文件:
spring:
rabbitmq:
host: localhost
port: 5672
username: admin
password: admin123
virtual-host: /
# 生产者确认
publisher-confirm-type: correlated
publisher-returns: true
# 消费者配置
listener:
simple:
acknowledge-mode: manual # 手动ACK
concurrency: 5 # 最小消费者数
max-concurrency: 10 # 最大消费者数
prefetch: 10 # 每次拉取10条
3. 配置类:
@Configuration
public class RabbitMQConfig {
// 声明Exchange
@Bean
public DirectExchange orderExchange() {
return new DirectExchange("orders", true, false);
}
// 声明Queue
@Bean
public Queue orderQueue() {
Map<String, Object> args = new HashMap<>();
args.put("x-message-ttl", 60000); // 消息TTL
args.put("x-max-length", 10000); // 最大长度
return new Queue("order_queue", true, false, false, args);
}
// 绑定
@Bean
public Binding orderBinding(DirectExchange orderExchange, Queue orderQueue) {
return BindingBuilder.bind(orderQueue)
.to(orderExchange)
.with("order.created");
}
}
4. 生产者:
@Service
public class OrderService {
@Autowired
private RabbitTemplate rabbitTemplate;
public void createOrder(Order order) {
// 业务逻辑
orderRepository.save(order);
// 发送消息
rabbitTemplate.convertAndSend("orders", "order.created", order, message -> {
MessageProperties props = message.getMessageProperties();
props.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
props.setContentType("application/json");
props.setMessageId(UUID.randomUUID().toString());
return message;
});
}
}
5. 消费者:
@Component
public class OrderConsumer {
@RabbitListener(queues = "order_queue")
public void handleOrderCreated(@Payload Order order,
@Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag,
Channel channel) throws IOException {
try {
// 处理业务逻辑
System.out.println("处理订单: " + order.getOrderId());
// 手动ACK
channel.basicAck(deliveryTag, false);
} catch (Exception e) {
// 拒绝并重新入队
channel.basicNack(deliveryTag, false, true);
}
}
}
3.6 "Hello World"示例
最简单的点对点消息
生产者:
public class HelloWorldProducer {
public static void main(String[] args) throws Exception {
// 1. 创建连接
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// 2. 声明队列
channel.queueDeclare("hello", false, false, false, null);
// 3. 发送消息
String message = "Hello World!";
channel.basicPublish("", "hello", null, message.getBytes());
System.out.println("发送: " + message);
// 4. 关闭连接
channel.close();
connection.close();
}
}
消费者:
public class HelloWorldConsumer {
public static void main(String[] args) throws Exception {
// 1. 创建连接
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// 2. 声明队列(与生产者一致)
channel.queueDeclare("hello", false, false, false, null);
// 3. 创建消费回调
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println("收到: " + message);
};
// 4. 开始消费
channel.basicConsume("hello", true, deliverCallback, consumerTag -> {});
System.out.println("等待消息...");
}
}
通过以上基础知识的学习,你已经掌握了消息队列的核心概念和RabbitMQ的基本操作,可以开始构建简单的异步消息应用了!接下来我们将深入学习更高级的功能和实战场景。
四、核心功能详解
(由于篇幅限制,此处是文档的前三部分。完整文档将继续包含第四到第十三部分的详细内容,总计约2800-3000行,涵盖核心功能、应用场景、进阶技巧、高阶知识、生产环境实践、最佳实践、学习路线、学习资源、常见问题和实战练习等所有章节。)
此文档将继续完善后续章节...