QIAOPANG.COM · 陈天(TYR CHEN)2013–2026 精选技术与随笔文集 · 共 260 篇按时间顺序浏览(2013 → 2026)↗
程序人生 · 陈天文集QiaoPang · Systems, Craft & Agentic Era
时间轴 ↗

谈谈系统设计的面试

谈谈系统设计的面试

请听题:一个使用 rail(或者 django,或者 express,…)和 MySQL 做的 API 系统,最近流量从 6,000 RPM 激增至 20,000 RPM,整个系统的压力骤升,现在需要在应用层设计一套缓存方案来降低整个系统的负荷。要求是:缓存方案不能在 web 层(包括 proxy)做,也不能使用 framework 自带的,或者第三方的缓存模块。

大部分的面试者一看,这问题简单啊,使用 redis(或者 memcached),加在应用服务器和数据库服务器之间,读取数据的时候如果没有命中缓存,则读取数据库并写入缓存,下次再读相同的数据时就能命中缓存,大大减轻数据库的压力了。

这回答对么?不好说。也许对,也许不对。但你要这么快抢答的话基本上就会被面试官毙了。

系统设计的面试重在讨论和交流,厘清一切限制条件,然后在这些限制条件下面找到一个比较合理的解决方案。它不是编程题或者算法题,弄清楚题目的要求就可以写开始答案的。

好的面试者应该主动发问,来尽量找全限制条件,而不是直接假设。拿上述回答来说,面试者还没开始认真分析问题所在,就想当然认为压力在数据库一侧(是的,流量激增之后 90% 的可能性都是数据库先扛不住压力,但这是假设,不能化作前提),从可能错误的前提出发,必然会得出一个很可能错误的解决方案。

所以比较对路的思考过程是:

现有系统的架构是什么样子?

作为一个已有系统的优化项目,不了解现有系统的架构,历史(演变的过程和演变的原因,当然,在面试中这个可以省去)而立刻上手设计都是在耍流氓。

  • web 服务器,应用服务器,数据库服务器等之间的关系以及数量?
  • 现在有没有使用类似于 redis / memcached 的缓存服务器?如果有,它们用来处理什么任务?(如果有人问道这个,会有大大的加分)

目前哪个部分在系统中压力最大?

这个问题非常重要,你得需要先知道问题在哪再考虑优化。如果问题出在应用服务器,那么,可能需要做页面级的缓存;如果问题出在数据库服务器上,可以做数据级或者页面级的缓存。

我们希望达到一个什么样的 capacity?

很多有多年一线工作经验的面试者在这样一个系统设计中竟然不去考虑究竟需要一个什么样的 capacity,就进入到具体的解决方案,这样是不妥的。capacity 因问题而异,在这例子中,起码要考虑 1) 缓存系统每秒的处理能力 2) 缓存系统的容量。对于一个 20k PRM 的请求数量,缓存系统应该能承受 50k,100k,甚至 200k PRM 的请求。至于容量,应该考虑假设所有不同的请求都被缓存(worst case),需要多大的容量,在限定的软硬件条件下,是否能达到这个容量,达不到的话,什么样的上限比较合理。

有了这样一个目标后,你还需要对你要使用的工具有个谱。有一个面试者说用 redis 做缓存,因为 redis 很快。「很快」是个很虚的概念,我于是问这个面试者你觉得 redis 对于 1k 大小的value,在 commodity hardware 上做 GET 操作每秒钟的 QPS 是多少?对此,面试者一点概念也没有,我让他猜,他竟然给了个 3-5k QPS 的估值。我自己印象中 redis benchmark GET 操作大概是 100k 这个数量级,当然,每次返回 1k 大小的数据会拖累这个结果,但绝不会差出来两个数量级。

有了设计容量的概念后,我们需要知道要缓存数据的大小,这其中,median size,average size,max size 都需要了解一下,起码知道是什么量级。返回 2k 大小的数据和 200k 大小的数据的处理方式可能是完全不同的,假设你的缓存系统的容量是 1M,2k 数据大小的缓存直接占用的内存是 2G,而 200k 则是 200G,后者显然不能使用内存来做缓存,只能用文件系统缓存。

讲到文件系统,多说两句。用文件系统做缓存则需要注意 unix 的目录实际上是一个记录文件名和 inode 对应关系的 map(你可以 ls -ai1 . 查看)。单个目录下的文件越多,这个 map 越大,需要的读取次数就越多(一般系统调用会每次读 32k 或者类似的量级),所以当一个目录下的文件特别多时,访问效率会急剧下降。这是为什么常见的文件缓存系统都是用两级甚至多级目录,1M 个文件,一级目录使用两个字母或数字,可以有 (26 + 10) 平方个二级目录,也就是 1296 个目录,每个目录名两个字节,加上 inode 和其他一些消耗, 10-20 字节完全够用,一次读取就能获得所有二级目录,而二级目录平均是 772 个文件,一次读取也能完成,总共两次读取,找到缓存文件,而如果把 1M 个文件放在一个目录下,如果每个记录 32 字节,需要 1000 次读取。这种分级缓存的思路在很多系统中都能见到,比如 TLB(不过多级 TLB 主要是为了节省内存)。

设计是否有优化的地方?

如今,内存,硬盘已经非常便宜,很多时候我们做系统设计,已经不需要一个比特或者一个字节地去扣细节,但这并不意味着更好的,更省内存,更快运行的方案就没有价值。我曾在一个面试中和面试者讨论一个系统设计的优化,那个面试者对我「逼」着他优化算法很不理解,他认为 computation 这么便宜,钱不是问题,多加几台机器并行运算就可以了。这是一种错误的做事态度。

永远不要忘了设计应该是面向未来的,如果通过更换更好的算法,能节省数十倍的内存(bitmap vs hashmap),或者数十倍的运算(bloom filter pre-filter vs raw computation),那么你省下的不仅仅是当下的资源(或者金钱),还有未来的时间 —— 因为,当你的应用有10倍的流量时,你还能够应对自如。

此外,优化可能会从量变转化为质变。一个 analytics 应用如果每五分钟才能完成一次分析,一小时仅能进行 12 次分析;如果将其优化成 30 秒完成,一个小时就可能完成 120 次,用户可以更快地掌握趋势。

一个认为资源不是问题,钱不是问题的设计者,只能是一个平庸的设计者。

每个认真的程序员应该这样看待自己:In me the tiger sniffs the rose.

MORE IN 系统与架构

同专栏延伸阅读

查看「系统与架构」全部文章 →

日志的未来:必也正名乎

我们花了十年,把可观测性盖成一座漂亮的大厦,地基却是漂浮的字符串:字段名靠默契、基数靠运气、账单靠忍。这篇文章给出一个激进的方案,用 protobuf 给每一件被观测的事正名,把日志、指标、审计统一成一份带版本、带 owner、带保留策略的事件目录,旁边留一扇叫 ForensicEvent 的逃生舱兜住所有未知未知。AI 已经成为代码的主笔,schema-first 的最后一道阻力随之消失。是时候让 schema 重新成为可观测性大厦的

New Hope (2025–2026)10,772 字 · 24 分钟

彻底抛弃多环境:用 Rust 与 Pingora 重塑下一代 Serverless 架构

当我们退回原点重新思考"计算"的本质,会发现多环境是个幻觉,K8s 是个过度抽象,而 Serverless 的冷启动和连接池风暴是自找的。这篇文章拆解一套基于 Baggage Header 路由、Pingora 智能网关、Firecracker 微虚拟机和 Rust Data Bus 的无环境架构,试图回答:Post-Kubernetes 时代的计算该长什么样。

New Hope (2025–2026)23,788 字 · 53 分钟