一、引言
系统设计中最痛苦的不是"怎么做",而是"选哪个方案"。
面对多个看似可行的技术选型,很多架构师靠直觉和经验做决定,结果上线后发现各种问题。今天我要分享 5 个架构师常用的决策框架,让你的技术选型有理有据。
二、框架一:CAP 三角决策法
2.1 CAP 定理精解
CAP 定理:在分布式系统中,三个特性最多只能同时满足两个:
- C (Consistency):一致性。所有节点同时看到相同的数据
- A (Availability):可用性。每个请求都能得到响应,不会超时或失败
- P (Partition Tolerance):分区容错性。网络分区时系统仍能继续运行
⚠️ 关键澄清:很多人误解 CAP 为"三选二",但 P 在分布式系统中是必须的。网络分区是不可避免的事实,你无法选择"不要 P"。真正的选择是:在发生网络分区时,你是选择 C(一致性)还是 A(可用性)。
正常运行时(没有分区),你可以同时拥有 C、A、P。
2.2 CP vs AP 决策矩阵
| 场景 | 选择 | 理由 |
|---|---|---|
| 银行转账 | CP | 数据一致性优先,宁可失败也不能出现资金不一致 |
| 电商库存 | AP | 可用性优先,显示过期库存比无法下单好(下单时再校验实际库存) |
| 社交动态 | AP | 用户体验优先,刷不出最新动态比无法打开好 |
| 订单状态 | CP | 订单状态必须准确,防止重复下单 |
| 缓存系统 | AP | 缓存降级时返回过期数据比报错好(缓存未命中时回源数据库) |
2.3 实战案例:电商库存系统
场景:大促期间,库存服务需要承受高并发
决策过程:
1. 是否允许分区?→ 是(分布式部署,网络分区不可避免)
2. 分区时优先 C 还是 A?
- C:库存不一致 → 用户下单后发现没货 → 体验差
- A:显示过期库存 → 用户下单后校验实际库存 → 可以接受
3. 结论:选择 AP
技术选型:
┌─────────────────────────────────────────────────────┐
│ 服务层:Redis(AP,最终一致性) │
│ ↓ 异步同步 │
│ 存储层:MySQL(强一致性) │
│ ↓ 库存扣减 │
│ 校验层:下单时再次检查实际库存 │
└─────────────────────────────────────────────────────┘
2.4 CAP 决策步骤
步骤 1:确认是否为分布式系统
→ 是 → 继续
→ 否 → CAP 不适用,考虑 ACID
步骤 2:定义业务一致性需求
→ 强一致性?(银行转账、订单状态)
→ 最终一致性?(缓存、消息队列)
步骤 3:分区时的权衡
→ 选择 CP:使用 Zookeeper、Etcd、MySQL
→ 选择 AP:使用 Redis、Cassandra、MongoDB
步骤 4:设计补偿机制
→ CP:设计重试和回滚机制
→ AP:设计最终一致性同步和冲突解决机制
三、框架二:非功能需求优先级矩阵
3.1 四大非功能需求
任何系统设计都需要在四个维度之间做权衡:
| 维度 | 含义 | 典型需求 |
|---|---|---|
| 性能 | 系统响应速度和吞吐量 | P99 < 100ms,QPS > 10000 |
| 可用性 | 系统正常运行的时间比例 | 99.9%,故障恢复 < 5min |
| 一致性 | 数据的正确性和完整性 | 强一致、最终一致 |
| 成本 | 资源投入和运维开销 | 服务器数量、云费用 |
3.2 权衡关系图
性能
╱ ╲
╱ ╲
╱ ╲
一致性───────可用性
╲ ╱
╲ ╱
╲ ╱
成本
权衡关系:
- 性能 ↑ → 成本 ↑(需要更多资源)
- 一致性 ↑ → 可用性 ↓(需要同步等待)
- 可用性 ↑ → 一致性 ↓(需要容忍过期数据)
- 成本 ↓ → 性能 ↓ / 可用性 ↓(资源不足)
⚠️ 注意:这不是"只能选两个"的硬性规则,而是权衡光谱。你可以在四个维度之间找到平衡点,但提高一个维度通常会牺牲另一个。
3.3 优先级矩阵示例
优先级排序:
┌─────────────────────────────────────────────────────┐
│ 系统类型 │ 优先级 1 │ 优先级 2 │ 优先级 3 │
├─────────────────────────────────────────────────────┤
│ 支付系统 │ 一致性 │ 可用性 │ 性能 │
│ 搜索引擎 │ 性能 │ 可用性 │ 一致性 │
│ 内部管理系统 │ 成本 │ 可用性 │ 性能 │
│ 实时监控系统 │ 性能 │ 一致性 │ 可用性 │
└─────────────────────────────────────────────────────┘
3.4 实战案例:内部管理系统
场景:企业内部的员工管理系统
决策过程:
1. 性能:中等即可,并发量低(< 100 QPS)
2. 可用性:较高,工作时间需要可用(99.5%)
3. 一致性:较高,员工数据不能出错
4. 成本:优先级最高,控制在最小投入
技术选型:
┌─────────────────────────────────────────────────────┐
│ 架构:单体应用(Spring Boot) │
│ 数据库:MySQL(单实例,主从备份) │
│ 部署:单台服务器(云服务器最低配置) │
│ 监控:基础日志(无需复杂监控系统) │
└─────────────────────────────────────────────────────┘
权衡说明:
- 牺牲性能:单体架构性能不如微服务,但足够用
- 牺牲高可用:单实例部署,故障时服务中断,但恢复快
- 保证成本:最小资源投入,满足业务需求
四、框架三:"不做清单"法
4.1 为什么"不做清单"很重要
列出"不支持什么"比列出"支持什么"更有价值:
- 明确边界:避免无限扩展需求范围
- 管理预期:让业务方清楚系统能力边界
- 减少返工:避免后续因为遗漏而重做
4.2 "不做清单"示例
系统:电商订单系统
【支持的能力】
├─ 下单、支付、退款
├─ 订单状态查询
├─ 订单导出(CSV)
└─ 订单消息通知
【不支持的能力】
├─ 不支持跨平台订单同步
├─ 不支持订单合并拆分
├─ 不支持批量退款(单次最多 10 笔)
├─ 不支持实时库存扣减(延迟 ≤ 5 秒)
├─ 不支持自定义订单状态
└─ 不支持订单数据修改(只允许新增和查询)
4.3 "不做清单"的类型
| 类型 | 示例 |
|---|---|
| 功能限制 | 单次批量操作上限、文件大小限制 |
| 性能限制 | QPS 上限、响应时间承诺 |
| 数据限制 | 数据保留期限、历史数据查询范围 |
| 业务限制 | 不支持的业务场景、特殊流程 |
| 技术限制 | 不支持的协议、不兼容的版本 |
4.4 实战案例:消息队列系统
系统:内部消息队列服务
【支持的能力】
├─ 消息生产和消费
├─ 消息持久化
├─ 消息重试(最多 3 次)
├─ 死信队列
└─ 消息追踪
【不做清单】
├─ 不支持事务消息
├─ 不支持消息顺序保证
├─ 不支持消息延迟发送(超过 24 小时)
├─ 不支持跨区域消息同步
├─ 不支持消息广播(只支持点对点)
├─ 不支持消息过滤(消费端全量订阅)
└─ 不支持消息优先级
【后续规划】
├─ V2.0:支持事务消息
├─ V2.1:支持消息顺序保证
└─ V2.2:支持跨区域同步
五、框架四:反模式检查表
5.1 五大常见反模式
在评审方案前,用这 5 个反模式快速自检:
反模式 1:分布式单体
特征:拆分成多个服务,但所有服务共享同一个数据库和配置,部署和发布必须同步。
检测信号:
- "我们有 10 个微服务,但每次发布都要一起上线"
- "某个服务出问题,所有服务都挂了"
- "数据库连接池经常被占满"
解决方案:
- 每个服务独立数据库
- 服务间通过 API 或消息队列通信
- 支持独立部署和发布
反模式 2:共享数据库
特征:多个服务直接访问同一个数据库表,绕过服务边界。
检测信号:
- "服务 A 需要查服务 B 的数据,直接连数据库查"
- "数据库表越来越多,没人知道谁在用"
- "改一张表需要协调 5 个团队"
解决方案:
- 每个服务只访问自己的数据库
- 通过 API 获取其他服务的数据
- 使用共享视图或数据同步机制
反模式 3:聊天式微服务
特征:服务间调用链过长,一个请求需要经过 5+ 个服务。
检测信号:
- "用户打开一个页面需要调用 8 个服务"
- "P99 响应时间 500ms,但单个服务都很快"
- "链路追踪图像蜘蛛网"
解决方案:
- 合并细粒度服务
- 使用聚合层(API Gateway)
- 引入本地缓存减少调用
反模式 4:上帝服务
特征:一个服务承担了所有核心业务逻辑,其他服务只是空壳。
检测信号:
- "订单服务包含了支付、库存、物流逻辑"
- "90% 的代码都在一个服务里"
- "每次发布都怕影响核心功能"
解决方案:
- 按业务边界拆分
- 识别核心领域和支撑领域
- 逐步迁移非核心功能
反模式 5:过早优化
特征:在需求不明确的阶段投入大量精力做架构优化。
检测信号:
- "我们需要设计一个支持百万级 QPS 的系统"
- "现在只有 100 个用户,但要做分布式"
- "为了可能的需求预留了很多扩展点"
解决方案:
- YAGNI 原则(You Ain't Gonna Need It)
- 先满足当前需求,再逐步优化
- 使用简单方案,预留扩展接口
5.2 自检清单
评审前快速检查:
✅ 是否存在分布式单体?(所有服务共享数据库?)
✅ 是否存在共享数据库?(服务直接访问其他服务的表?)
✅ 是否存在聊天式微服务?(调用链 > 5 个服务?)
✅ 是否存在上帝服务?(一个服务承担所有核心逻辑?)
✅ 是否存在过早优化?(为不确定的需求做了大量设计?)
如果有任何一项为"是",需要重新审视方案!
六、框架五:N-1 降级法
6.1 什么是 N-1 降级
N-1 降级:设计系统时,明确"最坏情况下可以砍掉哪些功能",确保核心业务在极端条件下仍能运行。
6.2 降级策略层级
降级策略:
┌─────────────────────────────────────────────────────┐
│ 层级 1:核心功能(不可降级) │
│ └─ 下单、支付、库存扣减 │
│ │
│ 层级 2:重要功能(可降级为基础版) │
│ └─ 推荐商品 → 返回默认推荐 │
│ └─ 优惠券计算 → 使用默认值 │
│ │
│ 层级 3:辅助功能(可完全关闭) │
│ └─ 商品详情页缓存 │
│ └─ 用户行为分析 │
│ └─ 非关键日志 │
│ │
│ 层级 4:可选功能(优先关闭) │
│ └─ 营销活动页面 │
│ └─ 非核心数据统计 │
└─────────────────────────────────────────────────────┘
6.3 降级实现方式
| 方式 | 适用场景 | 实现手段 |
|---|---|---|
| 断路器 | 外部服务调用 | Sentinel、Hystrix |
| 特性开关 | 功能级降级 | LaunchDarkly、自研开关 |
| 限流降级 | 高并发场景 | Nginx、Sentinel |
| 缓存降级 | 数据库压力 | Redis 返回过期数据 |
| 队列降级 | 消息堆积 | 丢弃非关键消息 |
6.4 实战案例:电商大促降级方案
场景:双 11 大促,流量预计是平时的 10 倍
降级方案设计:
┌─────────────────────────────────────────────────────┐
│ 正常状态(N):所有功能可用 │
│ │
│ N-1 状态(高负载):关闭非核心功能 │
│ ├─ 关闭营销活动页面 │
│ ├─ 关闭用户行为分析 │
│ ├─ 关闭非核心数据统计 │
│ └─ 推荐商品返回默认列表 │
│ │
│ N-2 状态(极高负载):进一步降级 │
│ ├─ 优惠券计算使用缓存值 │
│ ├─ 商品详情页返回简化版 │
│ ├─ 关闭商品搜索(返回热门商品) │
│ └─ 限流非核心接口 │
│ │
│ N-3 状态(极端负载):核心功能降级 │
│ ├─ 库存查询返回缓存值 │
│ ├─ 关闭退款功能 │
│ └─ 只保留下单和支付 │
└─────────────────────────────────────────────────────┘
触发条件:
┌─────────────────────────────────────────────────────┐
│ 指标 │ N-1 阈值 │ N-2 阈值 │ N-3 阈值 │
├─────────────────────────────────────────────────────┤
│ CPU 使用率 │ 70% │ 85% │ 95% │
│ 内存使用率 │ 75% │ 88% │ 95% │
│ 请求延迟 P99 │ 500ms │ 1000ms │ 2000ms │
│ 数据库连接数 │ 80% │ 90% │ 95% │
└─────────────────────────────────────────────────────┘
6.5 降级设计步骤
步骤 1:识别核心功能
→ 哪些功能是业务必须的?
→ 哪些功能可以暂时关闭?
步骤 2:定义降级层级
→ N-1:最小降级(关闭辅助功能)
→ N-2:中等降级(核心功能降级)
→ N-3:极端降级(只保留最核心)
步骤 3:设置触发条件
→ 基于监控指标(CPU、内存、延迟、错误率)
→ 自动触发或手动触发
步骤 4:设计恢复策略
→ 自动恢复(指标恢复正常后)
→ 手动恢复(需要人工确认)
步骤 5:编写降级文档
→ 每个层级的降级内容
→ 触发条件和恢复策略
→ 操作步骤和责任人
七、框架对比总结
7.1 决策框架对比
| 框架 | 核心维度 | 适用场景 | 输出结果 |
|---|---|---|---|
| CAP | 一致性 vs 可用性 | 分布式系统选型 | CP 或 AP 技术栈 |
| 非功能优先级 | 性能/可用性/一致性/成本 | 系统整体设计 | 优先级排序 |
| 不做清单 | 功能边界 | 需求定义 | 明确的能力边界 |
| 反模式检查 | 架构质量 | 方案评审 | 发现潜在问题 |
| N-1 降级 | 故障应对 | 高可用设计 | 降级策略文档 |
7.2 决策流程
完整决策流程:
┌─────────────────────────────────────────────────────┐
│ 1. 需求分析 │
│ └─ 使用"不做清单"明确边界 │
│ │
│ 2. 架构选型 │
│ └─ 使用 CAP 定理决定一致性模型 │
│ └─ 使用非功能优先级矩阵权衡 │
│ │
│ 3. 方案评审 │
│ └─ 使用反模式检查表自检 │
│ │
│ 4. 高可用设计 │
│ └─ 使用 N-1 降级法设计容错方案 │
│ │
│ 5. 迭代优化 │
│ └─ 根据实际运行数据调整 │
└─────────────────────────────────────────────────────┘
7.3 核心原则
- 没有银弹:每个框架都有适用场景,没有万能方案
- 数据驱动:基于实际指标做决策,不是拍脑袋
- 文档化:把决策过程和理由写下来,便于后续追溯
- 灵活性:方案要能适应变化,不要过度设计
八、总结
五个框架的核心价值
| 框架 | 解决什么问题 |
|---|---|
| CAP | 分布式系统中一致性和可用性的权衡 |
| 非功能优先级 | 多个非功能需求之间的取舍 |
| 不做清单 | 明确系统能力边界,避免需求膨胀 |
| 反模式检查 | 在方案评审阶段发现潜在问题 |
| N-1 降级 | 确保系统在极端条件下仍能运行 |
架构师的核心能力
架构师不是技术专家,而是决策专家。好的决策框架能帮助你:
- 系统化思考:避免遗漏关键因素
- 有理有据:决策过程可追溯、可验证
- 减少风险:提前发现问题,避免事后补救
💡 互动话题:你在系统设计中常用哪些决策方法?有没有遇到过因为决策失误导致的问题?欢迎在评论区分享你的经验!
