在技术圈里,我们经常看到各种”炫技”式的系统设计建议。有些是LinkedIn优化的”你肯定没听过队列”式的帖子,有些是Twitter优化的”如果你在数据库里存布尔值就是糟糕工程师”的聪明技巧。即使是好的系统设计建议,有时也可能并不实用。
作为一个在2026年仍在一线写代码的工程师,我想分享一些关于优秀系统设计的思考。这些不是什么高深的理论,而是经过实践检验的朴实智慧。
什么是优秀的系统设计?
如果软件设计是如何组装代码行,那么系统设计就是如何组装服务。软件设计的基本元素是变量、函数、类等;系统设计的基本元素是应用服务器、数据库、缓存、队列、事件总线、代理等。
优秀的系统设计看起来往往很平淡无奇。在实践中,它表现为长时间内没有出现问题。当你有”嗯,这比我预期的要简单”或”我从来不需要考虑系统的这部分,它运行得很好”这样的想法时,你就知道遇到了好的设计。
矛盾的是,好的设计是自我隐藏的:坏的设计往往比好的设计看起来更令人印象深刻。我总是对看起来很炫的系统保持怀疑。如果一个系统有分布式共识机制、多种不同形式的事件驱动通信、CQRS和其他巧妙的技巧,我会怀疑是否有一些根本性的错误决策需要被补偿(或者系统就是过度设计了)。
状态与无状态:核心挑战
软件设计的难点在于状态。如果你要存储任何信息任何时间,你就需要在如何保存、存储和提供这些信息方面做出很多棘手的决定。如果你不存储信息,你的应用就是”无状态的”。
你应该尽量减少系统中有状态组件的数量。原因很简单:有状态的组件可能进入错误状态。无状态的服务可以安全地永远运行,只要你做一些基本合理的事情。而有状态的服务无法像这样自动修复。
这在实践中意味着:
- 有一个服务了解状态(即与数据库交互)
- 其他服务做无状态的事情
- 避免让五个不同的服务都写入同一个表
- 让其中四个服务向第一个服务发送API请求或发出事件
数据库:状态管理的核心
由于管理状态是系统设计最重要的部分,最重要的组件通常是状态存在的地方:数据库。
模式和索引
如果你需要在数据库中存储某些东西,首先要做的是定义一个具有所需模式的表。模式设计应该是灵活的,因为一旦你有了成千上万或数百万条记录,更改模式可能会非常痛苦。
但是,如果你让它过于灵活(例如,将所有内容都放在”value” JSON列中),你会给应用程序代码增加大量复杂性。在这里划线是一个判断调用,但总的来说,我的目标是让我的表是人类可读的。
如果你期望你的表会超过几行,你应该在上面放索引。尝试让你的索引匹配你发送的最常见查询。索引的工作原理类似于嵌套字典,所以确保首先放置最高基数的字段。
瓶颈处理
访问数据库通常是高流量应用程序的瓶颈。复杂的应用程序需要进行大量的数据库调用——每个请求数百次,通常是顺序的。
查询数据库时,让数据库来做工作。 让数据库做工作几乎总是比你自己做更有效率。例如,如果你需要来自多个表的数据,JOIN它们而不是进行单独的查询并在内存中拼接它们。
尽可能多地将读查询发送到数据库副本。典型的数据库设置将有一个写节点和一堆读副本。你越能避免从写节点读取,越好——那个写节点已经忙于处理所有写入了。
慢操作与快操作的分离
服务必须快速完成某些事情。如果用户正在与某些东西交互(比如API或网页),他们应该在几百毫秒内看到响应。但服务也必须做其他需要时间的事情。
一般模式是分离出为用户做有用事情所需的最少工作量,并在后台完成其余工作。
什么是后台作业?每个技术公司都会有某种运行后台作业的系统。将有两个主要组件:队列集合(例如在Redis中)和作业运行器服务,它将从队列中获取项目并执行它们。
后台作业应该是慢操作的首选,因为它们通常是如此成熟的路径。
缓存:谨慎使用的利器
有时操作很慢是因为它需要执行一个昂贵(即慢)的任务,而这个任务在用户之间是相同的。经典的解决方案是缓存:只每五分钟查找一次价格,并在此期间存储值。
典型的模式是初级工程师学习缓存后想要缓存一切,而高级工程师想要尽可能少地缓存。为什么?这归结到我关于状态危险性的第一点。缓存是状态的来源。它可能获得奇怪的数据,或与实际真相不同步,或通过提供陈旧数据导致神秘错误等等。
你永远不应该在没有首先认真努力加速它的情况下缓存某些东西。例如,缓存一个没有被数据库索引覆盖的昂贵SQL查询是愚蠢的。你应该只是添加数据库索引!
事件系统:适度使用
除了某种缓存基础设施和后台作业系统外,技术公司通常还会有一个事件中心。最常见的实现是Kafka。事件中心只是一个队列,但不是在队列上放置”用这些参数运行这个作业”,而是放置”这件事发生了”。
你不应该过度使用事件。很多时候,让一个服务向另一个服务发出API请求更好:所有日志都在同一个地方,更容易推理,你可以立即看到其他服务的响应。
事件适用于发送事件的代码不一定关心消费者对事件做什么的情况,或者当事件是高容量且不特别时间敏感的情况。
推送与拉取
当你需要数据从一个地方流向很多其他地方时,有两个选项。最简单的是拉取。这就是大多数网站的工作方式。问题是用户可能会做很多拉取相同数据的操作。
替代方案是推送。不是允许用户请求数据,而是允许他们注册为客户端,然后当数据更改时,服务器将数据推送到每个客户端。这就是GMail的工作方式。
如果我们谈论的是后台服务而不是有网络浏览器的用户,很容易看出为什么推送可能是个好主意。即使在一个非常大的系统中,你可能只有大约一百个需要相同数据的服务。
热路径:关注关键部分
当你设计系统时,用户可以与之交互或数据可以流经它的方式有很多不同。技巧是主要关注”热路径”:系统中最关键重要的部分,以及将处理最多数据的部分。
热路径很重要,因为它们比其他设计领域有更少的可能解决方案。有一千种方法可以构建计费设置页面,它们都主要有效。但可能只有少数几种方法可以合理地消费用户操作的数据流。
日志和指标:可观察性的基础
你如何知道是否有问题?我从最偏执的同事那里学到的一件事是在不愉快的路径中积极记录日志。如果你正在编写一个检查一堆条件以查看面向用户的端点是否应该响应422的函数,你应该记录被命中的条件。
你还应该对系统的操作部分有基本的可观察性。这意味着主机或容器上的CPU/内存、队列大小、每个请求或每个作业的平均时间等等。对于面向用户的指标,如每个请求的时间,你还需要观察p95和p99(即你最慢的请求有多慢)。
优雅失败:系统韧性
你需要仔细考虑系统严重失败时会发生什么。
重试不是万能药。你需要确保你不会通过盲目重试失败的请求给其他服务增加额外负载。如果可以的话,将高容量API调用放在”断路器”内:如果你连续得到太多5xx响应,停止发送请求一段时间让服务恢复。
决定系统的一部分失败时会发生什么也很重要。例如,假设你有一些速率限制代码,检查Redis桶以查看用户是否在当前窗口中发出了太多请求。当该Redis桶不可用时会发生什么?你有两个选项:失败开放并让请求通过,或失败关闭并用429阻止请求。
你应该失败开放还是关闭取决于具体功能。在我看来,速率限制系统几乎应该总是失败开放。然而,认证应该(显然)总是失败关闭。
2026年的思考
在2026年,随着AI和云原生技术的进一步发展,这些基本原则变得更加重要。我们看到:
AI驱动的复杂性:虽然AI可以帮助我们构建更智能的系统,但基础的设计原则仍然适用。状态管理、缓存策略、优雅失败——这些都不会因为AI而改变。
云原生的成熟:容器化、微服务、服务网格已经成为标准,但这并不意味着我们应该过度设计。简单、可靠的系统仍然胜过复杂、”聪明”的系统。
可观察性的重要性:在分布式系统中,日志、指标和追踪比以往任何时候都更重要。但记住,工具只是手段,理解系统行为才是目的。
结语
优秀的系统设计不是关于巧妙的技巧,而是关于知道如何在正确的地方使用无聊、经过充分测试的组件。我不是水管工,但我想象好的管道工程是相似的:如果你做的事情太令人兴奋,你可能会搞得一团糟。
特别是在大型科技公司,这些组件已经现成存在(即你的公司已经有某种事件总线、缓存服务等),好的系统设计看起来就像什么都没有。你很少需要做那种可以在会议上谈论的系统设计。它们确实存在!但我在十年中只见过一两次。我每天都看到无聊的系统设计。
在这个AI和复杂技术栈盛行的时代,记住这个简单的真理:最好的系统设计往往是最无聊的设计。
这篇文章基于Sean Goedecke的系统设计经验,结合2026年的技术环境思考。在快速变化的技术世界中,基础原则的价值愈发凸显。