分布式配置管理系统
第一题
设计一个分布式配置管理系统(类似 AWS Parameter Store 或 Azure App Configuration)。
要求:
- 支持百万级服务实例实时拉取配置
- 配置更新后,所有实例需要在 30 秒内感知到变更
- 系统需要高可用,不能成为单点故障
请描述你的整体架构设计。
回答你的问题:
- 实例数量:峰值 100 万实例同时在线,正常情况约 60-70 万。
- 拉取频率:服务实例启动时拉取一次全量配置,运行期间不主动轮询——配置变更由系统推送通知,实例收到通知后再拉取。配置变更频率不高,平均每天几十次。
附加信息:
- 配置项数量级:每个服务约 10-100 个 key,单个 value 不超过 64KB
- 读多写极少,写操作来自运维人员或 CI/CD 系统
- 需要支持配置版本回滚
- 不同环境(prod/staging/dev)的配置需要隔离
回答:
- 大约 5000 种服务,每种服务平均 200 个实例
- 每种服务有自己独立的配置集,不同实例共享同一份配置(即配置是 per-service 的,不是 per-instance 的)
- 每个服务约 50 个 key,每个 value 平均 1KB
你现在有足够的数字了:
| 维度 | 数值 |
|---|---|
| 服务种类 | 5,000 |
| 实例总数 | 1,000,000 |
| 每服务 key 数 | 50 |
| 每 value 大小 | 平均 1KB |
| 配置版本保留 | 最近 100 个版本 |
可以开始算存储,然后继续设计了。
分布式配置管理系统 — 完整设计
第一步:澄清需求(必做)
拿到题目先问清楚:服务种类多少、实例数峰值、配置变更频率、是否需要版本回滚、多环境隔离需求。这些数字决定后续所有容量规划。
第二步:容量规划
存储计算(per 环境):
Redis(当前版本热数据): 5000 服务 × 100 keys × 64KB(max) = 32GB ← 单台 Redis 可以放下 MySQL(历史版本冷数据): 5000 × 100 keys × 100 版本 × 1KB(avg) = 50GB ← 实际远小于极限值 极限值:5000 × 100 × 100 × 64KB = 3.2TB
读 QPS:
Push 通知机制下,实例不轮询。只有配置变更时才有读请求。每天 100 次变更,每次影响 200 个实例,QPS 极低,Redis 轻松应对。
Long Poll 连接数:
每台 Config API Server 维持约 5 万连接,20 台覆盖 100 万实例。
第三步:整体架构
┌─────────────────────────────────────────────┐
│ 运维 / CI/CD │
└──────────────────┬──────────────────────────┘
↓ 写配置
┌──────────────────────────────────────────────┐
│ Write Service │
│ 1. 写 MySQL(Source of Truth) │
│ 2. 更新 Redis(热缓存) │
│ 3. Publish → Redis Pub/Sub │
└──────┬───────────────────────────────────────┘
│ Pub/Sub 通知
┌──────▼───────────────────────────────────────┐
│ Config API Server × 20台(无状态) │
│ - 每台维持 5 万个 Long Poll 连接 │
│ - 收到 Pub/Sub → 响应对应服务的等待连接 │
└──────┬───────────────────────────────────────┘
│ 实例拉取配置
┌──────▼───────────────────────────────────────┐
│ Redis Cluster │
│ (当前版本热缓存,32GB) │
└──────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ MySQL(历史版本 + Source of Truth) │
│ 主从复制,高可用 │
└─────────────────────────────────────────────┘
第四步:变更通知链路
从”运维改了一条配置”到”100 万实例感知”,完整流程:
1. 运维写入新配置
↓
2. Write Service 写 MySQL ✅ 确认落盘
↓
3. Write Service 更新 Redis(带 Retry,失败重试 3 次)
↓
4. Publish 到 Redis Pub/Sub:
"service-X 配置已更新,version=42"
↓
5. 20 台 Config API Server 同时收到 Pub/Sub 消息
↓
6. 每台找到本机挂着的 service-X Long Poll 连接
立即响应:「有新版本,来拉吧」
↓
7. service-X 的 200 个实例收到响应
请求 Config API Server 拉取 v42 配置
↓
8. Config API Server 从 Redis 读取返回给实例
全程 < 1 秒,远低于 30 秒要求
第五步:实例断线重连
实例不需要离线消息队列。配置是状态,不是事件流。
重连时只需带上当前版本号:
GET /config/service-X?current_version=41 API Server 判断: 最新是 v42 → 立即返回新配置 最新是 v41 → 挂住,进入 Long Poll 等待
这样断线 1 秒还是 1 小时,重连逻辑完全相同。
第六步:一致性保障(Reconciliation Loop)
两层防御,处理写入路径上的失败:
第一层:Retry(处理网络抖动) MySQL 写成功 → Redis 更新失败 → 指数退避重试 3 次 第二层:Reconciliation Loop(兜底) 后台任务每 30 秒扫一次 比对 MySQL 版本号 vs Redis 版本号 发现不一致 → 同步 Redis → 触发 Pub/Sub
第七步:高可用
| 组件 | 方案 |
|---|---|
| Config API Server | 无状态,20 台,任意挂几台不影响 |
| Redis | 主从 + Sentinel,或 Redis Cluster |
| MySQL | 主从复制,从库只读,主库故障自动切换 |
| Write Service | 多实例部署,无状态 |
关键设计原则总结
- MySQL 是 Source of Truth,Redis 是缓存,写入顺序永远 MySQL 先
- Long Poll 解决实时推送,避免无效轮询
- 版本号 解决断线重连,不需要离线消息队列
- Reconciliation Loop 保证最终一致性
- Config API Server 作为连接层,实例不直连 Redis
