《关于优秀系统设计我所知道的一切》的总结
今天看到 GitHub 高级工程师写的一篇文章《Everything I know about good system design》,写的很好。
链接地址:
https://www.seangoedecke.com/good-system-design/
总结一下。
真正优秀的系统设计是“不起眼的”(underwhelming)和简单的。它追求的是长期稳定运行,而不是使用各种时髦、复杂的技术。一个看起来令人印象深刻的复杂系统,往往掩盖了糟糕的底层决策。
核心设计哲学
- 好的设计是无趣的:当你感觉某个系统“比预想的简单”或“从来不用操心”时,这恰恰是好设计的标志。
- 从简单开始:一个能正常工作的复杂系统,总是从一个能正常工作的简单系统演化而来的。直接从零开始构建复杂系统是个坏主意。
状态管理 (State)
- 状态是万恶之源:软件设计中最难的部分就是管理状态(State)。无状态(Stateless)的服务更容易维护,因为出问题时可以直接重启恢复。
- 集中管理状态:尽量减少有状态的组件。最佳实践是让一个专门的服务与数据库交互来管理状态,而其他服务通过 API 请求或事件与该服务通信,而不是让多个服务都去读写同一个数据库表。
核心组件与模式
数据库 (Database)
- 数据库访问通常是性能瓶颈。
- Schema 设计要在灵活性和可读性之间找到平衡。
- 为常用查询创建索引 (Index),并将高基数(high-cardinality)的字段放在前面。
- 尽可能让数据库完成工作(如使用
JOIN
),而不是在应用内存中处理数据。 - 将读请求分发到只读副本(read-replicas)以减轻主库压力。
处理慢操作:后台作业 (Background Jobs)
- 面向用户的操作必须快速响应(几百毫秒内)。
- 耗时长的任务(如转换一个大文件)应该放入后台作业异步处理。这通常通过一个队列(Queue)和作业执行器(Job Runner)实现。
缓存 (Caching)
- 缓存是解决昂贵重复计算的经典方案,但它本质上也是一种状态,会引入复杂性(如缓存失效、数据不一致)。
- 高级工程师倾向于尽可能少地使用缓存。在使用缓存前,应首先尝试从根本上优化性能(例如,为慢查询添加索引)。
事件驱动 (Events)
- 事件中心(如 Kafka)用于实现服务解耦。一个服务发布“某事发生”的事件,多个下游服务可以消费该事件并各自处理。
- 适用于发送方不关心消费者行为的场景。不要过度使用,很多时候直接的 API 调用更简单、更易于追踪。
系统运维与容错
关注关键路径 (Hot Paths)
- 设计时应集中精力处理系统中最重要和流量最大的部分。这些路径的设计方案选择更少,且一旦出错影响巨大。
日志与监控 (Logging & Metrics)
- 在异常路径(unhappy path)中积极记录日志,以便于排查问题。
- 除了平均响应时间,还应监控 p95 和 p99 分位值,因为这能反映你最大、最重要客户的真实体验。
容错设计 (Failing Gracefully)
- 重试 (Retries):不能盲目重试,可能会压垮下游服务。写操作需要使用“幂等键 (idempotency key)”来防止重复执行。
- 故障开放/关闭 (Fail Open/Closed):必须明确组件故障时的行为。例如,限流系统应“故障开放”(允许请求通过),而认证系统则必须“故障关闭”(拒绝访问)。
结论
好的系统设计不是关于使用各种“聪明技巧”,而是关于知道如何将那些无聊、经过充分测试的组件用在正确的地方。就像好的管道工一样,如果你在工作中寻求刺激,最后很可能会搞得一团糟。