You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

236 lines
18 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 07 | 系统设计能力考察:系统设计内功到底怎么考?
你好,我是四火。
在上一讲,我们谈到对于数据结构和算法部分考察的把控,这一讲,我们来聊聊系统设计。
我们经常说软件工程师要注意积累工作经验,那么,年复一年的工作中,我们到底积累了哪些技术方面的经验?
我想,其中很重要的一个就是对于系统的理解。对于刚毕业和工作不久的工程师来说,我们不应苛求他们对系统具备相当深度的理解;另一方面,即便工作年数相同,不同候选人对于系统的认识深度也有着非常大的差距。
和能够明确判断出正误的算法类问题不同,系统设计我们往往没法给出丁是丁卯是卯的正误判断,因而面试中讨论的更多是可行性,以及对于不同实现方案的利弊权衡。这也是符合现实逻辑的,有一些相似的系统,它们的实现技术截然不同,有着不同的优势和短板,但是最终都解决了最核心的问题。
## 糟糕的例子
好,我们先来说几个典型的反面例子,和算法、数据结构的面试不同,系统设计的面试往往能遵循的简单套路更少,因而也能见到更多的“踩坑”实践。
### 排斥不一样的思路
最典型的例子就是面试官缺乏包容心。就像“看自己的代码越看越开心,看别人的代码越看越糟心”一样,在讨论一些经典系统设计思路的时候,如果对方的思路和常规思路明显不同,**某些面试官可能会立即觉得对方的想法很“奇葩”。**
**这种想法其实是非常危险的,因为面试官在心里默默建立了一个护城墙,凡是不符合“标准”的解决思路,都会产生排斥心理。**
下面我举一个具体例子说明吧。有一次,某位面试官和候选人讨论,怎样设计一个篮球比赛的文字直播系统。
这个系统需要实时显示当前的比分等信息那么一种思路是使用普通的查询客户端主动发起即定时地不断询问服务端最新的比分还有一种思路是使用长连接或者long poll这样的机制使得服务端可以更实时地推送最新的比分更新。
很多系统都采用后者的思路来实现,这主要是基于实时性等方面的考虑,但是当时候选人考虑使用前一种方案。于是,面试官就直接中断了他的陈述,并直接解释为什么应该选择后者。
暂且不说所谓的正误,或者好与不好。这个技术实现选择的不一致,本来可以引出一个很好的面试实践,也就是对两种实现机制的利弊做讨论,但很遗憾,这种打断的做法直接终止了这样的讨论。
也就是说,我们完全可以跟着候选人的思路,用上一讲提到的“要求澄清”的办法让他自己来分析出实现的利弊来。
之后,如果候选人自己意识到,或者面试官使用“给出挑战”这样的办法,通过讨论这个方案的不足,来引出后一个方案,并放到一起进行优劣比较。
这样一来就可以有比较多的讨论与互动,我们也能得到更多的关于候选人对于系统理解的数据点。我想强调的是,最后采用哪一个方案其实并不重要,重要的是这个过程和我们得到的考察数据。
还有种类似情况是,**有时候选人对于系统的设计思路是自底向上的,而面试官则是自上而下的。**
一般说来,自底向上去思考和设计系统的时候,确实存在“捡了芝麻丢了西瓜”的情况,但是如果面试中候选人这么做,我们不要去打断他,或者破坏他的这种思维模式,而要顺着他最熟悉的方法来讨论这个问题。
我们做系统设计面试的目的,是要考察候选人对于系统的理解,抓住一些有意义的数据点,而不是要尝试去把系统设计做到最好。
换言之,**我们在面试中,一定要摒弃掉所谓的“最佳设计”或者“推荐方案”这样的想法,而是尽量追着候选人的思路跑!**
最后设计出的系统也许有这样那样的问题,但请记住面试官可以主导面试,却最好不要主导设计——完成的系统一定是一个候选人主导设计的系统,而不是面试官。
### 无意义的深挖
我们当然可以针对系统的某一个点进行深挖,但是如果候选人已经暴露出,他还不具备深挖该问题的基础知识,那么这个继续深挖很可能就是徒劳的,因为它除了给候选人带来沮丧以外,并不能得到太多更有价值的考察数据点。
举个例子有一位面试官和候选人一起讨论“短URL系统”的设计问题。其中一个点是在做短URL重定向的时候返回码应该选择301还是302
302是临时重定向301是永久重定向虽然301可能会减轻一定的访问压力但通常来说我们一般都选择302。这里的原因是这些系统有一个很大的价值就是可以获取用户的访问数据使用301可能会使得用户后续的访问直接跳过了这个系统而去访问重定向后的系统这样这些有价值的URL访问数据就丢失了。
但是当时候选人只对HTTP返回码略有了解并不知道301和302的意义是什么回答也明显有些搪塞这种情况下面试官还想继续往下挖问到底应该选择301还是302。我觉得这种深挖的意义就不大了。
## 考察技巧
好了,说完反面,下面来从正面讲讲系统设计考察的技巧。通常来说,系统设计的考察有两种主要形式:
* 第一种,是让候选人讲他自己熟悉的、做过的系统,或者是引以为豪的项目;
* 第二种,是像前几讲中介绍的那样,给出一个实际问题,细化、分析,延伸成一个对于核心功能的系统设计问题,并加以讨论和解决。
### 做过的系统
第一种面试方式非常常见。**让候选人谈论做过的系统,把握一条原则,是候选人必须非常熟悉,最好是“最”熟悉的系统**,这样我们才能了解到,候选人在过去的工作中,表现如何,有多深入的理解,又有多少思考。
我们不妨回想一下其它的技术方面的考察路径,如果是算法和数据结构,问“做过的算法”,好像很难这么问,而且这么问很可能也挖不到什么有价值的信息;如果是面向对象,问“做过的项目中,类和方法的设计”,听起来就觉得非常别扭,对不对?
确实如此,对于技术方面的考察点,唯有系统设计,问“做过的系统”,是一个非常好的途径。
话说回来,这样的面试方式,**也存在着一定的局限性。**
**最大的问题,就是它只考察了候选人对于“已有系统”的理解,多为“经验之谈”,这是可以事先充分准备的。**它并不能很好反映出,对于一个陌生的新问题,候选人能否灵活地运用掌握的套路和方法来解决它。
事实上,在面试中,我也确实遇到个别候选人,虽然对于自己做过的项目说得头头是道,但给出一个新问题,却畏手畏脚,缺乏想法,表现不佳。
其次,必须指出的是,**这种方式,对于面试官的素质也有着相当高的要求。**
因为相对而言,这种方式下,谈论怎样的系统,包含着怎样的问题,涉及到怎样的技术,都是候选人所主导的,如果面试官并不具备丰富的经验,又恰好遇到一个不属于自己熟悉领域的软件系统,有时候就没办法提出一针见血的问题,也没法采集到充分且有说服力的考察数据。
在某些大厂的面试培训中,**培训老师甚至会明确建议,经验较少的面试官不要主导系统设计面试,而是主导相对容易操作的算法和数据结构的面试。**
![](https://static001.geekbang.org/resource/image/4a/11/4a2872f07c2da3e22e5873ae4cfa4411.jpg)
对于发问,我们可以这样做:
> 你能否介绍一下,过去有哪一个你最熟悉,或是最引以为傲的软件系统?
我们也可以找到简历上最加以渲染的项目,而发问:
> 你在简历上说你主导了一个半年期的项目X我对于这个系统很感兴趣你能否介绍一下
其实这两个方式的目的是一致的。
对于考察的过程,我觉得要从宏观和微观层面去分别把握。
**在宏观上,观察候选人能否对他介绍的系统有着清晰的把握,有着整体的认识**,而不是一下子就跳到某一个具体细节实现上去。比如说:
* 系统解决了什么问题?
* 系统的架构是怎样的?
* 模块、组件之间的交互是怎样的?
* 系统存在哪些问题,有哪些瓶颈?
* 如果再给一次重新设计系统的机会,哪些地方可以改善?
**在微观上,对于候选人自己熟悉的、做过的部分,我们要选择几个点向下挖,并且挖掘得足够深,到“具体是怎样实现的”这样的程度**,毕竟在现实中,夸夸其谈的人不在少数。
“深入”的核心,就是要抓住两个词——“具体”和“细节”,下面我通过一个小例子来说明。
比方说,候选人说,他最近主导了一个三个月期的性能优化项目,那我们就可以抓住这一点进一步细化。请看下面这个对话片段:
候选人:我最近在项目中把门户网站的性能优化了一倍。
面试官:我很感兴趣,能具体说说是什么方面的性能优化了一倍吗?
候选人是网页的加载时间优化了一倍以前平均一个页面要将近3秒现在1.5秒就可以打开了。
面试官:是什么网页,门户网站的所有网页吗?
候选人:不是,是媒体详情页,不是媒体列表页。
面试官:哦,那你做了哪些改进才做到的呢?
候选人我把详情页上的评论部分改成Ajax调用了这样页面加载出来用户浏览评论区时再去异步请求获取评论。
面试官了解了那你通过什么方法得知这个页面加载时间从3秒优化成1.5秒的呢?
候选人我们做了性能测试压测我们beta环境的系统1.5秒是TP99的值。
面试官:那在压测的过程中,系统的吞吐量是多少呢?
候选人只有一个主请求没有模拟静态资源访问的情况下QPS上限大概是2000。
面试官那在测试中你是怎样确认这个QPS已经达到上限了
候选人根据我们的单机压测模型开始并发数逐渐增加吞吐量线性增大后来增长减缓但到大概第10分钟的时候出现拐点延迟突然增大吞吐量也过了峰值走低。
这里抓住一个点深挖的目的,并不是考察候选人能否记得性能优化的每一处细节,而是要确认候选人是否真正参与了这个过程,是否对这个优化过程的几个核心问题有清晰准确的认识。
换言之,如果候选人重新做一个类似的性能优化项目,要看看他是否确实因为这个项目而具备了相当的功底。从中我们也可以看出候选人对于某项技术,到底把握到何种程度,对于做过的项目和系统,是否具备一定深度的理解。
需要说明的是一个技术点的深挖要以拿到预期的考察数据为目标可以问1分钟可以问10分钟这都是没有限制的。一旦拿到了预期考察的数据不必过多纠缠继续下一个环节。
当然,这个话题点一定要是候选人(至少是自称)熟悉的,并且,**深挖和关注细节不是纠缠于细节,我们不要把追问变成钻牛角尖,也不要把追问变成“考记忆力”。**
### 全新的问题
前面讲了我们怎样去考察候选人对于他做过的、熟悉的系统,到底有多少理解,下面我们再来讲讲,如果是一个新问题呢,候选人能否顺利地设计一个全新的系统来解决它?这两个方面其实是互相独立的。
那我们就用一个模拟案例来介绍,如果我们像[第4讲](https://time.geekbang.org/column/article/362407)说的像做一个迷你项目一样给出一个实际问题层层递进逐步细化、分解和延伸成一个系统设计的问题如果你忘记了请一定回看第4讲我们又该注意哪些方面。那个具体问题就是
> “请你设计一个网约车系统。”
并且我们也谈到了,经过细化以后,下面的系统设计考察,就是要求设计一个系统,来完成定位、叫车、搜索、接单等网约车系统的核心功能,如:
1. 乘客可以随时随地叫车;
2. 系统寻找邻近的司机并转发请求,如果无法接单,继续扩大寻找范围;
3. 司机可以抢单,即接受或者拒绝叫车请求;
4. 双方可以刷新自己在地图上的位置,也可以查看彼此当前的位置。
对于非功能需求,特别是系统容量的估算,我们后续将在专题中谈到;而对于功能需求的系统实现方面,我们继续来看后面候选人和面试官之间的对话片段。
**第一阶段:客户端 - Service - 存储的基本层次建立**
面试官:根据我们的讨论所确定的需求,这个系统中会有哪些主要模块?(要求澄清)
候选人我觉得这个系统中乘客可以叫车司机可以接单因此我打算创建一个Ride Service来接受乘客的请求ride的信息存放在数据库中。整个交互过程如下图
1. 乘客请求乘坐提交一个ride相当于一个请求乘坐的订单
2. Ride Service将乘坐请求发给多个司机
3. 司机抢单;
4. Ride Service再将接单的信息告知乘客。
![](https://static001.geekbang.org/resource/image/e8/50/e8a3c8d7a4001152ed4772f7e41f1650.png)
**第二阶段Service 解耦**
面试官那么其中的第2步和第4步消息怎样从服务端传递给客户端呢要求澄清
候选人我觉得可以使用长连接来保证消息能够第一时间送达如果这一步失败iOS或者安卓平台还有统一的消息推送机制。
面试官那么Ride Service怎么来寻找合适的司机来转发乘客的乘坐请求呢给出挑战
候选人我觉得Ride Service知道所有乘客和司机的位置那就可以寻找最近的一些匹配位置可以通过司机和乘客各自手机上的app来汇报比如每十秒钟就汇报一次GPS位置。
面试官:可是我没有看到你画的图中有这个啊?(要求澄清)
候选人他们都汇报给Ride Service。
面试官所以位置信息归Ride Service管乘车请求的匹配归Ride Service管匹配上了以后结果的通知也归Ride Service管它一个service要管那么多事情吗给出提示
候选人嗯……我觉得可以解耦根据单一职责的原则让一个service只做一件主要的事情
* Ride Service只管ride的匹配和决策
* Notification Service负责通知消息
* Location Service负责维护司机和乘客的位置。
![](https://static001.geekbang.org/resource/image/d8/fc/d8137ed34cbeaeb73ac5f0d402b6dbfc.png)
**第三阶段:进一步解耦,补足缺失**
面试官确实看起来好多了。但是我还有个问题司机和乘客汇报GPS位置的时候从图上看只有在乘客请求和司机接受ride的时候才会汇报位置信息吗要求澄清
候选人系统是需要实时获取位置信息才好做匹配我觉得GPS的信息也应该从ride的逻辑解耦开应该有单独的位置汇报请求发给Location Service。
![](https://static001.geekbang.org/resource/image/cf/f4/cf4ae62f446880320000614bdb170bf4.png)
**第四阶段:细化存储层设计**
面试官不错。我注意到ride数据和位置数据也随着Ride Service和Location Service的解耦而分离开了这很好。那么对于这两类数据的存储你打算分别选用怎样的技术来实现呢
候选人ride数据需要接受关系查询位置信息需要根据司机、乘客的ID来查询还需要能够快速找到邻近位置的司机和乘客……对于这两类数据的存储可以作为下一个话题来详细展开
不知道你发现没有,这个小片段,就模拟了面试官使用“要求澄清”和“给出挑战”两大法宝,怎样领着候选人一步步攻克系统设计问题的过程,这个过程和上一讲的算法和数据结构面试,是类似的。
其中,对于位置数据的存储,如果感兴趣的话《全栈工程师修炼指南》的[第26讲](https://time.geekbang.org/column/article/162965)中,我做了详细的介绍,你可以做延伸阅读。
![](https://static001.geekbang.org/resource/image/2c/fa/2c610e117e0ebdb75a614375ee70b9fa.png)
## 总结与思考
好,今天的内容主要就是这些。
我先给你举了一些系统设计面试的踩坑案例,然后又给你分享了两类主要的面试方式,深挖候选人做过的系统,或者和他一起讨论一个全新迷你项目的实现,它们都能考察候选人对于系统的理解,但是又各有侧重。
你可以结合我为你还原的面试现场对话,仔细体会系统面试的操作方法。
这里我需要额外强调的是,系统设计就和算法一样,我们还是更应该关注基础,选择合适的问题,可以问,但不要一味追求高大上的“海量存储”、“大数据”这样的问题,这和算法和数据结构的面试不要过度追求“偏题”、“怪题”是一样的。
**毕竟,能将生活中那些实际的问题,选择合适的工程技术来解决,才是软件工程师最根本的使命。**
![](https://static001.geekbang.org/resource/image/af/1d/af55fabf291cc6c979d439b7541cbd1d.jpg)
在最后,我想请你聊一聊你在阅读后的想法,特别是你是否有系统面试的经历,你觉得有哪些特别重要的方面,能否在留言区谈一谈呢?我会积极回复你的想法和问题。
好,我是四火,我们下一讲见!