Files
cs-note/hhs/MQ/12-架构与实战/44-MQ-高可用架构.md
T
2026-05-24 20:51:06 +08:00

161 lines
5.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags:
- MQ
create time: 2026-05-24 19:52
---
# MQ 高可用架构
## 概述
消息队列作为分布式系统的核心基础设施,其高可用性直接影响整个业务链路的稳定性。本文从可用性指标、Broker 集群模式、无单点设计、跨机房容灾、灰度发布等维度,系统梳理 MQ 高可用架构的设计要点。
## 正文
### 高可用指标
衡量高可用有两个核心维度:**可用性指标**和**恢复指标**。
可用性用"几个 9"来衡量:
| 级别 | 可用性 | 年停机时间 | 典型场景 |
|------|--------|-----------|---------|
| 3 个 9 | 99.9% | 8.76 小时 | 内部工具 |
| 4 个 9 | 99.99% | 52.6 分钟 | 核心业务 |
| 5 个 9 | 99.999% | 5.26 分钟 | 金融交易 |
恢复指标有两个:
- **RTO(Recovery Time Objective)**:从故障到恢复的时间目标,比如 RTO = 5 分钟意味着故障后 5 分钟内必须恢复。
- **RPO(Recovery Point Objective)**:可容忍的最大数据丢失量。RPO = 0 表示零数据丢失,这通常需要同步复制。
> [!question]
> 你的业务能接受多长的 MQ 不可用时间?如果 RPO = 0,同步复制带来的性能下降你能接受吗?
### Broker 集群模式
MQ 的高可用核心在于 Broker 层的集群化。不同产品有不同的实现路径,但本质都是**数据冗余 + 故障切换**。
**主从复制(Leader-Follower)**
最经典的模式。Leader 处理读写请求,Follower 同步数据作为备份。
- **同步复制**:Producer 发送消息后,Leader 等待 Follower 确认才返回 ACK。数据不丢,但延迟增加。
- **异步复制**:Leader 写入本地即返回 ACK,Follower 异步拉取。性能好,但主宕机可能丢数据。
**多副本 ISR(In-Sync Replicas)**
Kafka 的核心机制。ISR 是一组与 Leader 保持同步的副本集合。如果某个 Follower 落后太多,会被踢出 ISR。只有 ISR 中的副本才有资格被选为新 Leader。
```go
// ISR 维护的核心逻辑(简化示意)
type ISRManager struct {
leader int32
isr map[int32]bool
lagThresh int64 // 允许的最大滞后字节数
}
// Follower 拉取数据后更新 LEO,Leader 检查是否还在 ISR 中
func (m *ISRManager) UpdateISR(replicaID int32, logEndOffset int64, leaderLEO int64) {
if leaderLEO-logEndOffset > m.lagThresh {
delete(m.isr, replicaID) // 落后太多,踢出 ISR
}
}
```
**Raft 共识**
Raft 是强一致性协议,天然适合做 MQ 的副本同步。Kafka 从 3.3 开始用 KRaft(基于 Raft 的 Controller)替代 ZooKeeper;RocketMQ 5.0 引入 DLedger(也是 Raft)实现多副本。
```go
// Raft 核心:多数派确认后才提交
type RaftLog struct {
term uint64
index uint64
data []byte
}
// 只有超过半数节点写入成功,日志才算 committed
func (r *RaftNode) AppendEntries(entries []RaftLog) bool {
ackCount := 1 // Leader 自己
for _, peer := range r.peers {
if r.sendAppendRPC(peer, entries) {
ackCount++
}
}
return ackCount > len(r.peers)/2
}
```
### 无单点设计
一个真正的高可用集群不允许任何单点故障。
| 产品 | 传统单点 | 无单点方案 |
|------|---------|-----------|
| RocketMQ | NameServer | NameServer 本身就是无状态集群,任意节点宕机不影响服务 |
| Kafka | ZooKeeper | KRaft 模式下 Controller 使用 Raft 选出 Leader,不再依赖 ZK |
| RabbitMQ | 队列所在节点 | Quorum Queue 基于 Raft 多副本,队列数据分布在多个节点 |
> [!question]
> NameServer 是无状态的,那它和 ZooKeeper 的设计思路有什么根本区别?
### 跨机房容灾
单机房再怎么高可用,也挡不住机房级故障(断电、光纤被挖断、自然灾害)。跨机房容灾有几种常见模式:
- **同城双活**:两个机房距离 50km 以内,网络延迟 < 2ms。MQ 集群可以同步复制,RPO = 0。
- **异地多活**:多地部署独立集群,各自处理就近流量,异步复制数据。RPO > 0,但抗灾能力最强。
- **灾备切换**:主机房写入,备机房冷备或温备。故障时手动或自动切换,RTO 较长。
### 灰度发布
MQ 作为基础设施,升级必须谨慎。一次糟糕的滚动升级可能让整个集群瘫痪。
- **滚动升级**:逐台替换 Broker,每次只升级一台,确认无异常后再继续。Kafka 和 RocketMQ 官方文档都推荐这种方式。
- **蓝绿部署**:部署一套全新版本的集群,流量验证通过后一次性切换。
- **金丝雀发布**:先将少量流量导入新版本集群,观察指标稳定后再全量切换。
```mermaid
graph TB
subgraph "机房A - 主集群"
B1["Broker1 Leader"]
B2["Broker2 Follower"]
B3["Broker3 Follower"]
end
subgraph "机房B - 备集群"
B4["Broker4 Leader"]
B5["Broker5 Follower"]
B6["Broker6 Follower"]
end
subgraph "机房C - 观察节点"
B7["Broker7 Observer"]
end
P["Producer Group"]
C["Consumer Group"]
P --> B1
P --> B4
B1 -->|"同步复制"| B2
B1 -->|"同步复制"| B3
B4 -->|"异步复制"| B5
B4 -->|"异步复制"| B6
B1 -->|"跨机房复制"| B4
B4 -->|"观察同步"| B7
B1 --> C
B4 --> C
```
### 一致性与性能的权衡
高可用不是免费的。更多的副本、更强的一致性意味着更高的延迟和更低的吞吐。生产实践中需要根据业务特点选择合适的方案:
- 订单、支付类:同步复制,宁可慢也不能丢。
- 日志、埋点类:异步复制,允许少量丢失,追求高吞吐。
- 通知、推送类:异步复制 + 重试,最终一致即可。
## 关联笔记
- [[45-MQ-跨集群复制与容灾]]
- [[46-MQ-分布式事务实践]]
- [[49-MQ-客户端连接管理]]
- [[50-MQ-设计与实现]]