第七章 简单化

作者:John Lunney, Robert van Gent, Scott Ritchie,Diane Bates and Niall Richard Murphy

一个可正常工作的复杂的系统总是从以前可以正常工作的简单系统演变而来的。

 

——Gall’s Law

简单化是SRE的重要目标,因为它与可靠性密切相关:简单的软件很少出现故障,在故障发生时更容易且迅速地修复。简单的系统更易于理解、维护以及测试。

对于SRE而言,简单化是一个端到端的目标:它应该超越代码本身,延伸到系统架构以及用于管理软件生命周期的工具和流程中。本章探讨了一些样例,这些样例展示了SRE是如何衡量、思考和鼓励简单化的。

 

衡量复杂度

衡量软件系统的复杂度并不是一门绝对的科学。有许多方法可以衡量软件代码的复杂性,大多数是非常客观的。

[注1]如果你想了解更多信息,请阅读近期对软件复杂性趋势的评论,或阅读Horst Zuse,软件复杂性:度量和方法(柏林:Walter de Gruyter,1991)。

最著名且使用最广泛的衡量标准应该是代码圈复杂度,它通过一组特定的语句来衡量不同代码路径的数量。例如,没有循环或条件语句的代码块的圈复杂度(CCN)为1。其实软件社区很擅长测量代码复杂度,并且有许多用于集成开发环境的测量工具(包括Visual Studio,Eclipse和IntelliJ)。我们无法判断所得到的测量复杂度是必然还是偶然的,一种方法的复杂度是如何影响到系统的,以及哪种方法更适合重构。

另一方面,衡量系统复杂性的正式的方法很少见。

[注2] 虽然有一些例子-例如,“关于AWS系统的自动形式推理”。

你可能尝试使用类似CCN的方法来计算不同实体(例如,微服务)的数量以及它们之间可能存在的通信路径。但是,对于大多数较大规模的系统而言,这个数字的增幅十分迅速。

针对系统级复杂度,有一些更实用的替代度量方法:

 

训练时长

新成员多久能参与on-call工作?糟糕的或缺失的文档可能是主观复杂性的重要来源。

 

解释时长

向团队新成员解释服务的全面高级视图需要多久(例如,在白板上绘制系统架构图并解释各个组件的功能和依赖关系)?

 

管理多样性

有多少种方法可以在系统的不同部分配置类似的设置?配置是集中存储在一个位置还是存储在多个位置?

 

部署配置的多样性

生产过程中部署了多少唯一的配置(包括二进制文件、二进制版本、标志和环境)?

 

年龄

系统使用多久了?Hyrum定律指出,随着时间的推移,API的用户依赖于它实现的每个方面,导致了脆弱和不可预测的行为。

虽然测量复杂度有时是有价值的,但过程很困难。然而以下这些结论是没有争议的:

  • 一般而言,除非付出努力补偿,否则现存的软件系统的复杂度将会随时间增加。

  • 付出这样的努力是值得的。

 

简单化是端到端的,并且SRE会获益于此

通常,生产系统不是通过整体的方式设计的;相反,它们是有机地生长。随着团队添加新特性和推出新产品,它们会逐渐积累组件和连接。虽然单个变更可能相对简单,但每个变更都会影响周围的组件。因此,整体复杂度很快就会超出控制。例如,在一个组件中添加重试可能会使数据库过载并使整个系统不稳定,或者使对给定查询在系统中遵循的路径进行推理变得更加困难。

一般而言,复杂度带来的成本并不直接影响引入它的个人、团队或从经济角度来看的任何角色,复杂度是一种外部特性。相反,复杂性会影响那些继续在其中和周围工作的人。因此,有个拥护端到端系统简单化的支持者十分重要。

SRE非常适合这个角色,因为他们的工作需要他们将系统作为一个整体来对待

[注3] 因此,对于那些把复杂度当做技术债务来进行消除的产品开发主管而言,对SRE的投资是有价值的,然而这项工作的合理性很难在现有的团队范围内得到量化。

。除了维护自己的服务,SRE还必须深入了解与服务有交互的系统。Google的产品开发团队通常无法查看生产范围内的问题,因此他们可以通过咨询SRE来获取系统设计和运营的相关建议。

(一般说明)读者操作:在工程师第一次加入on-call工作之前,鼓励他们绘制(或重绘)系统架构图。可以在你的文档中保留一组规范的图表:不仅对新加入的工程师非常有帮助,还可以帮助更多有经验的工程师随时跟上系统的变更。

根据我们的经验,通常产品开发人员的工作局限在子系统或组件中。因此,他们没有形成针对整个系统的思维模式,所在的团队也没有制作系统级别的架构图。系统架构图的价值在于可以将系统交互可视化地呈现给成员,并且帮助成员使用常用词汇来阐明问题。通常,SRE团队都绘制了所有服务的系统级架构图。

(一般说明)读者操作:SRE要检查所有重要的设计文档,且团队文档中需要说明新设计会如何影响系统结构。如果一个设计会增加系统复杂度,SRE可能会建议选择降低系统复杂度的替代方案。

 

案例学习1:端到端API简单化

背景

之前章节的一位作者在一家使用键/值包数据结构的核心库的初创公司工作。RPCs(远程过程调用)取一个包并返回一个包;实际参数作为键/值对存储在包中。核心库支持包的常见操作,比如序列化、加密和日志记录。看起来所有的核心库和API都非常简单灵活,对吧?

遗憾的是,答案是否定的:核心库的客户最终为核心API的抽象化付出了代价。每个服务都需要仔细记录键和值(和值类型)的集合,但通常做法并非如此。此外,随着时间的推移、参数的添加、删除或更改,维护向后/向前的兼容性变得很困难。

经验教训

类似Google Protocol Buffers或Apache Thrift这样的结构化数据类型看起来可能比它们抽象的通用替代方案更复杂。但是由于它们强制预先设计方案和准备文档,获得了更简单的端到端解决方案。

 

案例学习2:项目生命周期复杂度

当您查看现有系统,发现它像一团乱麻,您可能希望用一个新的、干净的、简单的系统取而代之,且这个简单的系统能解决相同的问题。不幸的是,在保持现有系统的同时创建新系统的成本可能超乎您的预期。

背景

Borg是Google的内部容器管理系统。运行了大量Linux容器且具有多种使用模式:批处理与生产,管道与服务器等。多年来,随着硬件的变化,功能的增加以及规模的不断扩大,Borg及其周边生态系统在不断的发展壮大。

Omega旨在成为一个更合理,更清爽的Borg版本,且能支持相同的功能。然而,从Borg到Omega的转变过程产生了一些严重的问题:

  • Omega发展的同时,Borg的发展也没有停滞,因此Omega一直在追逐一个变化的目标。

  • 事实证明,前期对改善Borg难度的估计太过悲观,而对Omega的期望太过乐观(实际上,外国的月亮未必更圆)。

  • 我们对从Borg迁移到Omega的困难没有了然于胸。数百万行配置代码跨越数千个服务和多个SRE团队,这意味着迁移工作在工程和时间维度上成本都是极高的。可能需要数年时间完成迁移,在这期间,我们必须同时支持和维护这两个系统。

我们决定做什么

最后,我们提供了一些在设计Omega回归Borg时出现的想法。我们还使用了很多Omega的概念来启动Kubernetes,一个开源的容器管理系统。

经验教训

在考虑重写时,要考虑整个项目生命周期,包括对移动的目标的开发,完整的迁移计划以及在迁移时间窗口内可能产生的额外成本。具有大量用户的APIs很难迁移。在您投入了相应的努力之前,不要想当然的将预期结果与当前系统进行比较。在确保已经衡量了成本和收益以及没有低估成本的前提下,有时重写是最好的前进方式。

 

重获简单化

大多数的简化工作是从系统中删除元素。简化工作有时很直接(例如,消除对从远程系统获取的未使用数据的依赖)。简化工作有时需要重新设计。例如,系统的两个部分需要访问相同的远程数据。一个更简单的系统可能只需要获取一次数据并转发结果而非获取两次。

无论什么工作,领导层必须确保优先考虑简化工作。这里的简化指的是效率-而不是节省计算或网络资源,它节省了工程时间和认知负荷。项目成功的简化就如同成功启用了一个有价值的功能,就如同成功的度量并对代码进行了增删

[注4] 正如Dijkstra所言,“如果我们希望计算代码行数,我们不应该将它们视为‘生产线’,而应视其为‘线条花费’。”

。例如,Google的内部网络会为删除大量代码的工程师显示“Zombie Code Slayer”徽章。

简化是一项功能。您需要明确优先级并给出待简化的项目,同时为SRE预留时间。如果产品开发和SRE人员发现待简化项目对他们的工作没有益处,他们就不会承担这些项目。对于特别复杂的系统或过载的团队而言,可以将简单化作为明确的目标,安排一个独立的时间来完成这项工作。例如,为“简单化”项目保留10%的工程项目时间。

[注5] 为简化项目预留部分时间(例如10%)并不意味着为团队打开了引入其他90%复杂度的绿灯。仅仅意味着您在为简化特定的目标付出一些努力。

(一般说明)读者行动:让工程师集体讨论系统中已知的复杂度,并讨论如何简化。

随着系统复杂度的增加,SRE团队存在分裂的趋势,每个新的团队分别集中运维系统的某一部分。这样的操作有时是必要的,但新团队规模的缩小可能会降低他们推动较大简化项目的动力或能力。可以考虑指定一个小的轮转的SRE团队来维护整个堆栈的工作信息(可能比较浅显),推动整个堆栈的整合和简化。

如前所述,绘制系统图表的行为可以帮助您理解系统并预测其行为。例如,在绘制系统图表的过程中,您可能需要查找以下内容:

放大

当一个调用操作返回一个错误或超时,且在几个级别上进行重试时,会导致RPC的总数相乘。

循环依赖

当组件依赖于自身(通常是间接的)时,系统完整性可能会严重受损-整个系统可能无法进行冷启动。

 

案例学习3:简化广告网络的展示

背景

Google的广告展示业务有许多关联产品,其中包括一些收购于DoubleClick,AdMob,Invite Media等公司的产品。这些产品必须适用于Google基础架构和现有产品。例如,我们希望使用DFP广告管理系统的网站展示Google AdSense筛选的广告,也希望使用Double Click Bid Manager进行投标时可以通过访问Google Ad Exchange进行实时竞价。

独立开发的产品形成了难以推理的互连后端系统,很难观察流量在各组件的流通情况,因此不便且无法精确的为每个产品配置合适的容量。为了确保删除了查询流量中的所有无限循环,我们在其中添加了测试。

我们决定做什么

Ads的运维团队自然而然会推动标准化:虽然产品的每个组件都有特定的开发团队,但SRE是服务于整个系统的。我们的首要任务是制订统一的标准,与开发团队合作逐步采用这个标准。这些标准是:

  • 建立一种复制大规模数据集的方法

  • 建立一种执行外部数据查找的方法

  • 提供用于监控、配置、组态的通用模板

在此之前,需要为每个产品单独提供前端和拍卖功能。如图7-1所示,当广告请求可能到达两个系统时,需要重写请求以符合第二个系统的要求。过程中,增加了额外的代码和处理,还加大了非预期循环的可能。

图7-1:之前,广告请求可能会同时触及AdMob和AdSense系统

为了简化系统,我们为满足所有用例的常用程序增加了逻辑,并且添加了用于保护程序的标志。随着时间的推移,我们删除了标志,将功能整合到较少的的服务器后端中。

当服务器统一时,拍卖服务器可直接与两个目标服务器通信。如图7-2所示,当多个目标服务器需要查找数据时,查询只需统一在拍卖服务器中进行一次。

图7-2:统一后的拍卖服务器只需执行一次数据查询

经验教训

最好将已经在运行的系统逐步集成到你的基础架构中。

正如在单个程序中存在相似的函数表示“代码气味”来反应更深层次的设计问题一样,单个请求的冗余查询表示“系统气味”。

当你通过SRE和开发人员的支持建立了有明确定义的标准时,你可以提供更清晰的蓝图以便管理者对高复杂度的系统更认可和鼓励。

 

案例学习4:在共享平台上运行数百个微服务

by Mike Curtis

背景

在过去15年,Google成功开发了多个垂直类产品(搜索、广告和Gmail,仅举几例),并源源不断的产生了新的重构的系统。其中很多系统都有专门的SRE团队和与之对应的特定领域生产堆栈,包括定制化开发工作流程,持续集成和持续交付(CI/CD)软件周期以及监控。生产堆栈的定制化带来了巨大的维护、开发以及新的SRE成员工作的成本。此外也为团队之间轮转服务(或工程师!)以及新增服务带来困难。

 

我们决定做什么

负责社交网络领域的一组SRE团队致力于将其服务的生产堆栈融合到一个托管的微服务平台中,由一个SRE团队管理。共享平台是到目前为止的最佳实践,平台会绑定并自动配置一些之前并未充分利用的功能,这些功能可以提高可靠性且便于调试。无论该SRE团队有多熟悉所负责的服务,新增的服务都必须使用通用平台,而旧式服务必须迁移到新平台或逐步被淘汰。

共享平台在社交网络领域取得成功后,谷歌的其他SRE团队和非SRE团队也开始使用它。

 

设计

要知道,单个整体服务的变化是缓慢的,使用微服务可以迅速更新和部署功能。微服务实现自我管理,而非托管:团队可以有效的管理他们所负责的服务,无需委托个别团队管理和负责。微服务为每个团队提供工作流程工具用于发布、监控等功能。

微服务提供的工具包括UI,API和SRE以及开发人员常用的命令行交互界面。即使这些工具可能涉及许多底层系统,开发人员的体验感却是统一的。

 

成果

微服务平台的高质量和功能集成带来了意想不到的好处:开发人员团队可以运行数百项服务,而无需任何SRE的深入参与。

通用平台还改变了SRE和开发人员的关系。Google的SRE团队开始分层的参与到工作中,从咨询和设计审查到深度参与(即SRE承担on-call职责)。

 

经验教训

从稀疏的或不明确的标准转变为高度标准化的平台是一个长期项目。每个步骤可能都让人觉得是增量式的,但最终,这些步骤可以减少开销并使大规模运行服务成为可能。

这种转变可以让开发人员看到价值所在。即不要尝试说服人们执行一个只在全部完成后才得到回报的巨大重构工程,而是在每个开发阶段解锁增量生产力。

 

案例研究5:pDNS不再取决于自身

 

背景

当Google生产的客户要查询服务的IP地址时,通常使用名为Svelte的查找服务。过去,为了找到Svelte的IP地址,客户端使用了名为pDNS(生产DNS)的Google命名服务。通过负载均衡访问pDNS服务,负载均衡使用Svelte查找实际pDNS服务器的IP地址。

 

问题描述

pDNS对自身具有传递依赖性,某种程度上说这是无意中引入的,后来被明确为是可靠性问题。由于pDNS服务可复制,且在生产中的始终可以获得打破依赖关系循环所需的数据,因此查找通常不会遇到问题。然而,冷启动是无法做到的。借用一位SRE的话来说,“我们就像穴居人,只能依赖现有篝火来点火。”

 

我们决定做什么

我们修改了Google生产中的低级组件以便为所有Google生产机器的本地存储附近的Svelte服务器维护当前IP地址列表。除了打破前文所述的循环依赖之外,此举还消除了对大多数其他Google服务的pDNS的隐式依赖。

为了避免此类问题,我们还引入了一种方法,将允许与pDNS通信的服务集列入白名单,并慢慢减少该集合。因此,生产中每个服务的查找都通过系统且具有更简单更可靠的路径。

 

经验教训

注意服务的依赖关系 - 使用明确的白名单以防止意外添加。另外,需要注意循环依赖。

 

结论

通常简单的系统往往是可靠的且易于运行的,因此简单化自然而然就是SRE的目标。很难定量衡量分布式系统的简单性(或取逆,即复杂度),但可以挑选和改进合理的替代测量方案。

SRE对系统有着端到端的理解,在识别,预防和修复复杂度来源方具有优势,在软件设计,系统架构,配置,部署过程或是其他地方,SRE都应该参与设计讨论,提供对成本和效益的独特见解,尤其是简单化。SRE还可以主动制订标准来使生产统一化。

作为SRE,追求简单化应该是工作的重点内容。我们强烈建议SRE领导层授予SRE团队权利和奖励来推动简单化。系统在不断发展的过程中会不可避免的越来越复杂,因此追求简单化的斗争道路需要持久的关注和付出-但这份追求是值得的。

猜你喜欢

转载自blog.csdn.net/weixin_43947499/article/details/84941753