我停止了“Scaling Fast”,开始设计失败——以下是改变的结果

发布: (2026年1月14日 GMT+8 21:56)
6 min read
原文: Dev.to

Source: Dev.to

封面图片:我停止“快速扩展”,转而设计失败——这带来了什么变化

很长一段时间,我以为作为开发者的工作就是让系统正常运行。
现在我相信我的真正职责是让系统以清晰的方式失败。

本文描述了一种转变,它悄然改变了我设计架构、API,甚至团队的方式——尤其是在构建看似正常却随时可能出问题的复杂产品时。这不是给初学者的文章;它讨论的是在你已经发布、弄坏、在凌晨 3 点修复并对自己承诺“再也不这样”之后才会在意的决策。

问题:复杂性隐藏在“可运行代码”背后

大多数系统并不是因为代码本身的错误而失效。
它们失效的原因在于:

  • 假设是隐式的
  • 约束没有文档化
  • 失效模式不可见
  • 成功路径被优化,失效路径被忽视

在我的一个产品的早期版本中,一切“都能工作”:

  • API 有响应
  • UI 流畅
  • 指标显示为绿色

直到一个单一的边缘案例导致:

  • 部分数据损坏
  • 重试放大了负载
  • 日志记录的不是事实,而是故事

系统没有大声报错;它是礼貌地失效了。这才是真正的问题。

转变:首先为失败而设计

而不是问:

“我们如何让它可扩展?”

我开始问:

“它会怎么崩溃——我们如何立刻知道?”

这让我采纳了几条不可妥协的设计原则。

1. 约束是特性,而非限制

每个复杂系统都有约束。错误在于假装它们不存在。

我现在在编码前写下的显式约束示例:

  • 最大请求大小(硬性失败,而非尽力而为)
  • 可接受的数据陈旧度
  • 每个依赖的超时预算
  • 重试次数限制(使用指数退避或根本不重试)
  • 所有权边界(该服务不负责修复其他服务的 bug)

如果约束没有明确写出,它就会沦为口口相传的“传说”。传说在故障时撑不住。

2. 必须为失效模式命名

如果你说不出某件事会怎样失效,就无法对其进行推理。

我现在这样记录失效模式:

  • 上游不可用 → 返回缓存的降级响应
  • 部分写入成功 → 触发补偿事件
  • 客户端误用 → 大声拒绝并返回可操作的错误信息
  • 状态未知 → 停止处理,报警人工介入

这不是悲观,而是工程上的诚实。

3. 可观测性不是日志

  • 日志是叙事。
  • 指标是聚合。
  • 追踪是时间线。

单独任意一种都不能完整呈现真相。对于关键路径,我会问:

  • 哪个信号告诉我系统已经坏了?
  • 从故障到检测的时间有多长?
  • 我能否在不猜测的情况下知道受影响的对象是谁?

如果答案是“我们去看日志”,那系统就在对我撒谎。

4. API 应该严苛(以保护系统)

“对接受的内容宽容”听起来不错——直到它变成带利息的技术债务。

我现在设计的 API:

  • 积极进行校验
  • 拒绝模糊的输入
  • 返回的错误要说明如何修复,而不仅仅是说明哪里失败

友好的 API 保护用户,严格的 API 保护系统。优秀的 API 两者兼顾。

5. 团队是架构的一部分

如果所有权模糊,责任就会被每个人共享,故障会被归咎为“别人的层”,系统也会映射出这种模糊。

明确的所有权边界可以减少:

  • 静默故障
  • 重复修复
  • 事故期间的情绪负担

技术架构与组织架构密不可分。

对我而言的变化

在采用这种思维方式后:

  • 事件变得更少,更重要的是,持续时间更短。
  • 调试从“发生了什么?”转变为“正是这个具体的东西失败了”。
  • 新开发者的入职速度加快了。
  • 我自己的认知负荷显著下降。

系统并没有变得更简单;它变得更诚实了。

结论

复杂性是不可避免的。
混乱是可选择的。

为失败而设计并不意味着消极;它让你更可靠。如果你的系统出现故障:

  • 明确地
  • 迅速地
  • 并且以你已经理解的方式

你就做对了。

Back to Blog

相关文章

阅读更多 »

系统设计:日历应用

功能需求 - 创建 event,修改 event,取消 event - 按日、周或年查看 calendar - 设置 recurring meetings - 发送 notification …

系统设计快速指南

系统设计是规模的语言,每位工程师都需要掌握它。我创建了这份 1 页的 Quick Guide,帮助你解码复杂的系统设计主题……