📖 开场:医院的启示
想象你去医院看病 🏥:
单点医院(低可用):
整个城市只有1家医院
↓
医院停电 💀
↓
所有病人无法看病!😱
可用性:95%(一年宕机18天)
多点医院(高可用):
城市有5家医院,互为备份
↓
1家医院停电 💀
↓
病人去其他4家医院!✅
可用性:99.99%(一年宕机52分钟)
这就是高可用(High Availability, HA)!
定义:系统能够持续提供服务,即使部分组件故障也不影响整体
目标:
- 99.9%(3个9):一年宕机8.76小时 😐
- 99.99%(4个9):一年宕机52.56分钟 😊
- 99.999%(5个9):一年宕机5.26分钟 ⭐⭐⭐⭐⭐
🎯 高可用的核心原则
1️⃣ 无单点故障(No Single Point of Failure)
单点故障 = 一个组件挂了,整个系统就挂了 💀
❌ 单点架构:
Producer → Broker(只有1个)→ Consumer
↑
单点故障!💀
✅ 多副本架构:
Producer → Broker-1(Master)→ Consumer
Broker-2(Slave)
Broker-3(Slave)
↑
Broker-1挂了,切到Broker-2!✅
2️⃣ 故障自动转移(Failover)
手动切换:
Broker-1挂了 💀
↓
运维人员发现(5分钟)
↓
手动切换到Broker-2(10分钟)
↓
总计:15分钟不可用!😰
自动切换:
Broker-1挂了 💀
↓
监控发现(5秒)
↓
自动切换到Broker-2(10秒)
↓
总计:15秒不可用!😊
3️⃣ 数据冗余(Data Redundancy)
单副本:
数据只存1份
↓
磁盘坏了 💀
↓
数据永久丢失!😱
多副本:
数据存3份
↓
磁盘1坏了 💀
↓
还有磁盘2、磁盘3!✅
4️⃣ 负载均衡(Load Balancing)
不均衡:
Broker-1:处理90%的请求 🔥(过载)
Broker-2:处理5%的请求 😴(闲置)
Broker-3:处理5%的请求 😴(闲置)
均衡:
Broker-1:处理33%的请求 😊
Broker-2:处理33%的请求 😊
Broker-3:处理34%的请求 😊
🏗️ 高可用消息队列架构设计
🎯 整体架构
高可用消息队列架构
┌──────────────────────────────────────────────────────┐
│ Producer集群 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │Producer1│ │Producer2│ │Producer3│ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
└────────┼───────────┼───────────┼─────────────────────┘
│ │ │
└───────────┼───────────┘
│ 负载均衡
↓
┌──────────────────────────────────────────────────────┐
│ NameServer集群 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │NameServer│ │NameServer│ │NameServer│ │
│ │ 1 │ │ 2 │ │ 3 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ 路由信息注册与发现 │
└──────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────┐
│ Broker集群 │
│ │
│ ┌────────────────┐ ┌────────────────┐ │
│ │ Broker-a │ │ Broker-b │ │
│ │ (Master) │ │ (Master) │ │
│ │ 10.0.1.100 │ │ 10.0.1.101 │ │
│ └────────┬───────┘ └────────┬───────┘ │
│ │ 主从复制 │ │
│ ↓ ↓ │
│ ┌────────────────┐ ┌────────────────┐ │
│ │ Broker-a-s │ │ Broker-b-s │ │
│ │ (Slave) │ │ (Slave) │ │
│ │ 10.0.1.102 │ │ 10.0.1.103 │ │
│ └────────────────┘ └────────────────┘ │
│ │
│ 特点: │
│ - 多Master多Slave │
│ - 异步复制/同步双写 │
│ - 自动故障转移 │
└──────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────┐
│ Consumer集群 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │Consumer1│ │Consumer2│ │Consumer3│ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ 特点: │
│ - 消费者组负载均衡 │
│ - 自动Rebalance │
│ - 失败重试 │
└──────────────────────────────────────────────────────┘
🔧 各组件的高可用设计
1️⃣ NameServer高可用设计
什么是NameServer?
NameServer = 消息队列的"导航系统" 🧭
作用:
- 路由信息管理(Broker在哪?Topic在哪?)
- Broker注册与心跳检测
- 提供路由信息给Producer和Consumer
架构设计
单点NameServer(❌ 不推荐):
┌─────────────┐
│ NameServer │ ← 单点故障!💀
└─────────────┘
多点NameServer(✅ 推荐):
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ NameServer1 │ │ NameServer2 │ │ NameServer3 │
└─────────────┘ └─────────────┘ └─────────────┘
↑ ↑ ↑
└─────────────────┴─────────────────┘
各自独立,互不通信
特点:
- ✅ 无状态设计(各个NameServer独立)
- ✅ Broker向所有NameServer注册
- ✅ Client从任意NameServer获取路由信息
- ✅ 一个NameServer挂了不影响整体
配置示例
RocketMQ配置:
# Broker配置:连接多个NameServer
namesrvAddr=10.0.1.10:9876;10.0.1.11:9876;10.0.1.12:9876
Producer/Consumer配置:
// 配置多个NameServer地址
producer.setNamesrvAddr("10.0.1.10:9876;10.0.1.11:9876;10.0.1.12:9876");
consumer.setNamesrvAddr("10.0.1.10:9876;10.0.1.11:9876;10.0.1.12:9876");
Client自动故障转移:
Client首先连接NameServer1
↓
NameServer1挂了 💀
↓
Client自动切换到NameServer2 ✅
2️⃣ Broker高可用设计
架构模式
模式1:多Master模式 😐
┌─────────┐ ┌─────────┐ ┌─────────┐
│Master-a │ │Master-b │ │Master-c │
└─────────┘ └─────────┘ └─────────┘
优点:
- ✅ 配置简单
- ✅ 性能最高
- ✅ 无单点故障
缺点:
- ❌ Master挂了,该Master上的消息无法消费(直到恢复)
- ❌ 可能丢失少量数据(异步刷盘时)
适用:对可用性要求不高的场景
模式2:多Master多Slave(异步复制)⭐⭐⭐⭐⭐
Master-a ──复制──> Slave-a
Master-b ──复制──> Slave-b
Master-c ──复制──> Slave-c
优点:
- ✅ 高可用(Master挂了,Slave继续提供服务)
- ✅ 性能高(异步复制)
- ✅ 最常用!
缺点:
- ❌ Master挂了,Slave可能没有最新数据
- ❌ 需要手动切换(或使用DLedger自动切换)
配置:
# Master配置
brokerRole=ASYNC_MASTER
brokerId=0
# Slave配置
brokerRole=SLAVE
brokerId=1
模式3:多Master多Slave(同步双写)🛡️
Master-a ══同步══> Slave-a
Master-b ══同步══> Slave-b
Master-c ══同步══> Slave-c
优点:
- ✅ 数据安全性极高(Master和Slave都有数据)
- ✅ Master挂了不丢数据
缺点:
- ❌ 性能较低(需要等待Slave确认)
配置:
# Master配置
brokerRole=SYNC_MASTER
brokerId=0
# Slave配置
brokerRole=SLAVE
brokerId=1
模式4:DLedger高可用集群(自动故障转移)🚀⭐⭐⭐⭐⭐
┌────────────┐
│ Leader │ ← 当前Leader
├────────────┤
│ Follower 1 │
├────────────┤
│ Follower 2 │
└────────────┘
Leader挂了 💀
↓
自动选举新Leader(基于Raft协议)
↓
Follower 1 成为新Leader ✅
优点:
- ✅ 自动故障转移(无需人工介入)
- ✅ 强一致性(基于Raft协议)
- ✅ 数据不丢失
- ✅ RocketMQ 4.5+推荐方案!
配置:
# 启用DLedger模式
enableDLegerCommitLog=true
# DLedger Raft组配置
dLegerGroup=broker-a
dLegerPeers=n0-127.0.0.1:40911;n1-127.0.0.2:40911;n2-127.0.0.3:40911
dLegerSelfId=n0
3️⃣ Producer高可用设计
重试机制
DefaultMQProducer producer = new DefaultMQProducer("producer_group");
// ⭐ 配置重试次数
producer.setRetryTimesWhenSendFailed(3); // 同步发送失败重试3次
producer.setRetryTimesWhenSendAsyncFailed(3); // 异步发送失败重试3次
// ⭐ 发送超时时间
producer.setSendMsgTimeout(10000); // 10秒
重试流程:
发送到Broker-a → 失败 💀
↓
重试1:发送到Broker-b → 失败 💀
↓
重试2:发送到Broker-c → 成功 ✅
故障规避
// ⭐ 启用故障规避
producer.setSendLatencyFaultEnable(true);
工作原理:
Broker-a响应慢(延迟1秒) 🐌
↓
Producer标记Broker-a为"不可用"(隔离一段时间)
↓
下次发送时,跳过Broker-a,直接发送到Broker-b ✅
↓
Broker-a恢复后,自动加回
多NameServer连接
producer.setNamesrvAddr("ns1:9876;ns2:9876;ns3:9876");
好处:
- NameServer1挂了 → 自动切换到NameServer2
- 无需人工介入
4️⃣ Consumer高可用设计
消费者组负载均衡
Topic: orders (4个分区)
Consumer Group: order-group (4个消费者)
分配:
Consumer-1 → Partition-0
Consumer-2 → Partition-1
Consumer-3 → Partition-2
Consumer-4 → Partition-3
Consumer-2挂了 💀
↓
自动Rebalance
↓
新分配:
Consumer-1 → Partition-0, Partition-1 ← 接管Partition-1
Consumer-3 → Partition-2
Consumer-4 → Partition-3
消费进度持久化
Kafka:
// 消费进度存储在Kafka内部Topic: __consumer_offsets
// 即使所有Consumer都挂了,重启后可以从上次的offset继续消费
RocketMQ:
// 消费进度存储在Broker
// Consumer挂了重启,可以继续消费
消费重试和死信队列
@Override
public ConsumeConcurrentlyStatus consumeMessage(
List<MessageExt> msgs,
ConsumeConcurrentlyContext context
) {
try {
processMessage(msgs.get(0));
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
} catch (Exception e) {
log.error("消费失败", e);
// ⭐ 返回失败,进入重试
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
}
重试流程:
第1次消费失败 → 10秒后重试
第2次消费失败 → 30秒后重试
第3次消费失败 → 1分钟后重试
...
第16次消费失败 → 2小时后重试
超过16次 → 进入死信队列(人工处理)
🔐 数据可靠性保证
1️⃣ 持久化机制
同步刷盘 vs 异步刷盘:
同步刷盘:
Producer发送消息 → Broker写入磁盘 → 返回成功
安全但慢 🐌
异步刷盘:
Producer发送消息 → Broker写入内存 → 立即返回成功 → 后台异步写磁盘
快但可能丢失 ⚡
推荐配置:
# 重要数据:同步刷盘
flushDiskType=SYNC_FLUSH
# 一般数据:异步刷盘
flushDiskType=ASYNC_FLUSH
2️⃣ 主从复制
异步复制 vs 同步双写:
异步复制:
Master写入 → 立即返回 → 后台复制到Slave
性能高,可能丢数据 ⚡
同步双写:
Master写入 → 等待Slave确认 → 返回成功
安全但慢 🛡️
推荐配置:
# 重要数据:同步双写
brokerRole=SYNC_MASTER
# 一般数据:异步复制
brokerRole=ASYNC_MASTER
3️⃣ 消息ACK机制
RocketMQ:
Producer发送 → Broker存储 → 返回ACK
Consumer消费 → 处理成功 → 返回ACK
Kafka:
// ⭐ acks配置
props.put("acks", "all"); // 等待所有ISR副本确认
📊 监控告警体系
1️⃣ 监控指标
Broker监控:
- CPU使用率 > 80%:告警
- 内存使用率 > 90%:告警
- 磁盘使用率 > 85%:告警
- 磁盘IO > 90%:告警
- 网络流量异常:告警
Topic监控:
- 消息积压 > 10000:告警
- 消费延迟 > 1分钟:告警
- 消息发送失败率 > 1%:告警
Consumer监控:
- Consumer离线:告警
- 消费速率 < 生产速率:告警
- 重试次数过多:告警
2️⃣ 监控工具
Prometheus + Grafana:
# prometheus.yml
scrape_configs:
- job_name: 'rocketmq'
static_configs:
- targets: ['broker1:5557', 'broker2:5557', 'broker3:5557']
RocketMQ Console:
Web界面:http://console:8080
功能:
- Topic管理
- 消息查询
- 消费者管理
- 集群监控
3️⃣ 告警规则
# alertmanager.yml
groups:
- name: rocketmq
rules:
- alert: BrokerDown
expr: up{job="rocketmq"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: "Broker {{ $labels.instance }} is down"
- alert: MessageBacklog
expr: rocketmq_consumer_lag > 10000
for: 5m
labels:
severity: warning
annotations:
summary: "Consumer lag is {{ $value }}"
🌐 跨机房部署
同城双活
┌──────────────┐ ┌──────────────┐
│ 机房A │ │ 机房B │
│ │ │ │
│ Master-a ───┼──复制────>│ Slave-a │
│ Master-b ───┼──复制────>│ Slave-b │
│ │ │ │
└──────────────┘ └──────────────┘
↑ ↑ ↑ ↑
│ │ │ │
Producer Consumer Producer Consumer
特点:
- 两个机房都可以读写
- 数据双向同步
- 延迟低(<5ms)
异地多活
┌───────────┐ ┌───────────┐ ┌───────────┐
│ 北京机房 │ │ 上海机房 │ │ 深圳机房 │
│ │ │ │ │ │
│ Master-bj │<-->│ Master-sh │<-->│ Master-sz │
│ │ │ │ │ │
└───────────┘ └───────────┘ └───────────┘
特点:
- 三个机房互为备份
- 任一机房故障,其他机房继续服务
- 延迟较高(10-50ms)
🎯 最佳实践
1️⃣ Broker部署
✅ 推荐配置:
- 最少3个Broker节点
- 每个Broker配1个Slave
- 使用DLedger自动故障转移
- SSD磁盘(比HDD快10倍)
- 万兆网卡
❌ 避免:
- 单Broker(单点故障)
- 同一台物理机部署Master和Slave
- 机械硬盘(性能差)
2️⃣ 网络优化
# Broker配置
# 发送线程池
sendMessageThreadPoolNums=128
# 拉取线程池
pullMessageThreadPoolNums=128
# 网络缓冲区
socketSndbufSize=65535
socketRcvbufSize=65535
3️⃣ JVM调优
# Broker JVM参数
JAVA_OPT="${JAVA_OPT} -server -Xms8g -Xmx8g"
JAVA_OPT="${JAVA_OPT} -XX:+UseG1GC"
JAVA_OPT="${JAVA_OPT} -XX:MaxGCPauseMillis=200"
JAVA_OPT="${JAVA_OPT} -XX:+UnlockExperimentalVMOptions"
JAVA_OPT="${JAVA_OPT} -XX:G1HeapRegionSize=16m"
4️⃣ 容灾演练
定期演练(每季度1次):
1. 模拟Master宕机
- 验证Slave是否能接管
- 验证Producer是否能自动切换
- 验证Consumer是否能继续消费
2. 模拟NameServer宕机
- 验证Client是否能自动切换
3. 模拟网络分区
- 验证脑裂处理
4. 模拟消息积压
- 验证扩容流程
🎓 面试题速答
Q1: 如何设计一个高可用的消息队列?
A: 四个层面!
-
NameServer层:
- 部署3个以上NameServer
- 无状态设计,互不通信
-
Broker层:
- 多Master多Slave架构
- 使用DLedger自动故障转移
- 数据多副本(至少3副本)
-
Client层:
- 配置多个NameServer地址
- 启用重试和故障规避
- 消费者组负载均衡
-
监控告警:
- Prometheus + Grafana监控
- 及时告警,快速响应
Q2: Broker挂了怎么办?
A: 传统模式(异步复制):
- Master挂了 → Slave继续提供服务(只读)
- 需要手动切换
DLedger模式(推荐):
- Master挂了 → 自动选举新Master(基于Raft)
- 无需人工介入,自动故障转移
Q3: 如何保证消息不丢失?
A: 三个层面!
-
Producer端:
- acks=all(等待所有ISR确认)
- 启用重试
- 同步发送
-
Broker端:
- 同步刷盘(或异步刷盘+多副本)
- 同步双写(或异步复制+多副本)
- 至少3副本
-
Consumer端:
- 手动提交offset
- 处理成功才提交
Q4: 如何提高可用性?
A: 目标:从3个9(99.9%)提升到5个9(99.999%)
措施:
- 无单点:所有组件都有备份
- 自动故障转移:DLedger模式
- 快速恢复:监控告警,快速响应
- 定期演练:每季度一次容灾演练
- 灰度发布:新版本先小范围测试
Q5: 跨机房如何部署?
A: 同城双活:
- 两个机房,延迟<5ms
- Master-Slave部署在不同机房
- 双向数据同步
异地多活:
- 三个机房,延迟10-50ms
- 每个机房都是完整集群
- 互为备份
🎬 总结
高可用消息队列核心要点
┌──────────────────────────────────────────────┐
│ 1. 无单点故障 │
│ ├─ NameServer:3个以上,无状态 │
│ ├─ Broker:多Master多Slave │
│ └─ Consumer:消费者组,自动Rebalance │
└──────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────┐
│ 2. 自动故障转移 │
│ ├─ DLedger模式(基于Raft) │
│ ├─ 自动选举新Master │
│ └─ 无需人工介入 │
└──────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────┐
│ 3. 数据冗余 │
│ ├─ 多副本(至少3副本) │
│ ├─ 同步刷盘/异步刷盘 │
│ └─ 同步双写/异步复制 │
└──────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────┐
│ 4. 监控告警 │
│ ├─ Prometheus + Grafana │
│ ├─ 实时监控各项指标 │
│ └─ 及时告警,快速响应 │
└──────────────────────────────────────────────┘
可用性目标:99.99%+ ⭐⭐⭐⭐⭐
🎉 恭喜你!
你已经完全掌握了如何设计高可用的消息队列架构!🎊
核心要点:
- 无单点:所有组件都有备份
- 自动转移:DLedger自动故障转移
- 数据冗余:至少3副本
- 监控告警:实时监控,快速响应
下次面试,这样回答:
"设计高可用消息队列,我会从四个层面考虑:
第一,NameServer层部署3个以上节点,无状态设计,任一节点挂了不影响整体。
第二,Broker层使用多Master多Slave架构,配合DLedger模式实现自动故障转移,基于Raft协议自动选举新Master,无需人工介入。
第三,数据层至少3副本,重要数据使用同步刷盘和同步双写,保证数据不丢失。
第四,监控告警层使用Prometheus + Grafana实时监控,设置合理的告警阈值,快速响应故障。
通过这套架构,可以达到99.99%以上的可用性。"
面试官:👍 "非常全面!你对高可用架构理解很深刻!"
本文完 🎬
作者注:写完这篇,感觉可以去当架构师了!🏗️
如果这篇文章对你有帮助,请给我一个Star⭐!
上一篇: 188-消息队列的顺序性保证方案.md
下一篇: 190-延迟消息的实现原理.md