Skip to content

系统设计面试:循序渐进指南

许多软件工程师在系统设计面试(SDIs)中表现不佳,主要原因有三点:

  • SDIs 的非结构化特性,要求候选人处理一个没有标准答案的开放性设计问题。
  • 缺乏开发大规模系统的经验。
  • 没有为 SDIs 做好准备。

如同编码面试一样,没有进行有意识准备的候选人在 SDIs 中往往表现不佳,尤其是在 Google、Facebook、Amazon、Microsoft 等顶尖公司中。在这些公司里,表现未达到平均以上的候选人获得录取的机会有限。反之,良好的表现往往会带来更好的录用机会(更高的职位和薪资),因为它表明候选人有能力处理复杂系统。

在本课程中,我们将遵循循序渐进的方法来解决多个设计问题。首先,让我们了解这些步骤:

第 1 步:需求澄清 (Requirements clarifications)

在解决问题前询问问题的确切范围是一个好主意。设计问题通常是开放性的,没有唯一正确答案,因此在面试初期澄清不确定性至关重要。花足够时间定义系统的最终目标的候选人通常在面试中更容易成功。此外,由于我们只有 35-40 分钟来设计一个(假定是)大型系统,我们需要明确将重点放在哪些部分上。

以设计一个类似 Twitter 的服务为例,以下是在继续下一步之前应回答的一些问题:

  • 我们的服务用户能否发布推文并关注他人?
  • 我们是否需要设计创建和显示用户时间线的功能?
  • 推文中会包含照片和视频吗?
  • 我们是仅关注后端,还是也需要开发前端?
  • 用户能否搜索推文?
  • 是否需要显示热门话题?
  • 会否有关于新推文(或重要推文)的推送通知?

所有这些问题都将决定我们最终的设计方案。

第 2 步:系统接口定义 (System interface definition)

定义系统期望的 API。这不仅将建立系统所期望的确切契约,还将确保我们没有遗漏任何需求。对于我们类似 Twitter 的服务,以下是一些示例:

postTweet(user_id, tweet_data, tweet_location, user_location, timestamp, ...)

generateTimeline(user_id, current_time, user_location, ...)

markTweetFavorite(user_id, tweet_id, timestamp, ...)

第 3 步:初步估算 (Back-of-the-envelope estimation)

估算我们要设计的系统规模是一个好主意。这也将在我们后续关注扩展、分区、负载均衡和缓存时提供帮助。

  • 预计系统的规模是多少(例如:新推文数量、推文查看数量、每秒生成的时间线数量等)?
  • 我们需要多少存储?如果用户的推文可以包含照片和视频,所需的存储量将有所不同。
  • 我们预计的网络带宽使用量是多少?这在决定如何管理流量和在服务器之间平衡负载时至关重要。

第 4 步:定义数据模型 (Defining data model)

尽早定义数据模型将明确数据如何在系统的不同组件之间流动。随后,它将指导数据的分区和管理。候选人应该能够识别系统的各种实体,了解它们之间的交互,以及数据管理的不同方面,例如存储、传输、加密等。以下是我们类似 Twitter 服务的一些实体:

  • 用户:UserID、姓名、邮箱、出生日期、创建日期、上次登录等。
  • 推文:TweetID、内容、推文位置、点赞数量、时间戳等。
  • 用户关注:UserID1、UserID2
  • 收藏推文:UserID、TweetID、时间戳

我们应该使用什么样的数据库系统?像 Cassandra 这样的 NoSQL 是否最符合我们的需求,还是应该使用类似 MySQL 的解决方案?我们应该使用什么样的块存储来存储照片和视频?

第 5 步:高层设计 (High-level design)

绘制一个包含 5-6 个方框的块图,表示我们系统的核心组件。我们应该识别出解决实际问题所需的足够组件,以实现端到端的解决方案。

对于 Twitter,从高层次来看,我们需要多个应用服务器来处理所有的读写请求,并在前面放置负载均衡器以进行流量分配。如果我们假设读流量(与写流量相比)将更多,我们可以决定为处理这些场景设立单独的服务器。在后端,我们需要一个高效的数据库来存储所有推文,并能够支持大量的读取请求。我们还需要一个分布式文件存储系统来存储照片和视频。

图1-1

第 6 步:详细设计 (Detailed design)

深入探讨两个或三个组件;面试官的反馈应始终指导我们进一步讨论系统的哪些部分。我们应该能够提出不同的方法,分析其优缺点,并解释为什么选择一种方法而非另一种。请记住,系统设计没有单一答案,重要的是在考虑不同选项之间的权衡时,保持系统约束的意识。

  • 由于我们将存储大量数据,我们应该如何对数据进行分区,以将其分布到多个数据库中?我们是否应该尝试将用户的所有数据存储在同一个数据库中?这样做可能会导致什么问题?
  • 我们将如何处理那些频繁推文或关注很多人的热门用户?
  • 由于用户的时间线将包含最新(和相关)的推文,我们是否应该尝试以优化扫描最新推文的方式存储数据?
  • 我们应该在哪一层引入多少缓存以加速系统?
  • 哪些组件需要更好的负载均衡?

第 7 步:识别和解决瓶颈 (Identifying and resolving bottlenecks)

尽量讨论尽可能多的瓶颈及其缓解的不同方法。

  • 我们的系统中是否存在单点故障?我们在采取什么措施来减轻这一问题?
  • 我们是否有足够的数据副本,以便即使丢失一些服务器仍能为用户提供服务?
  • 同样,我们是否有足够数量的不同服务在运行,以确保即使发生少量故障也不会导致系统完全停机?
  • 我们如何监控服务的性能?在关键组件发生故障或性能下降时,是否会及时收到警报?

总结 (Summary)

简而言之,在面试中准备充分并保持组织性是成功进行系统设计面试的关键。上述步骤应指导您保持方向,覆盖设计系统的各个方面。让我们将上述指南应用于设计一些在系统设计面试中常见的系统。