# 21 | 高性能设计,一切都围绕着契约精神 你好,我是乔新亮。这一讲,我们来聊聊如何实现架构的高性能设计。 前面我们讲过,产品思维有两个核心关键词:“契约精神”和“洞察人性”。其实高性能设计,也和契约精神是密切相关的。我将其总结为:高性能设计,一切围绕着契约精神。 你可能会想,高性能设计不就是可以支撑大流量、高并发的架构设计吗?和契约精神又有什么关系? 其实关系很大。所谓的高性能,实际上,是与业务强相关的。比如,对于一台网络游戏服务器来说,可以支撑 400 名玩家同时在线,就算高性能;对于一台流媒体服务器来说,可以支撑 10,000 用户同时在线观看,就算高性能。 当然,数据可能不准确,但逻辑大体如此。在实际的业务场景中,还会有许多其他技术指标被引入。比如游戏需要关注连接稳定性、视频需要关注延时,等等。 明确了性能和业务的关系,下面我们就来聊聊,如何进行高性能设计,也借此加深对契约精神的认知和理解。 ## 契约精神是高性能架构设计的“拦路虎” 首先,我们要清楚,高性能设计可以大体拆解为两大步骤: 1. 清晰描述、定义性能目标; 2. 设计并实现这个目标。 而高性能架构的设计目标,是通过三类指标来具体定义的,分别是: 1. 系统响应时间,一般指服务/交易处理的时间,也包括网络响应时间; 2. 系统吞吐量,一般指系统的每秒交易量,通常需要结合并发量共同描述; 3. 系统容量,通常代表系统的可用资源数量,包括硬盘容量、网络带宽等。 但要注意:虽然我们有三类高性能指标,但彼此不能孤立地看待,否则就会出现问题。比如,在定义系统响应时间目标时,如果不与系统吞吐量关联起来,就会变得缺乏实践意义 —— 一旦流量的压力高峰到来,系统往往就不遂人愿了。 这意味着,我们要学会识别架构设计中的组件(或服务),首先弄清哪些组件需要明确高性能指标,再针对性地用以上三个指标做清晰定义。 除此之外,对于系统响应时间,我们还有一个更直观的监控指标,叫做 Top 百分数 (Top Percentile),简称 TP。比如, TP 90 = 2s,意思是,90% 的请求的响应时间都在 2s 内;那么 TP 99 = 2s 的意思就是 99% 的请求的响应时间都在 2s 内;我们一般说的 RT = 2s,指的是平均响应时间,可以作为参考,但实际意义可能并不大。我个人一般会重点看 TP 90 和 TP 99 的数据。 当我们明确了 TP 90 或者 TP 99 这一有关系统响应时间的指标时,一个交付给用户的“契约”就出现了。守好诸多类似的契约,也就是保证了高性能设计。但学习需要适当地进行延展思考,比如此时,我们应该多问自己一句:这个契约意味着什么? 对于没有经验的程序员或架构师来说,这里其实存在一个隐含的假设,某种意义上,也算是一种侥幸心理:如果该系统在业务流量低峰场景下,满足了响应时间等性能指标,也就意味着,该系统在任何场景下,都能满足这些指标。 你可能会觉得有点绕, 那我换个说法来描述刚刚签订的高性能“契约”:本系统、组件、服务承诺在任何情况、任何资源、任何压力高峰下,都保证 TP 90 = 2s 。 是不是立刻就有了不一样的感觉?好像契约发生了变化,光是读出来,就让人心里一抖。 其实到此时,还谈不上契约是否发生了变化,因为打从一开始,这个契约就是不明确、不清晰、不具体的。 注意,这里还没有谈架构设计和研发能力,问题就已经出现了。很多时候,我们会被工作中出现的一些实际问题所困扰,其根源往往不在于相关责任人缺乏设计能力,而在于签订的“契约”并不明确。团队不可能实现和保卫一个“空中花园”。 所以,我总说,高性能设计,一切围绕着契约精神。所以接下来,我们的内容都将围绕着“如何将契约描述清楚”这件事来展开。 ## 有上限的“契约”才能交付 我曾经培养过许多架构师。一开始在做高性能设计时,他们常常会这样描述给用户的“契约”: “该架构最高支持 200 万并发流量,TP 90 = 2s 。” 在不考虑业务需求的情况下,这个契约看似是没问题的。这意味着:对于第 1 名连接服务器的用户来说,TP 90 = 2s ;但对于第 300 万名连接服务器的用户来说,契约又是什么呢?答案是,没有,或者说比较模糊。 **如果目标不清晰,隐患就埋下了。高性能设计非常看重系统响应时间的一致性,尤其是在不同的服务阶段,并发量和吞吐量发生变化的时候。** 对于管理者或架构师而言,理想的契约应该是“保证设计流量内的用户 TP 90 = 2s,超出并发限制的用户,则暂时不在契约承诺的范围内。”。 这就要求我们通过四大步骤去思考所谓高性能架构的设计问题: 第一步,当服务器不超过 200 万并发用户时,TP 90 = 2s 。 第二步,当连接服务器的并发用户数量超过 200 万,达到 250 万时,保证有 200 万用户的 TP 90 = 2s ,拒绝其他用户的连接请求。 第三步,为设计容量外的用户提供服务器排队机制,并附带具体、明确的系统提示。 第四步,3 分钟内完成扩容,保证并发用户数量小于等于 250 万时,任何在线用户的 TP 90 = 2s。 其实,这样才构成了一个可以履行的契约:无论流量波动如何,该架构始终支持 200 万用户的 TP 90 = 2s;高峰时期,其他用户会被拒绝访问或进行排队,但系统能在三分钟内完成扩容,支持 250 万并发在线用户,并且保证其中任何一个用户的 TP 90 = 2s 。 在某种程度上,这也是对业务诉求的精确表达,对于业务发展来说至关重要。很多同学设计的架构和契约,压根没有控制上限。他们想当然地认为,流量不可能出现太大的变化或波动。对于用户来说,这无异于一张“空头支票”。 所以说,要做高性能设计,一定要明确目标,并交付给用户;没有清晰的目标,也就没有针对性的设计,怎么能实现高性能的架构呢? 目标清晰后,我们再来看看,高性能架构如何设计并落地。 ## 实现高性能架构的关键技术点 落地也不难,有三项工作最为关键,分别是: 1. 为架构做好“保护系统”; 2. 使架构具备扩容能力; 3. 提升系统各组件处理能力。 **保护系统,是为应对容量规划外的过载而设计的,通过流量控制来具体实现。** 所谓流量控制,就是当实际并发压力,超过设计性能的时候,人为阻断服务器连接,告知用户需要排队或“稍后再试”。 经常玩游戏的同学可能知道,对于网络游戏来说,流量控制是个基础设计,基本上所有的服务器都有“排队机制”。 流量控制有两种具体的实践,一种是面向连接数做控制,一种是面向用户数做控制。前者让用户在不断尝试连接时,有一定成功的可能;后者则保证用户对系统的期望是一致的 —— 要么可以登录、要么不能登录。具体应该选择哪种方式,取决于业务的实际诉求。 **而对于扩容能力来说,一般要包括储备额外的计算资源和具备快速弹性扩容能力。** 你可能会说,既然已经有计算资源了,为什么不提前进行扩容呢? 工作经验比较丰富的同学可能会知道其中的“隐情”:一般来说,该做的系统扩容,通常很早就完成了,架构设计真正缺乏的是应急手段。毕竟,你不能“不分青红皂白”,将每个系统的应急扩容都提前做好。一个经营稳健的企业,是不会允许这样的开销出现的。 另一个关键要素在于扩容速度。扩容工作是需要一小时、一分钟,还是一秒钟完成,其差别非常之大。“天下武功,唯快不破”,就算架构设计做得不好,如果响应够快,很多情况下,也是能解决问题的。 无论是利用公有云扩容,还是私有云扩容,目标都应该是一致的。想象一下这样的场景: 双十一来了,你通过监控平台,看到在线用户数量快速增长,很快突破了 200 万,流量控制机制开始生效,新用户进入排队序列。你考虑了 3 秒钟,然后做了一个决定:系统需要扩容。于是,你轻轻地输入一个数字,按下回车。3 秒钟后,排队序列消失了。你对旁边的同学说,继续监控吧!然后悠闲地端起茶杯,离开了座位。 这将是一种多么美妙的工作状态?更棒的是,以上场景完全有可能实现。 至此,我们聊过了契约,也聊过了应急处理,也就意味着,我们把握住了高性能设计的“头”和“尾”。最后,我们再简单谈一下“中间”部分,也就是对系统各组件处理能力的提升。 在高性能设计中,对于每个组件/服务都要确定目标,进行设计,然后进行评审和测试,确保满足性能需求。对于架构负责人来说,性能设计一定要尽早开始,具体工作内容包括: * 需求早期收集 * 容量分析 * 估算与建模 * 技术研究 * 设计、开发、跟踪 * 测试计划执行 * 风险与绩效管理 * 实时监控与容量管理 工作内容和流程可能有些多,但这更多属于标准化的研发管理流程,这里列出来仅供参考,实际执行的时候,需要参考具体的业务和企业情况做调整。 **在这中间,有一点需要额外注意,我们称之为“对系统资源的争抢问题”。** 对于一个组件或服务,并发压力增大,响应时间却没有变化,意味着在请求的处理过程中,没有发生对资源的争抢和排队。同理,当响应时间随着并发压力的增大而变长时,一般都意味着这些请求引起了对系统资源的争抢。 **对于无状态的服务,理论上可以通过集群扩容,无限增加系统的并发处理能力,简单、直接地解决问题;** **但对于有状态的数据服务而言,比如缓存或数据访问,就要考虑资源争抢问题,并针对性地设计、处理**。 所以,高性能架构在设计落地时,一个重要的任务,就是去发现那些可能出现资源争抢的模块,并一一进行隔离。 谈到“隔离”,在架构维度有两种方案,一种是在应用层进行隔离,也就是说,在业务功能层面就开始隔离;一种是基础软件层面进行隔离,比如与数据库相关的“读写分离”概念,就是在基础软件层面进行隔离。 对应到具体的实施方法上,有三种主要的“隔离”技巧,你可以多做了解: 1. 缓存机制,适用于部分场景,可以解决数据库资源的争抢问题; 2. EDA 架构,适用于处理时间较长的代码逻辑,需要提前区分哪些模块可以做同步处理,哪些模块可以做异步处理; 3. 提前进行预处理,以空间换时间,这也是一种卓有成效的处理手段。 当然了,方法还有很多,听完这节课后,你可以单独再做了解,并将了解到的方法,在评论区分享给大家。 做好隔离后,我们也要注意提升架构的可扩展性,具体方法可以参考[《架构设计,专业分工和协作精神的体现》](https://time.geekbang.org/column/article/317135),这里不再重复。 至此,关于高性能设计的三个最重要的步骤就讲完了,我们总结一下,分别是: 1. 为架构做好“保护系统”:做好系统的流量控制; 2. 使架构具备扩容能力:储备额外的计算资源,提升弹性扩容的速度; 3. 提升系统各组件处理能力:识别高并发情境中的资源争抢情况,同时注意保留架构的可扩展性。 最后,我们也要对测试工作保持关注。可能你在 PRE 环境做过压测,但后来发现,实际的业务复杂度是远远超过想象的。 针对这种问题,行业内的解决方案更偏向“全链路生产压测”,说白了,就是在生产环境准备数据、进行压测。如果一个系统通过了全链路压测,那就可以基本确认没有性能问题了。 ## 结语 从我的角度来看,**技术行业发展到今天,基本不存在太多的技术挑战了。** 如果你能将业务问题抽象为一个技术问题,那么不管是寻求同事的帮助,还是 Google、看书、到知识平台付费学习,都能解决你的疑惑。 所以,在理清高性能架构设计的整体思路后,困惑、焦虑,应该从你的认知中消失。具体到数据库设计、缓存设计、队列使用等技术细节,我认为都不是问题。关键之处,是要守住契约,按照我们今天所讲的三大步骤尝试落地。 过去的几年间,我曾在多个企业内建设高性能的 IT 系统架构,很好地服务了用户,使用的就是我们今天所讲的各个方法和思维框架。我个人感觉,过程也是相对轻松的。 需要注意的是,如果时间充足且落实到位,系统确实不会出现高性能问题。可是实际情况是,团队没有时间做完整的高性能设计,没有预算做全链路压测,所以埋下了隐患。 这时,我建议相关负责人要去识别最关键的服务,针对性地进行设计、测试,确保关键服务没有问题,并且,为非关键服务提供降级和熔断处理的开关。 另外一个比较常见的情况,是企业决策层将高性能的设计问题和技术问题混为一谈:看似是技术能力太过欠缺,其实是契约和设计没有做好。**任何复杂问题都可以被拆解为简单问题,只要拆解得足够细致。一定要牢记这一点,这种思维能力是技术人的安身立命之本。** 当然,如果你有问题或想交流的内容,也不要犹豫,立刻在评论区写出来,我会认真回复。 我们下一讲再见。