Paul Jialiang Wu agentic-portfolio English Español 한국어 日本語✉️ 免费订阅列表
田野笔记 · 系统设计

我没通过 Google 的系统设计面试。这是我从中建立起来的架构师循环。

1 分钟速览——你能带走什么

我参加了一场 Google Forward-Deployed-Engineer 的系统设计面试,没有通过,之后把这项技能从头重建了一遍。十五篇著名的面试文章不会让你成为架构师——但有一个可以反复练习的习惯会:先说出权衡取舍,再画框图。架构师循环:约束、估算、草图、压力测试、演进。

十五篇著名的面试文章不会让你成为架构师。有一个可以反复练习的习惯会:在画框图之前,先说出权衡取舍。

← 返回作品集

周一我参加了 Google Forward-Deployed-Engineer 的系统设计面试,没有通过。缓存、队列、分片、负载均衡器——这些组件我都认识。可在压力之下,我没做到那件把工程师和架构师区分开来的事。于是我花了一整周,从头把这项技能重新练了一遍。以下就是我的发现,按我当初希望自己能提前知道的方式整理出来。

网上流传着一篇热帖——「These 15 articles will turn you from engineer to architect」(中译:这 15 篇文章能让你从工程师变成架构师)——里面链接了 Instagram 的设计、Netflix 的架构、缓存、Kafka 等等。这些文章都不错。但读十五篇蓝图并不会让你变成架构师,就像读十五份菜谱不会让你变成厨师。蓝图讲的是什么。面试考的是怎么做——而「怎么做」,说到底就是一个习惯。

这一个习惯:说清楚你愿意放弃什么

工程师拿到「设计 Instagram」这个题目,会本能地伸手去抓一个熟悉的方案,然后再倒着找理由。架构师会先问一个不一样的问题:我究竟允许自己牺牲什么?现实中的每一个系统,都是在若干个「不可能同时全都要」的东西之间做取舍——其中最有名的那个取舍,还有专门的名字。

CAP三个字母,装下了整套思维方式。当网络发生分区时(规模大了迟早会发生),对那份数据,你只能在一致性和可用性之间保留其中一个一致性或者可用性——不可能两者兼得。银行系统会选择保住一致性,宁可拒绝这笔写入;「点赞数」这类统计则会选择保住可用性,之后再慢慢对账。架构师会把自己牺牲的是哪一项说出口,并说明理由。「对这份数据,我愿意用一致性换可用性,因为一条过期的点赞无所谓,但一笔被误拒的支付不行」——这句话,才是这份工作该有的样子。

十五份蓝图是词汇,权衡取舍才是语法。光靠多背单词,说不出这门语言。

架构师循环——一套能在压力下跑起来的方法

我真正犯的错误是:还没搞清楚数字——正是这些数字决定了哪种架构合理、哪种是浪费——我就直接跳去画方框画箭头了。所以我把这个习惯变成了一个五步循环,能在任何面试里大声跑一遍。它也是每个答案的提纲。

1 · 约束(CONSTRAIN) 功能性需求 + 非功能性需求 需求;SLA 2 · 估算(ESTIMATE) QPS、存储量、 读写比例、 p99 预算 3 · 草图(SKETCH) 画出正常路径(happy path), 从上面的数字 推导而来 4 · 压力测试(STRESS) 什么会挂?热分片、 冷缓存、 队列积压 5 · 演进(EVOLVE) 指出 瓶颈 + 下一个扩容杠杆 随着规模扩大,循环会不断重复——每个答案都是一圈,而不是一张快照

架构师循环。 蓝色的步骤,是我以前会直接跳过、径直画草图的地方。面试真正考察的是「设限」「估算」「压测」「演进」这几步——这些才是能看出判断力、而非死记硬背的地方。

真正的魔法在第二步。粗略估算出的数字,决定了之后所有的选择。日活用户数 × 操作次数 × 载荷 = 你的写入 QPS。读操作通常是写操作的 10 到 1000 倍。存储量则是「每日数据量 × 保留期限」。没有这些数字,你根本没法证明缓存、分片数或队列的必要性——你只能瞎猜,而面试官一眼就能看出你在瞎猜。有了这些数字,某种架构会显得明显正确,另一种则显得明显浪费,你也就能说清楚为什么。

把这些术语,按它们各自解决的决策重新分组

现在,这十五个话题不再是一份阅读清单,而是变成了四组决策。每一组里,唯一值得记住的,是那个反直觉的判断——也就是聪明的工程师最容易搞错的地方。

第一组——数据层(状态存在哪里)

主题反直觉的判断
数据库选型访问模式选,而不是按热度选:先把最热的查询建模出来,再选一种能让它做到 O(1) 的存储方案。SQL 适合 ACID 和关系型数据;宽列存储适合海量写入的信息流;分片键存在的意义,就是让最热的查询始终落在同一个分片上。
缓存真正难的不是速度,而是失效。TTL 是务实的默认方案;陷阱在于冷 key 或过期 key 触发的惊群效应——在 key 冷掉或过期时触发,解决办法是请求合并(request coalescing)加先返回旧值再异步刷新(stale-while-revalidate)。cache-aside这也是最常见的写入策略。
数据结构它们与存储介质一一对应:B 树最小化磁盘寻道(SQL 索引);LSM 树偏好写入(Cassandra);一致性哈希环在节点加入/离开时只需迁移最少的数据;布隆过滤器能低成本地跳过注定会未命中的查找。

第二组——入口与约定

主题非显而易见的判断
负载均衡器 vs 反向代理问的是不同的问题。LB 回答的是「一台机器扛不住这么多流量」(把流量分摊到多台相同的后端上)。反向代理回答的是「我需要一个聪明的门面」(TLS、路由、把慢客户端挡在应用外层缓冲住)——它也可以只服务单个后端。但更常见的是,一个网关身兼二职。
REST / API无状态性才是关键:每个请求自带上下文,所以任何一台服务器都能处理它——这正是让你能在 LB 后面做水平扩展的特性。幂等性则是让重试在分布式系统里变得安全的关键。当 REST 的统一性开始让你吃亏时,可以转向 gRPC(内部调用、低延迟、支持流式)或 GraphQL(按客户端需求定制载荷)。

第三组——异步骨干(在时间维度上解耦)

主题反直觉的判断
Kafka / 队列可回放的日志能把生产者和消费者在时间和速率上解耦。顺序保证是分区内有序,而非全局有序——挑选分区键的关键,就是保住你真正需要的那种顺序。真正该盯的健康指标是消费延迟(consumer lag),而不是吞吐量。
微服务驱动它的是组织问题,而非技术问题(Conway's law 康威定律——团队需要各自独立部署)。你用一次进程内调用换来一次可能失败的网络跳转,代价是要多做服务发现、重试、熔断、链路追踪,还得用sagas来代替分布式事务。反直觉的地方在于:大多数系统一开始就该是一个结构良好的单体应用。
架构模式每种模式都是用耦合换灵活性,或者用一致性换扩展性:事件驱动(解耦、最终一致)、CQRS(读写负载分化时拆分读写模型)、绞杀者模式(逐步迁移单体应用)。说得出模式的名字不是本事——知道它到底缓解了哪个约束才是。

第四组——两个案例研究(均匀策略失效的地方)

Instagram 和 Netflix 之所以出名,是因为它们各自教会了我们一个关于分布尾部 的深刻道理。

普通用户发帖 粉丝数少 写入时扇出 → 预先写入每个粉丝的信息流缓存(读取 O(1)) 名人发帖 粉丝数百万 读取时扇出 → 不主动推送(否则将产生数百万次写入); 粉丝打开 App 时才拉取并合并 读取时合并 推送的信息流 + 拉取的 名人帖子 → 合并进信息流

Instagram = 混合式扇出。 这背后的道理是:统一策略在尾部会失效——推送模式读取是 O(1),但对名人账号会写爆;拉取模式写入便宜,但读取慢。答案是两者兼用,按账号类型分别选择。能否看出统一规则在尾部失效,就是这道题的全部考点。

Netflix 教会我们的是相反方向的拆分:把庞大、可缓存的数据面(视频预先编码成多种码率,推送到靠近 ISP 的 CDN 边缘节点)与小型、动态的控制面(认证、推荐——云端的微服务)。而且它把故障当作常态:混沌工程会故意杀掉实例,以验证系统能否承受住。架构师的收获是——把故障视为默认要发生的事,并把数据尽量挪到离用户近的地方。

我当时漏掉的收尾一步

以下是我现在会做、但周一那天没做的事。画完正常路径的草图之后,我会把故障模式大声说出来:这个节点挂了会怎样?这个队列积压了会怎样?这个缓存冷了会怎样?这个分片过热了会怎样?然后我会指出瓶颈,以及下一个扩容杠杆。一个没有故障剧本的设计,只是工程师的答案。能说出瓶颈在哪、下一步怎么演进——这才是架构师的答案,也是真正被打分的部分。

一口气讲完整套方法

约束 住问题,估算 出数字, 出这些数字所要求的路径,压测 它在故障下的表现,然后演进——指出瓶颈在哪、下一个杠杆是什么。把每一步取舍都说给别人听。仅此而已,这就是读过那十五篇文章、和能设计出第十六篇之间的区别。

我周一没通过一场系统设计面试。到周五,它已经变成了一套框架——说实话,我现在对这套东西的理解比当时通过面试还要深。把失败变成一件能教给别人的事,这才是它真正能留下来的原因。如果你也在备考:别只读那十五篇文章去找答案,要读它们背后的取舍,并且每一次都练习把自己选的那一个大声说出口。


写于我没通过的那场 Google FDE 系统设计面试之后,我把「15 篇文章,从工程师到架构师」那份阅读清单,消化成了藏在它们背后的那一个习惯。如果这篇文章能帮你比我当时走得更从容一点,那它就算是完成使命了。——Paul