Youda's blog

努力工作 认真生活......

0%

Everything I know about good system design

原文地址:Everything I know about good system design
基于DeepSeek总结修改

这篇文章的精髓在于它颠覆了很多人对“好系统”的认知:真正优秀的系统设计往往看起来平淡无奇,甚至让人觉得“比我想的要简单”;而那些看起来很厉害的复杂系统,可能反而掩盖了根本性的设计问题。所有能正常运行的复杂系统,无一不是从能正常运行的简单系统演化而来,从零开始直接构建复杂系统绝对是糟糕的想法。

下面这个表格概括了优秀系统设计与不良系统设计的关键特征对比,能帮助你更直观地理解肖恩·戈德克的核心观点。

特征维度 优秀的系统设计 不良的系统设计
核心表现 长期稳定,很少出错 频繁出现问题,维护成本高
复杂性处理 简单直观,从有效的简单系统演化而来 过度复杂,可能从零开始就设计成复杂系统,或滥用“聪明技巧”
状态管理 严格控制状态,尽可能无状态,状态集中化 状态分散,多个服务直接操作数据库,难以维护
数据库设计 表结构清晰易懂,索引策略合理 结构复杂(如过度使用JSON或键值表),或缺乏必要索引
缓存使用 谨慎使用缓存,优先优化底层(如数据库索引) 过度依赖缓存,试图用缓存掩盖底层性能问题
架构选择 实用主义,简单API调用往往优于事件驱动 炫技倾向,盲目采用事件驱动、CQRS等复杂模式
给人的感觉 “平淡无奇”,甚至让人觉得“就这么简单?” “令人印象深刻”,看起来“很厉害”

🧩 系统设计的本质
肖恩开篇明义:“程序设计是组装代码,系统设计是组装服务。” 这意味着系统设计的基本单元不再是变量、函数和类,而是应用服务器、数据库、缓存、队列、事件总线、代理等基础设施组件

💡 核心原则与实战策略

  1. 🤖 状态管理的艺术:肖恩认为状态是系统设计中最困难的部分。他的核心建议是:

    • 无状态优先:尽量采用无状态组件(如一个纯粹的PDF转HTML服务)。
    • 状态最小化:极力减少系统中有状态组件的数量。
    • 状态集中化让一个服务“拥有”并管理某种状态,其他服务通过API或事件与之交互,而不是多个服务直接读写同一个数据库表。因为有状态服务可能陷入异常状态(如错误数据导致崩溃、存储空间耗尽),往往需要手动干预修复,而无状态服务则可以通过简单重启自动恢复。
  2. 💾 数据库设计哲学:数据库是存储状态的核心。

    • 表结构清晰:目标是打开表结构就能大致理解存储的内容和原因。应避免过度“灵活”的复杂结构(如滥用JSON列或键值表),这会把复杂性转移给应用代码并引发性能问题。
    • 索引策略:为最常用的查询模式创建复合索引,并将高基数字段放在前面。避免盲目添加所有可能字段的索引,因为每个索引都会增加写入开销。
    • 读写分离:采用一个写入节点(主库)和多个只读副本(从库) 是常见且有效的策略。尽量将查询导向只读副本,以减轻主库压力。对于“写入后立即读取”的场景,若无法容忍复制延迟,可考虑将更新后的数据暂存于内存,而非立即重新读取数据库。
  3. ⚡ 性能优化策略

    • 让数据库做繁重的工作:在绝大多数情况下,使用JOIN等数据库操作比在应用内存中手动拼接数据更高效。尤其要警惕ORM在循环中意外触发N+1查询问题。
    • 缓存是双刃剑:一个有趣的观点是,初级工程师总想缓存一切,而高级工程师则希望尽量少用缓存。因为缓存本身就是一种状态源,会带来数据一致性、过期和异常等问题。优先考虑优化底层(如数据库索引),而非首选缓存。对于大规模或昂贵操作,可考虑使用计划任务将结果(如报告)持久化到S3等对象存储中,而非依赖Redis/Memcached。
  4. ⏳ 快慢操作分离与后台作业:对于耗时操作(如处理大文件),通用解决方案是拆解出核心部分快速响应,其余部分放入后台作业队列异步处理。一个后台作业系统通常包含:

    • 队列服务:如Redis(紧急任务)或数据库(非紧急任务)。
    • 作业运行器:从队列中获取并执行任务。
    • 对于需要长期调度(如一个月后执行)的任务,肖恩建议使用数据库表而非Redis队列来管理,以保证持久化和便于查询。
  5. 🚀 事件驱动架构的陷阱:虽然事件驱动架构(常用Kafka)适用于“发送方不关心消费方如何处理”或事件量很大的场景,但肖恩警告不要过度使用。他指出,很多时候,更简单的做法是让一个服务直接调用另一个服务的API,这样更直接,也更易于调试和追踪。

🛠️ 实战经验与建议

  • 热路径优先:识别系统中最关键、处理数据最多的部分(热路径),并给予其最高关注,因为这里的选择更少,出错影响也更严重。
  • 日志和监控积极记录异常路径上的详细信息。监控关键指标(如CPU/内存、队列大小、请求时间),并重点关注P95和P99分位数,而非平均值
  • 容错和降级:采用断路器模式防止级联失败。设计幂等性操作(如使用UUID)以避免重复处理。为功能设计优雅降级策略,决定失败时是开放还是关闭。

💎 总结与启示

肖恩·戈德克的这些观点,对于当前热衷追求“微服务”、“云原生”、“事件驱动”等技术社区来说,是一剂宝贵的清醒剂。他提醒我们回归系统设计的本质:用最简单、最经考验的方式解决实际的问题

“好的系统设计不是关于聪明的技巧,而是知道如何在正确的地方使用无聊的、经过测试的组件。如果你在做一些太令人兴奋的事情,你可能会弄得一团糟。”