首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >GitHub工程师:我所知道的优秀系统设计

GitHub工程师:我所知道的优秀系统设计

原创
作者头像
闫同学
发布2025-08-26 20:36:08
发布2025-08-26 20:36:08
1550
举报

我常看到许多糟糕的系统设计建议。一种经典例子是针对行业新人写的“你可能从没听说过消息队列的帖子”,还有像“如果你在数据库里存布尔值就是糟糕工程师”的那种推特式“聪明技巧”。即便是好的系统设计建议,有时也可能是误导性的。我很喜欢《Designing Data-Intensive Applications》这本书,但我认为它对大多数工程师将遇到的系统设计问题来说并不是特别有用。

什么是系统设计?在我看来,如果“软件设计”是关于如何组织代码行,那么“系统设计”就是关于如何组织服务。软件设计的原始元素是变量、函数、类等;而系统设计的原始元素是应用服务器、数据库、缓存、队列、事件总线、代理等等。

这是我试图总结出的,我所知道的关于优秀系统设计的方方面面。很多具体的判断都取决于经验,我在这篇文章里无法完全传达这些经验,但我尝试把能写下来的内容都抒写出来。

识别优秀设计

优秀系统设计是什么样子的?我以前写过,它看起来不起眼。实际上,它的表现方式就是长时间没出问题。如果你对系统的某部分有类似“诶,这比我预期的简单多了”、“我从不需要操心这一块,它就工作得挺好”的感觉,那么你很可能正处于优秀设计的实物中。矛盾的是,优秀设计是自谦的:而糟糕设计往往更“有看头”。我总会对那些“看起来很厉害”的系统抱持怀疑——如果一个系统看起来很复杂,我就会想,是不是它本来就不好,而用复杂性来掩饰这个事实?或者干脆就是过度设计。

状态和值无状态

软件设计最难处理的问题就是“状态”。如果系统要存储某种信息并保留一段时间,你必须做很多关于如何保存、存储和提供这些数据的抉择。如果你不存储信息,你的应用就是“无状态”的。举个简单的例子:GitHub 内部有个 API,用来把 PDF 转成 HTML,那就是个真正的无状态服务。任何写入数据库的行为都是有状态的。

系统设计中应尽量减少有状态组件。从某种意义而言,你应该尽量减少系统里的所有组件,但有状态组件尤其应慎重。原因是,有状态组件容易进入坏的状态。我们的无状态 PDF 转 HTML 服务,只要运行在可重启的容器中,就能持续稳定地工作。但一个有状态的服务就不能自动恢复:数据库里如果写入了格式导致崩溃的条目,就得手动修复;数据库空间满了,就得去剪数据或者扩容。

这意味着实践中应该让一个服务独自负责“状态”,例如,写入数据库应该集中在一个服务里,其它服务通过 API 或事件与它通信,而不是五个服务都去写同一个表。如果可能,也可以让一个服务集中处理读取操作,虽然我对此稍微没有那么绝对。但在一些场景下,直接读数据库比调用内部会慢得 HTTP API 更高效——前提是这种读取不会带来繁重的状态管理成本。

数据库设计

由于管理状态是系统设计中最重要的部分,所以数据库往往是关键组件。以下是一些建议:

模式与索引:设计表结构时应考虑灵活性,因为一旦有成千上万甚至更多记录后,再改结构就会非常麻烦。但如果结构太“灵活”(比如把所有内容都存为 JSON 或者使用键值表保存各种随意数据),就会把复杂性压到应用层,也可能带来性能问题。我一般倾向于让表结构可读:你应该能通过查看 schema,大致理解应用在存什么、为什么存。

预计表中可能存储大量数据时,务必添加索引。应针对最常见查询来建索引(例如 email 和 type),并把高基数字段放到索引字段前面。切记不要对所有字段都建索引,因为每个索引都会增加写入开销。

瓶颈问题:高流量应用中,数据库往往是主要瓶颈。即使计算性能不高(例如 Rails run 在 Unicorn),数据库复杂调用仍是性能拖累因素。

  • 优先让数据库处理逻辑,例如使用 JOIN 而不是多个查询后在内存中拼接。
  • 谨防 ORM 在循环中发起查询,可能从一次查询变成 N 次。
  • 在某些情况下,拆查询可能比尝试优化单一复杂查询更实用。
  • 把尽可能多的读取请求发送给副本节点,减少主写节点压力。对于写后立即读取,若不能容忍副本延迟,可以在内存中填充更新值。
  • 注意写入或事务带来的查询峰值,这是常见性能“雪崩”原因。设计批量导入服务时应考虑限流

慢任务与快任务分离

服务应将用户感知的操作做到快速响应,同时把耗时操作转移到后台。比如 PDF 转 HTML,可先渲染首页并立即返回,再将剩余页面任务放入后台队列处理。后台任务系统通常包括:队列(如 Redis)和 任务执行服务;也可以定时调度任务,如定期清理或汇总。

如果你需要调度未来某时运行的大任务(如一个月后),Redis 不适合作为长期持久化任务存储,因为其持久性不稳定,也不方便查询。此时建议在数据库建表,保存待运行任务与计划时间字段,并由每日任务扫描处理。

缓存策略

有时操作变慢是因为重复进行耗时任务。典型场景是获取价格时频繁调用第三方 API,这既慢又压力大。常用解决办法是缓存,通常采用内存或 Redis/Memcached,但缓存本身就是另一种“状态”,容易出现不一致或过期问题。首选应尽量优化底层,如加索引,而非盲目缓存。

对于大型或昂贵的操作,可使用持久缓存策略:比如生成报告时,将结果 blob 存到 S3,然后直接从 S3 提供结果,而不一定用缓存系统。


原文:https://www.seangoedecke.com/good-system-design

这篇文章非常实用、扎实,它从“减少状态”出发,强调保持系统简单可恢复,并直接从性能、安全、可靠性角度阐述合理设计的底层原则。这种“平凡胜于炫酷”的思维在实际工程中很少被强调,而一旦实践,就十分有效。

几条补充思考:

  • 建立故障安全机制:在设计系统时,应考虑“死亡开关”(kill switch)、降级路径和监控报警,这些与“看似不起眼”但稳定的系统理念一脉相承。
  • 自动化恢复能力:关于有状态系统,可以考虑更深入的是如何自动修复状态错误,例如基于 schema 演进、数据补偿流程、幂等设计等。
  • 基础设施演进策略:复杂系统应该源自简单系统的进化,这一点文章强调,但在实际企业级架构中,还需结合团队能力、运维成熟度逐步扩展,而避免“一开始就上车”。这需要文化与流程上的支持。
  • 微服务 vs 单体:文章未涉及服务划分策略。保持系统简单并不意味着保持单体架构,但在拆分服务时仍然要保证“单一责任”且状态集中管理,否则可能引发更多复杂性。

总之,这篇文章强调:

  • 优秀设计是“看不出设计的好”
  • 控制状态是核心
  • 后台处理、缓存与副本使用应合理而不是默认开启

它非常适合作为系统设计原则的底层指引,也值得在工程团队中广泛传播和实践。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 识别优秀设计
  • 状态和值无状态
  • 数据库设计
  • 慢任务与快任务分离
  • 缓存策略
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档