本文最初发表于 Denise Yu 个人博客,经原作者 Denise Yu 授权,InfoQ 中文站翻译并分享。
我最近注意到,在开放式、“无代码”技术面试中如何脱颖而出,有关这方面的指导并不多。通常情况下,应聘者在面试高级职位时,常被问及的主题之一就是软件架构的规模化。并没有多少公司能够像 Netflix、Amazon、Shopify、GitHub 等那样规模化运营软件。如果你从来没有做过类似规模的项目,你又该如何准备回答这样的问题呢?
尽管我在架构、部署和支持庞大的生产系统方面并没有太多的个人经验,但这篇博文是我试图阐述我是如何通过这类面试的经历。
补充:如果你知道你面试的公司的用户数不到 100 万,而且增长速度相当稳,在未来 1~3 年内不太可能出现“曲棍球棒效应”(译注:指数据在长时间的平稳后急剧上升或下降)的增长,如果他们在面试中问我很多关于“规模化”的问题,我就会认为这是一个危险信号。对我来说,这表明该公司的招聘决定是不成熟的,对规模的优化也是不成熟的,他们可能会将工程资源消耗在错误的优先级上,而此时他们需要的是通过特征集开发来投资于市场差异化。规模化不是重头戏。反正现在还不是。
我偶尔会引用一个真实面试问题,这个问题来自一家我不愿透露名称的公司。曾有一次,面试官问我:“如果你现在要打造 Twitter 的话,你将会如何构建它?围绕一个日常活跃用户高达数亿的平台的构建和扩展,你认为主要的挑战是什么?”
关于我的目标受众
我想指出的是,本文并不是针对初级面试者而写的。我假设阅读本文的是这样的读者:拥有几年的专业全栈或后端开发经验,且没有涉及任何大型产品的工作经验,有理论知识但缺乏实践经验,现在想在一家大公司谋求更有挑战性的职位,而这家公司的主打产品有 100 多万用户。这就是很可能会被问到这类问题的面试场景。
理想情况下,如果不是应聘架构师或领导职位的话,你至少要谋求一个高级工程师的职位。出于这一原因,本文有一些高级术语我没有给出定义——我假设阅读本文的读者以前遇到过像数据库复制这样的概念。
如果你已经开始了你的旅程,我建议你迈出以下几步:
首先,请观看我为初学者做的关于分布式系统挑战的演讲视频。
其次,请从头到尾阅读 Martin Kleppmann 著的《设计数据密集型应用程序》(Designing Data-Intensive Applications)。
最后看看这本读物《系统设计入门》(systems design primer)。
1. 想一想你要如何存储数据
我们应该选择哪个数据库?
我经常问自己的第一个问题是:我们将主要使用什么数据库来存储用户数据?我相信,在 2020 年,除非你有非常非常非常令人信服的理由,否则,正确答案应该是 MySQL 或 PostgresQL。当然,对于 Twitter 的克隆,或者任何本质上是“大规模 CRUD”的应用程序来说,都是如此。两者之间一些细微的差别,也许值得提前研究一下。
译注:CRUD,指 Create, read, update and delete,即数据库操作创建、读取、更新和删除。
这里的原因是,MuSQL 和 PostgresQL 在巨大的生产规模上都有着数十亿小时的实战经验,这是许多以前走过这条道路的公司所采用的,它们都是为了快速存储和处理大量数据而设计的。几乎所有的开发人员都知道如何与 SQL 数据库交互,使用任意形状的文档存储、数据湖、NoSQL 和其他类型的数据库都有充分的理由,但是对于主要用户数据存储,我会选择更合适的方式。
在数据运行规模化时,我们还需要考虑哪些?
当你开始拥有几万个活跃用户,他们每天产生数百万行数据的时候,你最终需要一个数据分区策略。从某种程度上说,将整个数据库存储在一台主机上是不可行的。你必须以某种逻辑的方式将数据进行分块,这样多个主机就可以同步,并确保读写请求被转发到正确的地方。顺便说一下,这就是数据库分片(database sharding)。
数据库分片
我通常以百科全书的例子作为分片的真实例子。百科全书并不是作为一本巨著出版的:你不可能在物理上把一本书装订成那么大的书,这与数据吞吐量和存储存在物理上的限制何其相似!在开发团队中,你可能也不会真的将一张表分块(至少在很长时间内不会这么做)。MySQL 和 PostgresQL 表可以存储数百万行(也许可以存储几十亿?)。更实际的做法是,你将表划分为逻辑组,并将每个组放在不同的主机上。那要如何定义这些组呢?这完全取决于团队。有些团队喜欢按领域来组织它们:如果你的代码库中有明确的领域,比如社区模式和企业模式,那么它们相关联的表可能位于不同的主机上。还需要考虑如何跨这些组进行连接:SQL 连接已经很昂贵了,要是频繁地跨模式域进行连接的话可能会……嗯,如果操作不当,会造成生产停机。
数据复制
你还需要考虑数据复制策略。这需要对你的预期用户群有一定的了解。如果你正在构建 Twitter,可以肯定的是,你的用户分布在全球范围内,可能会高度集中在北美和欧洲,所以你也许会考虑在这些地区的数据中心中保留副本。复制数据和保持数据同步都是有成本的,因此,要将它作为一个投资决策来对待,就像本文中的其他决策一样。
一致性和高可用性的权衡
我认为在面试中无需解释 RAFT、Paxos 或其他共识算法,但是,如果对一些常见的分布式读写策略有所了解的话,在数据一致性要求的方面,你将能够做出更为全面的回答。例如,如果你在银行面试,数据完整性就非常重要,你可能希望使用事务来阻止竞争性的写操作。否则,就会有人可以从 200 美元的余额中提取 100 美元三次!
对于像社交媒体平台这样的东西,如果用户在发推后几秒钟内没有看到 Chrissy Teigen 的最新推文,那也不是什么太重要的事,但网上通常是在线的,这是非常重要的,所以我们优先考虑的是可用性而不是一致性,让“最终一致性”来处理大部分用户数据。
这里的关键在于,理解这个领域,并弄清楚业务需求到底是哪些,然后再将自己锁定在技术决策上,而这些技术决策对以后的迁移来说成本很昂贵。
租用托管数据库,还是自己托管?
就我个人而言,我还没有被问及过这个问题,我怀疑你也不会被明确要求回答这一问题,除非你要面试的职位涉及到解决方案架构师,或者公司有商业理由需要对数据存储位置保持警惕(如银行、律师事务所、或者医疗数据存储业务等)。但对于一些大型公司来说,如 Facebook、Amazon,运营自己的数据中心是有意义的。重要的是,这些公司也有高度专业化的员工来管理这些数据中心。因为对于大多数公司来说,这样的人员配置在现实中既不真实,也不可行。所以,通常情况下,请别人来担任你的数据库管理员是具有商业意义的。那个“别人”很可能就是 AWS,或者是 Google Cloud、Azure 等,如果它们有合适产品的话。
2. 在较高层次上理解分布式系统中的故障场景
如果面试要求你在白板上绘制方框和线条,那么很有可能是让你表示出服务以及两者之间的通信模式。对于关键的服务到服务通信路径,人们通常使用消息队列,如 RabbitMQ。而对于跨域消息发送,人们通常会使用发布/订阅(Pub-Sub)消息传递,如 Kafaka。我认为,你选择的具体技术并不比你想使用它们的原因更重要,至少在面试中是这样。所有这些工具都是解决方案,要想明智地谈论解决方案,你需要了解它们解决了什么问题。
还有,为什么是分布式系统?每个系统都是分布式系统,而且在很长一段时间内都是如此。
在工作中,你要做的决定不是“解决这个问题的最佳工具是什么?”,而是“什么是最好的工具,是经过安全审计批准的、财务部签字认可的、我们的供应商提供的、我的团队知道如何使用或能快速学习的、能解决这个问题的最佳工具?”——而这个具体的答案是不可知的,直到你深入了解。
当我在 Pivotal 工作时,我做的最有价值的一件事就是,花了一些时间与支持工程师合作,以便更多地了解庞大的、杂乱无序的分布式系统在实践中失败的所有方式(即使 UML 图是原始的)。如果这是你目前可以做的事情,我强烈建议你这样做:要求到客户支持部门进行为期一周的轮岗,见证客户在使用你所构建的系统时的各种困难。这不仅是一个很好的培养同理心的练习,会让你在任何高级职位上都更有效率;它还是一种非常安全的获得经验的方式,可以让你目睹系统是如何失败的。一些公司有专门的 SRE 团队,他们随身携带传呼机,如果可以跟踪某人一个星期,当他们醒来时你也跟着醒来,实时观察事件是如何发展的,并参与事后评估,这也是一种安全的学习方式。
事情怎么会出错了呢?
我从事 SRE 的朋友会告诉你,没有两个事件是完全相同的。特别是如果他们正确地完成了他们的工作:随着时间的推移,产生警报的事件应该越来越奇怪,而且很难用自动化来补救。(这意味着他们正在有效地构建自动化修复工具。)
但是,我仍然认为,为了给面试官留下深刻印象,有几种类型的失败场景你可以大致谈一谈:
内存不足、停止垃圾回收和其他应用程序级故障。见鬼,如果你正在编写 JavaScript API 的话,甚至“undefined is not a function”这种错误也算。
配置更改错误。
外部攻击(尤其是由恶意或无知的第三方发起的 DDoS 攻击)。
合法使用量的意外激增,看起来像是 DDoS 攻击。
基于集群的一致性问题。
几年前我见过一个臭名昭著的问题:RabbitMQ 节点会被网络分区导致离线,然后尝试重新加入它们的旧集群,但是节点加入的顺序对于建立法定人数(Quorum)非常重要。当然,它们试图以错误的顺序重新加入,这就导致了一个无限循环的恢复尝试。
还有很多!如果你想以一种愉快的方式阅读有关实际故障的信息,我建议你看一下 Incident Labs 出的《事后回顾》(Post-Incident Review)杂志:https://zine.incidentlabs.io/
3. 表明你可以在性能和可维护性之间进行权衡
抛开胡里花哨的白板图不谈,我认为有一件事可以令你脱颖而出,那就是让面试官知道你清楚软件系统的长期成功,是完全依赖于构建和维护它们的人。我认为这个讨论有两个层次,大致可以归纳为“性能(速度)与可维护性”:涉及你日常代码行的“底层”讨论;以及“500 英尺以上”层次的讨论,它更具有前瞻性,可能更符合高级工程师所期望的操作高度。这里的基本假设是,大型组织对构建快速可靠的系统感兴趣,并且他们希望雇佣懂得如何实现这一点的人。
你在系统中添加的每一个组件,都会增加认知复杂性。如果你在使用一个工具的方式稍微有点偏离常规,则在认知复杂性的债务上再加一分。如果要复制或分区,或者使用非主流的负载均衡策略,那就多加一点。以此类推。重点是,有很多设计选择,可以提高系统的整体稳定性、速度,或者两者兼而有之,但对于那些必须学习它们、在它们内部或周围构建、支持它们的人们来说,它们带来了实实在在的成本,也许有一天会弃用它们。别忘了团队是会发生变化的,人们会离开或加入组织,而你今天的决定会在很大程度影响新员工入职曲线的陡峭程度。这就是这次谈话的“可维护性”部分。
还有一点:如果面试我的人对我用另一个 RabbitMQ 进行网络调用而无动于衷,并且,也没有让我来阐述选择每一个设计的理由,那么我想知道这家公司对可维护性的要求该有多苛刻。我想知道那些随时待命的开发人员做得怎么样,是否需要给他们一个拥抱鼓励安慰下。
“靠近底层”的级别
我每天大概会做出 5~10 个这样的决定。这些关于性能的小决定通常是用几行代码表示的,将来相对容易改变:我只需打开一个新的 PR,或者,如果可能会产生级联影响的话,可以编写一个 RFC 并获得一些反馈,然后进行更改。从了解问题、提出解决方案到实施解决方案之间的时间大约是几天或几周,也可能是几小时或几分钟。
例如,如果我想出一个优化 SQL 查找的好方法,也许是通过添加一些额外的索引,添加一个子查询,也许是进行一些巧妙的分页,那么我就可以节省几毫秒的响应时间。但是,如果我的代码包含了一个 Bug,当它在生产环境中出现时,而我却不在待命状态,那该怎么办?不仅要知道如何使代码更快,还要质疑聪明代码的可维护性,并找到一些认知上的债务缓解策略(比如:额外的单元测试,包含命名良好的变量和清晰的描述,额外的跟踪问题中的文档,描述性的提交信息等等),这才是高级职位的基线。我还希望强大的中级开发人员能够合理地做出这些决策。
“500 英尺以上”的级别
同样的原则也适用于系统级变更中的更大的决策或投资。但是,与可以删除的 SQL 查询不同,选择添加新组件,或者迁移到新框架、语言或托管平台,以后更改起来就要困难得多。我已经提到过,你不能在没有正常理由的情况下就轻率地将 RabbitMQ 扔到架构图的中间。作为这个理由的一部分,有必要讨论一下你将达到的抽象层规模,这个抽象层在本质上是系统级的。
要继续使用这个 RabbitMQ 示例,你还可以进一步讨论从业务角度来看,为什么围绕消息发送引入更强的保证是有意义的。你是否需要审核通过 Web 表单发送的请求的能力?是否因为下游某处的已知稳定性的问题而重试失败的发送?我认为,表现出对业务需求的深入理解(如果没有,那就问问题,直到你确实理解为止),当以后更改成本较高时,他们可以相信你能够始终如一地做出慎重的、明智地选择。
4. 了解系统可见性和故障排除的基本支持
这已经比我原本打算要写的内容多了几千字,因此,我将简单地讲讲如何监控系统。系统健康是一个非常全面的概念,特别是当你将“系统”的定义扩展到包括人类时,因此对于一个假设的系统,要求你当场构建,是很难制定出一套标准的指导方针来说明应该监控哪些方面。但是有几件事人们通常还是会说的。
首先,你的核心业务价值存在于你的应用程序中,因此,从监控可能会拖垮你的应用程序的事情开始:过多的内存使用、过多的 CPU 使用、非 200 状态码问题、数据库响应过慢、应用程序级错误率。《SRE 手册》中的“监控”一章很好地解释了要跟踪什么,以及为什么要跟踪。
其次,我会发表一个简短的演讲,说明如果开发人员在仪表板上的一条线越过图中可怕的红色部分时不能立即知道该怎么做时,那么监控任何东西都是没有用处的。所有的警报,无论是在工作时间内还是在下班时间,都需要具有可操作性,并且该操作需要立即显而易见,最好是作为警报通知的消息正文的一部分。而这通常需要一些培训,在面试时,你不一定非要自愿去做。有专门的咨询公司可以进行这种培训。
关于 SLO 的热议话题
如果搁在一两年前,你被问到 SLO 和 SLA 之类的问题,我会说:“我们应该争取尽可能多的 9。”但现在,我的感觉已经变了:你的正常运行时间要求应与产品经理、设计室和支持团队密切合作来确定,因为他们可能对客户的需求有更深入的了解。即使在一个公司内部,SLO 在团队之间也可能有所不同:并非所有团队都在关键路径上工作。后退一步也是可以的,我认为通过承认正常运行时间要求是由客户满意度决定的,而不是反过来说,稍微回避一下 SLO 的问题,可能会给人留下深刻的印象。
无论如何,我希望本文对你有用。我主要是为《我,但在 2018 年》(me, but in 2018)写的。我已经尽力将我希望当时知道的单词都包括在内了。希望这能帮助你找到正确的方向,开始在 Google 上进行搜索,并更深入地挖掘一些概念。我希望你会发现,像我一样,系统设计和学习分布式系统,可以是一个真正有趣、富有挑战性的追求。
作者介绍:
Denise Yu,华裔,女性软件工程师,现居加拿大多伦多,专注于 GitHub 社区与安全。女权主义者,爱猫人士,喜欢涂鸦和创作漫画作品,有自己的绘画工作室。
原文链接:
https://deniseyu.io/2020/05/21/talking-about-software-at-scale.html
评论