架构选型、容量估算与 ADR:把选择写成可验证的判断
“以后可能有一亿用户”不足以证明今天要上微服务。架构选择应从当前需求、可预期变化和可承担成本出发,并说明什么时候重新考虑。
1. 先列质量要求
功能需求描述能做什么,质量要求描述在什么条件下做得怎样。例如:订单提交 p95 延迟目标、峰值流量、数据保留时间、允许的恢复窗口、团队值班能力。数字先作为明确假设,再通过测量修正,别把估算写成硬件保证。
优先排序也很重要。预算有限、团队三个人的内部系统,发布简单和排错容易可能比跨语言服务自治更重要。相反,成熟平台中多个独立团队确有不同发布周期,服务拆分可能有现实收益。
2. 估算只为找数量级
假设每天 100 万个请求,平均每秒约为 1000000 / 86400 ≈ 11.6,但平均值掩盖了突发。若计划用 20 倍平均值作为演练峰值,这是人为假设,不是流量自然规律。
在稳定系统和适当统计边界下,Little 定律可写作平均在途数约等于到达率乘平均停留时间。若平均 200 次/秒、平均停留 0.2 秒,平均在途约 40。它不直接给出线程池大小,也不用 p99 代替平均值来宣称严格成立。
requests_per_day = 1_000_000
average_rps = requests_per_day / 86_400
assumed_peak_rps = average_rps * 20
average_in_flight = 200 * 0.2
print(round(average_rps, 1), round(assumed_peak_rps, 1), average_in_flight)
存储估算同样加上索引、副本、日志、保留期与增长余量。压测要说明数据规模、冷热缓存、请求分布和负载发生器是否饱和,不能只给“每秒十万次”的孤立数字。
3. 一个起步选型表
| 已确认需求 | 倾向方案 | 暂时不引入什么 |
|---|---|---|
| 小团队,业务边界仍在变化 | 模块化单体 + 关系数据库 | 未有收益证据的微服务拆分 |
| 需要异步导出、通知 | 单体 + 队列与 Worker | 所有请求都改成异步消息 |
| 读写形态差异显著 | 专用查询模型,必要时 CQRS | 默认 Event Sourcing |
| 独立团队和发布边界成熟 | 有明确数据所有权的服务 | 共享内部表的伪自治 |
| 请求波动大且任务适配平台 | 评估 Serverless | 无视连接与执行时限 |
这张表是本教程的起步建议,不是采购结论。实际选择要通过业务约束、负载与运维能力验证。
4. 用 ADR 留下决策依据
Architecture Decision Record 记录一次重要决策的背景、选项、选择和后果。不必写成几十页汇报,关键是未来的人能理解当时为什么这样做,以及什么条件变化后应该重审。
决策:初版采用模块化单体,通知使用独立 Worker。
背景:三人维护;订单与库存需要本地事务;通知允许延后。
选项:纯同步单体 / 单体加 Worker / 全量微服务。
选择理由:保留事务简单性,同时隔离通知延迟。
代价:核心应用仍统一发布;需要维护 Outbox 与消费监控。
重审条件:模块间发布阻塞持续发生,且团队已具备独立值班能力。
验证:并发下单、通知停机恢复、备份恢复与发布回归。
5. 完成一份真正的设计
交付不只是框图。补上接口契约、数据表、状态机、权限、失败恢复、指标、部署与回滚。再找一个同学只读你的文档,能否复现“重复下单”“数据库超时”“消息重复”三种情况?如果读者只能运行成功路径,设计还没有讲完。