Files
cs-note/hhs/MQ/12-架构与实战/44-MQ-高可用架构.md
T

161 lines
5.8 KiB
Markdown
Raw Normal View History

2026-05-24 20:51:06 +08:00
---
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-设计与实现]]