读书笔记《发布!设计与部署稳定的分布式系统》

英文版原名:Release It! Design and Depoly Producation-Ready Software

不太习惯这本书的翻译,读起来令人略感不适,:(

总结:
这本书比较全面的介绍了建设稳定系统的反模式与模式,涵盖了软件系统开发的方方面面,当读到一些例子时能让人联想到工作中遇到的一些故障案例。这些模式与反模式往往是我们在进行系统的设计中容易忽略的,我们可能更关注了功能性设计而忽略了一些影响系统稳定性的功能设计。架构设计除了考虑系统开发过程的成本,也应该考虑后期的运维成本,通过全面的评估来进行设计的决策,而不是仅仅关注开发而导致后期维护的痛苦。

以下为摘抄及读书笔记,主要为反模式与模式相关部分。

第1章 生产环境的生存法则

  1. 瞄准正确的目标:系统的设计和构建目的不应该仅仅是为了通过QA(quality assurance)的测试,而是应该“为生产环境而设计”,并满足能以低成本、高质量的方式进行运维工作。
  2. 应对不断扩大的挑战范围:由于系统用户的规模和范围越来越大,对系统正常运行的时间要求也提高了。
  3. 重视运维成本:如果不取消或废止系统,在系统的整个生命周期中,运维时间远远超过开发时间。如果在做成本决策时忽视了运维成本,那这为了节省一次性的开发成本,却耗费无尽的运维成本。而创建不停机发布的构建流水线和部署过程将可以避免停机发布(按系统使用5年且每月发布停机5分钟,那系统停机时间共计300分钟,乘以系统的收入则可以计算出停机发布造成的损失)带来的损失,而且大有可能提供系统部署频率,以便于占领更多的市场份额。
  4. 重视早期决策对系统的长期影响:虽然敏捷开发强调应该尽快交付和渐进式改进以便于软件能快速上线,但即使在敏捷项目中,也最好要有远见的制定系统架构设计的决策。虽然不同的设计方案通常具有相近的实施成本,但这些方案在整个软件生命周期中的总成本截然不同。因此,考虑每个方案对系统可用性、系统容量和灵活性的影响至关重要。
  5. 设计务实的架构:务实的架构设计应该综合考虑软件、硬件、用户三者之间的关系,并充分考虑系统在后续的运维中的问题,包括部署、监控等等。

第一部分

第2章 案例研究:让航空公司停飞的代码异常

本章节分享了一个实际的故障案例的发现、处理过程、事后分析,最后找到的原因竟然是一个未被捕获的 SQLException 异常。

虽然当我们找到问题的原因之后可以比较方便的重现这个缺陷,但是如果按常规的测试用例有办法全方位的避免这样的问题吗?很显然,我们没办法把每一个这样的缺陷都找出来。而我们更应该关注的是系统中的一个缺陷可能会传播到所有的其他相关联或者相互依赖的系统中,所以我们需要探讨防止这类问题蔓延的设计模式。

第3章 让系统稳定运行

当构建系统的架构、设计甚至底层实现时,许多决策点对系统的最终稳定性具有很大的影响力。而面对这些决策点,高度稳定的设计与不稳定的设计投入的成本通常是相同的。

  1. 什么是稳定性:
    1. 事务:是系统处理的抽象工作单元,这与数据库事务不同,一个工作单元可能包含许多数据库事务、外部系统集成等。一个系统可以是只能处理一种事务的专用系统,也可以是能处理不同种类事务的组合的混合工作负载。
    2. 系统:是指为用户处理事务所需的一套完备且相互依赖的硬件、应用程序和服务。
    3. 稳定性:即使在瞬时冲击(对系统快速施加大量的访问流量)、持续压力(长时间持续地对系统施加访问流量)或正常处理工作被失效的组件破坏的情况下,稳健的系统也能够持续的处理事务。这不仅仅是指服务器或应用程序仍能保持运行,更多的是指用户仍然可以完成工作。
    4. 寿命长的系统会长时间处理事务,“长时间”是指两次代码部署的间隔时间。
  2. 系统寿命测试(书上是“延长系统寿命”,?)。威胁系统寿命的主要敌人是内存泄漏和数据增长。可以通过负载测试工具(JMeter、Marathon或其他)持续向系统发送请求来模拟长时间运行的场景以便于测试系统寿命。(也可以每天有几个小时不怎么向系统发送请求来模拟半夜低峰时段)
  3. 系统失效方式
    1. 突发的压力和过度的压力
    2. 我们需要接受“系统必然失效”这一事实,然后针对性进行相应的设计。
  4. 系统失效链
    1. 术语:
      1. 失误:软件出现内部错误。出现失误的原因既可能是潜在的软件缺陷,也可能是在边界或外部接口处发生的不受控制的状况。
      2. 错误:明显的错误行为。
      3. 失效:系统不再响应。
    2. 失误一旦被触发,就会产生裂纹。失误会变成错误,错误会引发失效。这就是裂纹的蔓延方式。
    3. 共识:第一,失误总会发生,且永远无法杜绝,必须防止失误转变为错误;第二,即使在尽力防止系统出现失效和错误时,也必须决定承担失效或错误的风险是否利大于弊。

第4章 稳定性的反模式

一、集成点

集成点是系统的头号杀手。每一个传入的连接都存在稳定性风险。每个套接字、进程、管道或RPC都会停止响应。即使是对数据库的调用,也可能会以明显而微妙的方式停止响应。系统收到的每一份数据,都可能令系统停止响应、崩溃,甚至产生其他的冲击。

  1. 套接字协议
    1. 基于三次握手的连接的问题:a. SYN; b. SYN/ACK; c. ACK
      1. reset拒绝连接
      2. 网络丢包
      3. 因各种原因(比如突发大量连接请求)服务端监听队列满了
    2. read()调用阻塞
  2. (案例)防火墙对空闲连接的处理
    1. 案例中防火墙会自动删除空闲连接导致JDBC连接失效
    2. 解决办法:Oracle数据库增加无效连接检测主动探测有效客户端
  3. HTTP协议
    1. 问题:
      1. 服务提供方可能接受TCP连接,但不会响应HTTP请求
      2. 服务提供方可以接受连接但不能读取请求。如果请求体很大,它可能会填满服务提供方的TCP窗口,导致调用方的TCP缓冲区一直去填充,从而阻塞套接字写操作。在这种情况下,即使发送请求也永远不会完成。
      3. 服务提供方可能返回调用方不知道如何处理的响应状态。如:418 我是茶壶;451 资源被删除
      4. 服务提供方返回的响应的内容类型是调用方不知道如何处理的
      5. 服务提供方可能声称要发送JSON,但实际发送了纯文本,或者是二进制文件,或者其他格式文件
    2. 解决办法:
      1. 客户端对超时时间进行细粒度控制(包括连接超时时间和读取超时时间),并能对响应进行处理
      2. 应该先确认响应内容是否符合预期再处理响应内容,而不是直接把响应映射成对象
  4. 供应商的API程序库
    1. 警惕供应商的API程序库的稳定性,包括质量、风格和安全性等方面的不稳定性。---- 注 (现身说法):我今年2月份就因为国内某大厂提供的sdk的默认超时时间设置踩过坑。
    2. 阻塞是影响供应商API程序库稳定性的首要问题,包括内部资源池、套接字读取指令、HTTP连接、Java序列化等
    3. 所以我们在使用供应商的API程序库前,最好能够获取到源码或者反编译审视一下里面的实现
  5. 总结
    1. 每个集成点最终都会以某种方式发生系统失效,所以需要为系统失效做好准备
    2. 为各种形式的系统失效做好准备,包括网络错误、语义错误
    3. 由于调试集成点有时比较困难,所以可以考虑通过数据包嗅探器和其他网络诊断工具来排查问题
    4. 如果系统的代码缺乏一定的防御性,集成点失效的影响会迅速蔓延。所以系统代码需要考虑通过断路器、超时、中间件解耦和握手等模式来进行防御性编程,以防止集成点出现问题

二、同层连累反应

这种情况对应于通过负载均衡器实现水平扩展的集群。
当负载均衡组中的一个节点发生故障时,其他节点必须额外分担该节点的负载。

比如一个8个节点的集群,每个节点分担12.5%的负载。
当1个节点故障时剩余的7个节点每个节点需要处理总负载的14.3%,虽然每个节点只增加了1.8%的负载分担,但单节点的负载比之前增加了14.4%。
当2个节点故障时剩余的6个节点每个节点需要处理总负载的16.7%,虽然每个节点只增加了4.2%的负载分担,但单节点的负载比之前增加了33.3%。

  1. 一台服务器的停机会波及其余的服务器
  2. 一台服务器的内存泄漏的停机会导致其他服务器的负载增加,增加的负载又会加速其他服务的内存泄露速度
  3. 一台服务器陷入死锁,其他服务器所增加的负载会导致它们也更容易陷入死锁
  4. (措施)采用自动扩展。应该为云端每个自动扩展组创建健康检查机制。自动扩展将关闭未通过健康状况检查的服务器实例,并启动新的实例。只要自动扩展机制的响应速度比同层连累反应的蔓延速度快,那么系统服务就依然可用。
  5. 利用“舱壁模式”进行保护

三、层叠失效

原因:

  1. 内存泄露
  2. 某个组件超负荷运行
  3. 依赖的数据库失效
  4. 等等

层叠失效有一个将系统失效从一个层级传到另一个层级的机制。

层叠失效通常源于枯竭的资源池。资源池枯竭的原因往往是较低层级所发生的系统失效。没有设置超时时间的集成点,必定会导致层叠失效。

正如集成点是裂纹的头号来源,层叠失效是裂纹的头号加速器。防止发生层叠失效,是保障系统韧性的关键。断路器和超时是克服层叠失效最有效的模式。

总结:

阻止裂纹跨层蔓延。当裂纹从一个系统或层级跳到另一个系统或层级时,会发生层叠失效。这通常是因为集成点没有完善自我防护措施。较低层级中的同层连累反应也可能引发层叠失效。一个系统肯定需要调用其他系统,但当后者失效时,需要确保前者能够保持运转。

仔细检查资源池。层叠失效通常是由枯竭的资源池(例如连接池)所导致的。当任何资源调用都没有响应时,资源就会耗尽。此时获得连接的线程会永远阻塞,其他所有等待连接的线程也被阻塞。安全的资源池,总是会限制线程等待资源检出的时间。

用超时模式和断路器模式实现保护。层叠失效在其他系统已经出现故障之后发生。断路器模式通过避免向已经陷入困境的集成点发出调用请求,进而保护系统。使用超时模式,可以确保对有问题的集成点的调用能及时返回。

四、用户

一、网络流量

  1. 堆内存:如果用户的会话保存在内存中的话,那么每增加一个额外的用户,就会增加更多的内存。

    解决办法:

    1. 尽可能少保留内存中的会话

    2. 使用弱引用(用弱引用完成这些操作。这种做法在不同程序库中的叫法不同,比如在C#中叫System.WeakReference,在Java中叫java.lang.ref.SoftReference,在Python中叫weakref)。其基本思想是,在垃圾收集器需要回收内存之前,弱引用都可以持有另一个对象,后者称为前者的有效载荷。当该对象的引用只剩下软引用[插图]时,则软引用就可以被回收。

      当内存不足时,垃圾收集器可以回收任何弱可达对象[插图]。换句话说,如果对象不存在强引用,那么有效载荷就可以被回收。关于何时回收弱可达对象、回收多少这样的对象,以及可以释放出多少内存,这些都完全由垃圾收集器决定。必须非常仔细地阅读编程语言系统运行时的文档,但通常唯一的保证,是在发生内存不足错误之前,弱可达对象都可以回收。

  2. 堆外内存和主机外内存
    redis、memcache

  3. 服务器上的套接字数量
    一般人可能不太会关注服务器上的套接字数量。但在流量过大时,这也可能会造成限制。每个处于活动状态的请求都对应着一个开放式套接字,操作系统会将进入的连接,分配给代表连接接收端的“临时”端口。如果查看TCP数据包的格式,就能看到端口号长16位,这表示端口号最大只能到65535。不同的操作系统会对临时套接字使用不同的端口范围,但互联网数字分配机构的建议范围是49152~65535。这样一来,服务器最多可以打开16383个连接。但是机器可能用来处理专门的服务,而不是如用户登录这样的通用服务,所以可以将端口范围扩展为1024~65535,这样最多可以有64511个连接。
    然而,一些服务器能够处理100多万个并发连接。有人会把上千万的连接推给单独一台服务器。
    如果只有64511个端口可用于连接,那么一台服务器如何能有100万个连接?秘诀在于虚拟IP地址。操作系统将多个IP地址绑定到同一个网络接口。每个IP地址都有自己的端口号范围,所以要处理上百万个连接,总共只需要16个IP地址
    这不是一个容易解决的问题。应用程序可能需要进行一些更改,从而监听多个IP地址,处理它们之间的连接,并且保证所有监听队列的正常运行。100万个连接也需要很多内核缓冲区。此时需要花一些时间,了解操作系统的TCP调优参数。

  4. 已关闭的套接字
    TIME_WAIT

二、 难伺候的用户

—— 真正下单的用户,这些用户相对而言涉及的事务处理更多更复杂(从下单到结算等等),所以需要关注这些用户相关的操作,因为他们时网站创造收入的来源。

通常零售系统的转化率为2%左右,所以我们可以按4%或者6%或者10%进行系统负载测试。

三、不受欢迎的用户

可能是爬虫之类的程序产生的请求,这些请求可能会对系统产生较大的负载。

解决办法:

  1. 使用技术手段识别并屏蔽这种请求(一些CDN厂商就会提供这种服务),或者在对外的防火墙上执行屏蔽,最好可以让被屏蔽的IP定期过期
  2. 法律手段:为网站写一些使用条款,声明用户仅能以个人或非商业用途查看网站内容。

四、不受欢迎的用户

恶意攻击的用户,即黑客

五、线程阻塞

● 线程阻塞反模式是大多数系统失效的直接原因。应用程序的失效大多与线程阻塞相关。系统失效形式包括常见的系统逐渐变慢和服务器停止响应。线程阻塞反模式会导致同层连累反应和层叠失效。

● 仔细检查资源池。就像层叠失效一样,线程阻塞反模式通常发生在资源池(特别是数据库连接池)周围。数据库内发生死锁以及不当的异常处理方式会造成连接永久丢失。

● 使用经过验证的元操作。学习并使用安全的元操作。形成自己的生产者-消费者队列看似很容易,但事实并非如此。相比新形成的队列系统,任何并发实用程序库都执行了多重测试。

● 使用超时模式进行保护。虽然无法证明代码不会发生死锁,但可以确保死锁不会一直持续下去。避免函数调用中的无限等待,使用需要超时参数的函数版本。即使意味着需要更多的错误处理代码,调用过程中也要始终使用超时模式。

● 小心那些看不到的代码。所有的问题都可能潜伏在第三方代码的阴影中。要非常谨慎,自行测试一下。只要有可能,获取并研究一下第三方代码库的源代码,了解那些出人意料的代码和系统失效方式。出于这个原因,相比闭源程序库,大家可能更喜欢开源程序库。

六、自黑式攻击

自黑式攻击的典型例子,是从公司市场部发出的致“精选用户组”的一份邮件。该邮件包含一些特权或优惠信息,其复制速度比“库娃”木马病毒(或者是岁数稍大的人所熟悉的“莫里斯”蠕虫)快得多。任何限一万名用户享受的特别优惠,都可以吸引数百万的用户。网络比价社区,会以毫秒为单位的速度检测并分享可重复使用的优惠码。

● 保持沟通渠道畅通。自黑式攻击发源于组织内部,人们通过制造自己的“快闪族”行为和流量高峰,加重对系统本身的伤害。这时,可以同时帮助和支持这些营销工作并保护系统,但前提是要知道将会发生的事情。确保没有人发送大量带有深层链接的电子邮件,可以将大量的电子邮件分批陆续发送,分散高峰负载。针对首次点击优惠界面的操作,创建一些静态页面作为“登陆区域”。注意防范URL中嵌入的会话ID。

● 保护共享资源。当流量激增时,编程错误、意外的放大效应和共享资源都会产生风险。注意“搏击俱乐部”软件缺陷,此时前端负载的增加,会导致后端处理量呈指数级增长。

● 快速地重新分配实惠的优惠。任何一个认为能限量发布特惠商品的人,都在自找麻烦。根本就没有限量分配这回事。即使限制了一个超划算的特惠商品可以购买的次数,系统仍然会崩溃。

七、放大效应

生物学中的平方-立方定律解释了为什么永远不会看到像大象一样大的蜘蛛。虫子的重量随着体积增加而增加,符合时间复杂度O(n3)。虫子的腿部力量会随腿的横截面积的增加而增加,符合时间复杂度O(n2)。如果让虫子增大为原来的10倍,那么变大后的虫子的“力量与体重”之比就会变成原来的1/10。此时,虫子的腿根本支撑不了10倍大的个头。

我们总会遇到这样的放大效应。当存在“多对一”或“多对少”的关系时,如果这个关系中一方的规模增大,另一方就会受到放大效应的影响。例如当1台数据库服务器被10台机器调用时,可以很好地运行。但是当把调用它的机器数量再额外添加50台时,数据库服务器就可能会崩溃。

在开发环境中,每个应用程序都只在一台机器上运行。在测试环境中,几乎每个应用程序都只安装在一两台机器上。然而,当到了生产环境时,一些应用程序看起来非常小,另一些应用程序则是中型、大型或超大型的。由于开发环境和测试环境的规模很少会与生产环境一致,因此很难在前两个环境中,看到放大效应跳出来“咬人”。

  1. 点对点通信

    不像开发环境或者测试环境,生产环境的连接的总数,会以实例数量平方的数量级上升。当实例增加到100个时,连接数会扩展到时间复杂度O(n2),这会让开发工程师感到非常痛苦。这是由应用程序实例数量所驱动的乘数效应,虽然根据系统的最终规模,O(n2)的扩展或许没问题,但无论上述哪种情况,在系统进入生产环境之前,开发工程师都应该知道这种放大效应。
    点对点通信的替代方案:

    1. UDP广播:广播能够应对服务器数量的不断增长,但它不节省带宽。由于服务器的网卡会获取广播,且必须要通知TCP/IP协议栈,因此广播会让那些与广播消息不相关的服务器产生一些额外的负载。
    2. TCP或者UDP组播:组播只允许相关的服务器接收消息,因此传送效率更高。
    3. 发布-订阅消息传递:发布-订阅消息传递的效率也较高,即使服务器在消息发送的那一刻没有监听,也可以收到消息。当然,发布-订阅消息传递,通常会让基础设施成本大增。
    4. 消息队列
  2. 留意共享资源。

    共享资源会成为系统的瓶颈、系统容量的约束和系统稳定性的威胁。如果系统必须使用某种共享资源,那就好好地对它进行压力测试。另外,当共享资源处理速度变慢或发生死锁时,要确保其客户端能继续工作。

八、失衡的系统容量

服务提供方和服务调用方的服务器的比例往往不好确定一个绝对合适的比例,所以对于服务的构建,如果不能使之全部满足前端潜在的压倒性需求,那么就必须构建服务调用方和服务提供方的韧性,从而能够应对海啸般袭来的请求。对服务调用方来说,当响应获取速度变慢或连接被拒绝时,使用断路器模式有助于缓解下游服务的压力。对服务提供方来说,可以使用握手和背压[插图]通知调用方,限制调用方发送请求的速度。还可以考虑使用舱壁模式,为关键服务的高优先级调用方预留系统容量。

● 检查服务器和线程的数量。在开发环境和QA环境中,系统可能看起来在一两台服务器上运行,其调用的其他系统的所有QA环境也是如此。然而在生产环境中,比例更有可能是10比1,而不是1比1。要将QA环境和生产环境对照起来,检查前端服务器与后端服务器的数量比,以及两端所能处理的线程的数量比。

● 密切观察放大效应和用户行为。失衡的系统容量是放大效应的特例:关系中一方的增幅变化大大超过另一方。季节性、市场驱动或宣传驱动等流量模式的变化,会导致前端系统的大量请求涌向后端系统(通常是良性的),就像热门的社交媒体帖子导致网站流量剧增。

● 实现QA环境虚拟化并实现扩展。即使生产环境的规模是固定的,也不要仅只使用两台服务器运行QA环境。扩展测试环境,尝试在调用方和服务提供方的规模扩展到不同比例的环境中运行测试用例,这些应该能够通过数据中心的自动化工具自动实现。

● 重视接口的两侧。如果所开发的是后端系统,假如系统突然收到10倍于历史最高的请求数量,并且都涌向了系统开销最大的事务部分,将会发生什么?系统完全失效了吗?它是先慢下来然后又恢复了正常吗?如果所开发的是前端系统,那么就需要看一看当后端系统在被调用时停止了响应,或变得非常慢的时候,前端系统会发生什么。

九、一窝蜂

系统达到稳态时的负载,会与系统启动或周期性运行的负载存在明显不同。想象一个应用程序服务器农场的启动过程,每台服务器都需要连接到数据库,并加载一定数量的参考数据或种子数据。每台服务器的缓存都从空闲状态开始,逐渐形成一个有用的工作集。到那时,大多数HTTP请求会转换为一个或多个数据库查询。这意味着当应用程序启动时,数据库上的瞬时负载要比运行一段时间后的负载高得多。

一堆服务器一同对数据库施加瞬时负载,这被称为“一窝蜂”。

引发一窝蜂现象的几种情况如下。
❑ 在代码升级和重新运行之后,启动多台服务器。
❑ 午夜(或任何一个整点时间)触发cron作业。
❑ 配置管理系统推出变更。

一些配置管理工具允许配置一个随机的“摆动”(slew)值,使各台服务器在稍微不同的时间点下载配置变更,从而把一窝蜂分散到几秒钟内。

当一些外部现象引起流量的同步“脉冲”时,也可能发生一窝蜂现象。想象城市街道每个角落安装的红绿灯。当绿灯亮时,人们会簇拥成群地过马路。因为每个人的行走速度不同,所以他们会在一定程度上分散开,但下一个红绿灯会再次将他们重新聚成一群。注意系统中阻塞许多线程的所有地方,它们在等待某个线程完成工作。而当这个状态打破时,新释放的线程就会对任何接收数据包的下游系统施加一窝蜂。

如果虚拟用户的脚本存在固定等待时间,则在进行负载测试时,就会产生流量脉冲。此时,脚本中的每个等待时间都应该附带一个小的随机时间增量。

● 一窝蜂所需系统成本过高,高峰需求无法处理。一窝蜂是对系统的集中使用,相比将峰值流量分散开后所需的系统能力,一窝蜂需要一个更高的系统容量峰值。● 使用随机时钟摆动以分散需求。不要将所有cron作业都设置在午夜或其他任何整点时间执行。用混合的方式设置时间,分散负载。
● 使用增加的退避时间避免脉冲。固定的重试时间间隔,会集中那段时间的调用方需求。相反,使用退避算法,不同调用方在经过自己的退避时间后,在不同的时间点发起调用。

十、做出误判的机器

就像杠杆一样,自动化使得管理员能够花费较少的努力进行大量的操作,这就是一个力量倍增器。
同样,当自动化程序出现“判断失误”时,引发的故障同样也会被放大。

这种情况一般会出现在控制层中。控制层中的软件主要管理基础设施和应用程序,而不是直接交付用户功能。日志记录、监控、调度程序、扩展控制器、负载均衡器和配置管理等都是控制层的一部分。
贯穿这些系统失效事件的常见线索是,自动化系统并未简单地按照人类管理员的意愿行事。相反,它更像是工业机器人:掌握控制层感知系统的当前状态,将其与期望的状态进行对比,然后对系统施加影响,使当前状态进入到期望状态。

可以在控制层软件中实现类似的防护措施。
❑ 如果软件观测器显示系统中80%以上的部分不可用,那么与系统出问题相比,软件观测器出问题的可能性更大。
❑ 运用滞后原则,快速启动机器,但要慢慢关机,启动新机器要比关闭旧机器更安全。
❑ 当期望状态与观测状态之间的差距很大时,要发出确认信号,这相当于工业机器人上的大型黄色旋转警示灯在报警。
❑ 那些消耗资源的系统应该设计成有状态的,从而检测它们是否正在试图启动无限多个实例。
❑ 构建减速区域,缓解势能。假想一下,控制层虽然每秒都能感知到系统已经过载,但它启动一台虚拟机处理负载需要花费5分钟。所以在大量负载依然存在的情况下,要确保控制层不会在5分钟内启动300个虚拟机。

● 在造成一片狼藉之前寻求帮助。基础设施管理工具可以迅速对系统产生巨大的影响,要在其内部构建限制器和防护措施,防止其快速毁掉整个系统。
● 注意滞后时间和势能。由自动化所引发的操作,需要花时间才能完成。这段时间通常会比监控的时间间隔要长,因此请务必考虑到系统需要经过一些延迟后,才能对操作做出响应。
● 谨防幻想和迷信。控制系统会感知环境,但它们有可能被愚弄。它们会计算出一个预期状态,并形成有关当前状态的“信念”,但这两者都有可能是错误的。

十一、缓慢的响应

生成响应较慢比拒绝连接或返回错误更糟,在中间层服务中尤为如此。

快速返回系统失效信息,能使调用方的系统快速完成事务处理,最终成功或是系统失效,取决于应用程序的逻辑。但是,缓慢的响应会将调用系统和被调用系统中的资源拖得动弹不得。

缓慢的响应通常由过度的需求引起。当所有可用的请求处理程序都已开始工作时,就不会有任何余力接受新的请求了。当出现一些底层的问题时,也会表现出响应缓慢。内存泄漏也经常表现为响应缓慢,此时虚拟机就越来越奋力地回收空间,从而处理事务。CPU利用率虽然很高,但都用在垃圾回收上,而非事务处理。另外,我偶尔会看到由于网络拥塞而导致的响应缓慢。这种情况在局域网内部相对少见,但在广域网上是肯定存在的,尤其是在协议过于“饶舌唠叨”的情况下。然而,更常见的情况是,应用程序一方面忙着清空它们的套接字发送缓冲区,另一方面又任由其接收缓冲区不断积压,从而导致TCP协议停顿。当开发工程师自行编写一个较低层级的套接字协议时,这种情况经常发生,其中read()例程只有当接收缓冲区被清空之后,才会被循环调用。

缓慢的响应倾向于逐层向上传播,并逐渐导致层叠失效。

让系统具备监控自身性能的能力,就能辨别其何时违背SLA。假设服务提供方需要在100毫秒内做出响应,当刚刚过去的20次事务的响应时长移动平均值超过100毫秒时,系统就会开始拒绝请求。这可能发生在应用层,此时系统可以利用给定的协议返回一个错误响应。这也可能发生在连接层,此时系统开始拒绝新的套接字连接。当然,任何这样的拒绝服务,都必须有详细的文档记录,并且调用方也能对此有所预期。

● 缓慢的响应会触发层叠失效。一旦陷入响应缓慢,上游系统本身的处理速度也会随之变慢,并且当响应时间超过其自身的超时时间时,会很容易引发稳定性问题。
● 对网站来说,响应缓慢会招致更多的流量。那些等待页面响应的用户,会频繁地单击重新加载按钮,为已经过载的系统施加更多的流量。
● 考虑快速失败。如果系统能跟踪自己的响应情况,那么就可以知道自己何时变慢。当系统平均响应时间超出系统所允许的时间时,可以考虑发送一个即时错误响应。至少,当平均响应时间超过调用方的超时时间时,应该发送这样的响应。
● 搜寻内存泄漏或资源争夺之处。争着使用已经供不应求的数据库连接,会使响应变慢,进而加剧这种争用,导致恶性循环。内存泄漏会导致垃圾收集器过度运行,从而引发响应缓慢。低层级的低效协议会导致网络停顿,从而导致响应缓慢。

十二、无限长的结果集

● 使用切合实际的数据量。典型的开发数据集和测试数据集都太小了,不能呈现“无限长结果集”的问题。当查询返回100万行记录并转成对象时,使用生产环境规模大小的数据集查看会发生什么情况。这样做还有一个额外的好处:当使用生产环境规模的测试数据集时,性能测试的结果更可靠。
● 在前端发送分页请求。前端在调用服务时,就要构建好分页信息。该请求应包含需要获取的第一项和返回总个数这样的参数。服务器端的回复应大致指明其中有多少条结果。
● 不要依赖数据生产者。即使认为某个查询的结果固定为几个,也要注意:由于系统某个其他部分的作用,这个数量可能会在没有警告的情况下发生变化。合理的数量只能是“零”“一”和“许多”。因此除非单单查询某一行,否则就有可能返回太多结果。要想对创建的数据量加以限制,不要依赖数据生产者。他们迟早会疯狂起来,无端地塞满一张数据库表,而那个时候该找谁说理去?
● 在其他应用程序级别的协议中使用返回数量限制机制。服务调用、RMI、DCOM[插图]、XML-RPC[插图]以及任何其他类型的请求-回复调用,都容易返回巨量的对象,从而消耗太多内存。

第5章 稳定性的模式

一、超时

良好的超时机制可以提供失误隔离功能——其他服务或设备中出现的问题不一定会成为你的问题。

超时机制与断路器相得益彰。断路器可以记录一段时间内的超时情况,如果超时过于频繁,断路器就会跳闸。

超时模式和快速失败模式(请参阅5.5节)都解决了延迟问题。当其他系统失效时,要保证自身系统不受影响,超时模式非常有用。若需要报告某些事务无法处理的原因,快速失败模式则能派上用场。就像一枚硬币的两面,快速失败模式适用于传入系统的请求,超时模式则主要适用于系统发出的出站请求。

● 将超时模式应用于集成点、阻塞线程和缓慢响应。超时模式可以防止对集成点的调用转变为对阻塞线程的调用,从而避免层叠失效。
● 采用超时模式,从意外系统失效中恢复。当操作时间过长,有时无须明确其原因时,只需要放弃操作并继续做其他事。超时模式可以帮助我们实现这一点。
● 考虑延迟重试。大多数超时原因涉及网络或远程系统中的问题。这些问题不会立即被解决。立即重试很可能会遭遇同样的问题,并导致再次超时。这只会让用户等待更长的时间才能看到错误消息。大多数情况下,应该把操作任务放入队列,稍后再重试。

二、断路器

断路器能有效防止集成点、层叠失效、系统容量失衡和响应缓慢等危及稳定性的反模式出现,它能与超时模式紧密协作,跟踪调用超时失败(区别于调用执行失败)。

三、舱壁

船舶的舱壁是一些隔板,一旦将其密封起来,就能将船分隔成若干独立的水密隔舱。在舱壁口关闭的情况下,舱壁可以防止水从一个部分流到另一个部分。通过这种方式,船体即使被洞穿一次也不会沉没。舱壁这种设计强调了控制损害范围的原则。
在软件开发中也可以使用相同的技术。通过使用隔板对系统进行分区,就可以将系统失效控制在其中某个分区内,而不会令其摧毁整个系统。物理冗余是实现舱壁最常见的形式。如果系统有4台独立的服务器,那么其中一个硬件所出现的失效,就不会影响其他服务器。同样,在一台服务器上运行两个应用程序实例,如果其中一个崩溃,那么另一个仍将继续运行。(当然,除非让第1个应用程序实例崩溃的那种外部影响力也能让第2个应用程序实例崩溃。)

四、稳态

无论是文件系统中的日志文件,数据库中的记录行还是内存中的缓存,任何累积资源的机制,都令人联想起美国高中微积分题目中的存储桶。随着数据的累积,存储桶会以一定的速率填满。此时这个存储桶必须以相同或更快的速率清空数据,否则最终会溢出。当该存储桶溢出时,坏事会发生:服务器停机,数据库变慢或抛出错误信息,响应长得仿佛是在星球间进行通信。稳态模式表明,针对每个累积资源的机制,要相应存在另一个机制回收该资源。下面介绍几种长期存在并有可能继续扩大的问题,以及如何避免去摆弄它们。

  1. 数据清除
  2. 日志文件:不加控制的日志文件会打满磁盘,耗尽文件系统的空间。最好一开始就避免一直往文件系统里添加内容。配置日志文件回转(rotation)只需要花几分钟时间。
  3. 内存中的缓存:如果缓存键数量没有上限,则必须限制缓存大小,并且采用某种形式的缓存失效机制。定时刷新缓存是最简单的缓存失效机制,“最近最少使用”[插图]或工作集算法也值得研究,但定时刷新缓存能够处理90%的情况。

● 避免摆弄。人为干预生产环境会导致问题。要消除对生产环境重复进行人为干预的需求。系统应在无须手动清理磁盘或每晚重新启动的情况下,至少运行一个发布周期。
● 清除带有应用程序逻辑的数据。DBA可以通过创建脚本清除数据,但他们不知道删除数据之后,应用程序会如何运转。为了保持逻辑完整性(特别是在使用对象关系映射工具时),应用程序需要清除自己的数据。
● 限制缓存。内存中的缓存可以加快应用程序的运行速度。但若不对内存中的缓存加以控制,执行速度仍会降低。需要限制缓存可消耗的内存量。
● 滚动日志。不要无限量保留日志文件。基于日志文件的大小来配置日志文件回转。如果合规性要求保留,则在非生产服务器上执行。

五、快速失败

即使在快速失败时,也要确保用不同的方式报告系统性失效(资源不可用)和应用程序失败(参数违规或无效状态)。否则,哪怕仅是用户输入了错误数据并点击了三四次重新加载,若报告成不具分辨度的一般性错误,也可能导致上游系统引发断路器跳闸。快速失败模式通过避免响应缓慢来提高整个系统的稳定性。与超时模式配合使用,快速失败模式有助于避免层叠失效。当系统由于部分失效而面临压力时,快速失败模式还有助于保持系统容量。

为了让“任其崩溃并替换”卓有成效,系统要具备几个前提。

  1. 有限的粒度:必须为崩溃定义边界。发生崩溃的组件应该是独立的,系统的其余部分必须能够自我防护,避免受到层叠失效的影响。在微服务架构中,服务的整个实例可能是正确的崩溃粒度。这在很大程度上取决于它被一个干净的实例所取代的速度,继而引入了下面这个关键的前提。
  2. 快速替换:启动NodeJS进程需要花几毫秒,可实现快速替换;但是启动一台新的虚拟机则需要几分钟,就不适合“任其崩溃并替换”的策略了。
  3. 监管:监管器需要密切注意它们重新启动子进程的频率。如果重新启动子进程过于频繁,那么监管器可能需要自行崩溃。这种情况表明系统状态没有得到充分的清理,或者整个系统面临危险,而监管器只是掩盖了根本问题。

● 快速失败,而非缓慢响应。如果系统无法满足SLA要求,快速通知调用者。不要让调用者等待错误信息,也不要让他们一直等到超时。否则你的问题也会变成他们的问题。
● 预留资源,并尽早验证集成点有效。本着“不做无用功”的原则,确保在开始之前就能完成事务。如果关键资源不可用,比如所需调用的断路器已跳闸,那么就不要再浪费精力去调用。在事务的开始阶段和中间阶段,关键资源可用状态发生变化的可能性极小。
● 使用输入验证。即使在预留资源之前,也要进行基本的用户输入验证。不要纠结于检查数据库连接,获取域对象,填充数据以及调用validate()方法,找到未输入的必需参数才是关键。

六、任其崩溃并替换

有时,为了实现系统级稳定性,放弃组件级稳定性就是所能做的最好的事情了。在Erlang语言中,这被称为“任其崩溃并替换”的哲学。

程序所能拥有的最干净的状态,就是在刚刚完成启动的那一刻。任其崩溃并替换的方法认为错误恢复难以完成且不可信赖,所以我们的目标应该是尽快回到刚完成启动时的干净状态。

● 通过组件崩溃保护系统。通过组件级不稳定性构建系统级稳定性,这似乎违反直觉。即便如此,这可能是将系统恢复到已知良好状态的最佳方式。
● 快速重新启动与重新归队。优雅崩溃的关键是快速恢复。否则,当太多组件同时启动时,就可能会失去服务。一旦一个组件重新启动,就应该自动令其重新归队。
● 隔离组件以实现独立崩溃。使用断路器将调用方与发生崩溃的组件隔离开来。使用监管器确定重新启动的范围。设计监管器层级树,既实现崩溃隔离,又不会影响无关的功能。
● 不要让单体系统崩溃。运行时负载较大或启动时间较长的大型进程,不适合运用“任其崩溃并替换”策略。同样,将许多特性耦合到单个进程中的应用程序,也不推荐运用该策略。

七、握手

当失衡的系统容量导致响应缓慢时,“握手”可能是最有价值的。如果服务器检测到自己不能满足其SLA要求,那么它应该通过一些方法请求调用方停止进程。如果服务器位于负载均衡器之后,那么它们可以使用“开关”控制,来“开启或停止”对负载均衡器的响应,然后负载均衡器可以将无响应的服务器移出负载均衡池。不过,这是一种粗放的机制,最好在执行的所有自定义协议中构建握手机制。
当调用缺乏握手机制的服务时,断路器是一种可以使用的权宜之计。在这种情况下,无须礼貌地询问服务器能否处理请求,只须发起调用并跟踪调用是否有效。总体而言,握手是一种未被充分利用的技术,在应用层协议中拥有巨大的优势。在层叠失效情况下,握手是一种防止裂纹跨层蔓延的有效方法。

● 创建基于合作的需求控制机制。客户端和服务器之间的握手,允许将需求的流量调节到可服务的级别。在构建客户端和服务器时,两者都必须实现握手。而大多数最常见的应用程序级协议,并没有实现握手。
● 考虑健康状况检查。在集群或负载均衡服务中,使用健康状况检查实现实例与负载均衡器握手。
|● 在自己的低层协议中构建握手。如果创建了基于套接字的协议,那么可以在其中构建握手机制。这样一来,端点就可以在未准备好接受工作时,通知其他端点。

八、考验机

(本书中,“考验机”是指能够用网络错误、协议错误或应用级错误等各种低层错误来测试被测软件。)

模拟依赖系统的失效行为来测试集成系统的一些无法验证的行为。

假设要构建一个替代每个远端Web服务调用的考验机。由于远程调用使用网络,因此套接字连接容易出现以下类型的失效。
❑ 连接被拒绝。
❑ 数据一直在监听队列中等待,直到调用方超时。
❑ 远端在回复了SYN/ACK之后就不再发送任何数据。
❑ 远端只发送了一些RESET数据包。
❑ 远端报告接收窗口已满,但从不清空数据。
❑ 建立了连接,但远端一直不发送数据。
❑ 建立了连接,但数据包丢失,重新传输导致延迟。
❑ 建立了连接,但远端对接收到的数据包从不进行确认,导致无休止的重新传输。
❑ 服务接受了请求,并且发送了响应头(假设是HTTP),但从不发送响应正文。
❑ 服务每30秒发送一字节的响应。
❑ 服务发送的响应格式是HTML,而不是预期的XML。
❑ 服务本应发送几千字节的数据,但实际上发送了几兆字节。
❑ 服务拒绝了所有身份验证证书授权。
以上失效问题可以分为几类:网络传输问题、网络协议问题、应用程序协议问题和应用程序逻辑问题。

混沌工程

要点回顾
● 模拟偏离接口规范的系统失效方式。调用真正的应用程序,仅能测试真实应用程序刻意生成的那些错误。优秀的考验机可以让你模拟现实世界中的各种混乱的系统失效方式。
● 给调用方施加压力。考验机能产生缓慢响应和垃圾响应,甚至不响应。这样一来,就可以看看应用程序如何反应。
● 利用共享框架处理常见系统失效问题。对每个集成点来说,不一定需要有单独的考验机。考验机这个“杀手”服务器可以监听多个端口,并根据你所连接的端口创建不同的系统失效方式。
● 考验机仅是补充,不能取代其他测试方法。考验机模式是对其他测试方法的补充和完善。它并不能取代单元测试、验收测试、渗透测试等(这些测试都有助于验证系统的功能性行为)。考验机有助于验证非功能性行为,同时又与远程系统保持隔离。

九、中间件解耦

松耦合、紧耦合
同步、异步

● 在最后责任时刻再做决定。在不对设计或架构进行大规模更改的情况下,大部分稳定性模式可以实施。但中间件解耦是架构决策,相关的实施会波及系统的每个部分。应该在最后责任时刻到来时,尽早做出这种几乎不可逆转的决策。
● 通过完全的解耦避免众多系统失效方式。各台服务器、层级和应用程序解耦得越彻底,集成点、层叠失效、响应缓慢和线程阻塞等问题就越少。应用程序解耦后,系统可以单独更改其他应用程序的所有配件,因此也更具适应性。
● 了解更多架构,从中进行选择。并非每个系统都必须看起来像是带有关系数据库的三层应用程序。多了解一些架构,并针对所面临的问题选择最佳的架构。

十、卸下负载

服务应该模仿TCP的做法:当负载过高时,就开始拒绝新的工作请求。这与快速失败模式相关。

在系统或企业的内部,运用背压机制会更有效(请参阅5.11节),这样做有助于在同步耦合的服务中,维持均衡的请求吞吐量。在这种情况下,卸下负载模式可以作为辅助措施。

● 无法满足全世界的请求。无论基础设施的规模有多大,也无论容量的扩展速度有多快,这个世界总是拥有超出系统容量极限的人数和设备数。当系统面对数量不受控制的需求时,一旦来自世界各地的请求疯狂地涌来,系统就需要卸下负载。
● 通过卸下负载避免响应缓慢。响应缓慢可不是好事。让系统的响应时间得到控制,而不是任其让调用方超时。
● 将负载均衡器用作减震器。个别的服务实例可以通过向负载均衡器报告HTTP 503错误,获得片刻喘息,而负载均衡器擅于快速地回收这些连接。

十一、背压模式

每个性能问题都源于其背后的一个等待队列,如套接字的监听队列、操作系统的运行队列或数据库的I/O队列。
如果队列无限长,那么它就会耗尽所有可用的内存。随着队列长度的增加,完成队列中某项工作的时间也会增加(类似“利特尔法则”)。因此,当队列长度达到无穷大时,响应时间也会趋向无穷大。我们绝对不希望系统中出现无限长的队列。
如果队列的长度是有限的,那么当队列已满且生产者仍试图再塞入一个新请求时,必须立刻采取应对措施。即使要塞入的新请求很小,也没有任何多余的空间。
我们可以在以下情况中做出选择。
❑ 假装接受新请求,但实际上将其抛弃。
❑ 确实接受新请求,但抛弃队列中的某一个请求。
❑ 拒绝新请求。
❑ 阻塞生产者,直至队列出现空的位置。
对于某些使用场景,抛弃新请求可能是最佳选择。对于那些随着时间的推移价值迅速降低的数据,抛弃队列中最先发出的请求可能是最佳选择。
阻塞生产者是一种流量控制手段,允许队列向发送数据包的上游系统实施“背压”措施。有可能这个背压措施会一直传播到最终的客户端,而这个客户端会降低请求发送的速度,直到队列出现空位置。
TCP在每个数据包中都采用额外的字段构建背压机制。一旦接收方的窗口已满,发送方就不得发送任何内容,直至窗口被释放。来自TCP接收方窗口的背压,会让发送方填满其发送缓冲区,这时后续写入套接字的调用将被阻塞。发送方与接收方的机制有所不同,但做法仍然是让发送方放慢速度,直至接收方处理完“手上堆积的工作”。
显然,背压机制会导致线程阻塞。将两种由临时状态导致的背压,和消费者运行中断导致的背压区别开来极为重要。背压机制最适合异步调用和编程,如果编程语言支持,可以利用许多Rx框架、actor或channel工具实现这个机制。
当消费者的缓冲池容量有限时,背压机制就只能对负载进行管理。原因在于,发送数据包的各个上游系统千差万别,无法对其施加系统性的影响。可以用一个例子来说明这一点,假设系统提供了一个API,能让用户在特定位置创建“标签”。而使用该API的上游,就包括大批手机应用程序和Web应用程序。
在系统内部,创建新标签并为其创建索引的速度是确定的,并且会受到存储和索引技术的限制。当上游调用“创建标签”的速率超过存储引擎的处理上限时,会发生什么情况?调用会变得越来越慢。如果没有背压机制,这会导致处理速度逐渐减慢,直至该API与离线无异。
然而,可以利用一个阻塞队列构建背压机制,实现“创建标签”的调用。假设每台API服务器允许100个调用同时访问存储引擎。当第101个调用抵达API服务器时,调用线程将被阻塞,直至队列中空出一个位置,这种阻塞就是背压机制。API服务器不能超出额定速度对存储引擎发起调用。
在这种情况下,限制每台服务器仅接受100个调用,这样处理过于粗糙。这意味着有可能一台API服务器有被阻塞的线程,而另一台服务器队列中却有空闲位置。为了令其更加智能化,可以让API服务器发起任意多次调用,但将阻塞置于接收端。这种情况下,就必须在现有的存储引擎外面包裹一个服务,进而接收调用、度量响应时间并调整其内部队列长度,实现吞吐量的最大化,保护存储引擎。
然而在某些时候,API服务器仍然会有一个等待调用的线程。正如4.5节所述,被阻塞的线程会快速引发系统失效。当跨越系统边界时,被阻塞的线程会阻碍用户使用,或引发反复重试操作。因此,在系统边界内运用背压机制效果最好。而在系统边界之间,还是需要使用卸下负载模式和异步调用。
在上面的示例中,API服务器应该用一个线程池接受调用请求,然后用另一组线程向存储引擎发出后续的出站调用。这样一来,当后续出站调用阻塞时,前面请求处理的线程就可以超时、解除阻塞并回应HTTP 503错误状态码。或者,API服务器可以丢弃队列中的一个“创建标签”命令,以便进行索引,此时返回HTTP 202状态码(表示“请求已接受,但尚未处理”)更为合适。
系统边界内的消费者,会以性能问题或超时的方式再现背压过程。事实上,这确实表明了一个真实的性能问题,消费者集体产生了超出提供者所能处理的负载!尽管如此,有时提供者也情有可原。尽管它有足够的容量应对“正常”的流量,但碰上一个消费者疯狂地发送请求,这可能源于自黑式攻击或者仅是网络流量模式自身发生了变化。
当背压机制生效时,需要通知监控系统,从而判断背压是随机波动还是大体趋势。
要点回顾
● 背压机制通过让消费者放慢工作来实现安全性。消费者的处理速度终究会减慢,此时唯一能做的就是让消费者“提醒”提供者,不要过快地发送请求。
● 在系统边界内运用背压机制。如果是跨越系统边界的情况,就要换用卸下负载模式,当用户群是整个互联网时更应如此。
● 要想获得有限的响应时间,就需要构建有限长度的等待队列。当等待队列已满时只有以下选择(虽然都不令人愉悦):丢弃数据,拒绝工作或将其阻塞。消费者必须当心,不要永久阻塞。

十二、调速器

● 放慢自动化工具的工作速度,以便人工干预。当事情的发展即将脱离控制时,我们经常会发现自动化工具会像“将油门踩到底”似的将事情搞砸。因为人类更擅长情景思维,所以我们需要创造机会亲身参与其中。
● 在不安全的方向上施加阻力。有些行为本身是不安全的。关机、删除、阻塞……这些都可能会中断服务。自动化工具将迅速执行这些操作,所以应该使用一个调速器,让人们获得时间来干预。
● 考虑使用响应曲线。在规定范围内操作就是安全的。但如果在该范围之外,行动就应该遇到相应的阻力,以减缓速度。

十三、总结

系统失效是不可避免的。我们的系统及其所依赖的系统,将会以大大小小的方式失效。稳定性的反模式放大了瞬态事件,它们会加速裂纹的蔓延。避免反模式虽然不能防止坏事发生,但当灾祸来临时,这样做有助于将损害降到最低。
无论面临什么困难,只要明智地运用本章所描述的稳定性模式,就会令软件始终保持运行。正确的判断是成功地运用这些模式的关键。因此,要本着不信有好事的原则审查软件的需求;以怀疑和不信任的眼光审视其他企业系统,防备这些系统在你背后“捅刀子”;识别这些威胁,并运用与每种威胁相关的稳定性模式;“迫害妄想”般的自我保护也是不错的工程实践。

第16章

二、过程和组织

开发和运维之间的边界不仅已经模糊,而且已经被全部打乱并重新组合了。这种状况甚至在DevOps这个词广为人知之前就已经出现了(参阅本节框注)。虚拟化和云计算的兴起,实现了基础设施的可编程性。开源运维工具同样实现了运维工作的可编程性。虚拟机镜像以及后来的容器和unikernel的出现,意味着程序都变成了“操作系统”。

回顾第7章介绍的各个层级,可以发现整个层级栈都需要软件开发。

同样,这个层级栈也需要整体运维。现在,基础设施(过去由运维团队负责)中出现了大量的可编程组件,这些基础设施发展成了平台,其他软件都能在这个平台上运行。无论是在云上还是在自己的数据中心里,都需要有一个平台团队,将应用程序开发团队视作其客户,为应用程序所需的常用功能以及第10章介绍的控制层工具提供API和命令行配置。

这个列表很长,而且随着时间的推移会变得更长。虽然其中的每一项都可以由单个团队自行构建,但若将它们相互孤立起来,那就毫无价值了。平台团队要记住,当前实施的机制,应该允许其他团队自行配置,这一点很重要。换句话说,平台团队不应该实施各个应用程序开发团队特定的监控规则。相反,平台团队应该为应用程序开发团队提供API,使其能在平台提供的监控服务上,实施自己的监控规则。同样,平台团队也不会为各个应用程序开发团队构建API网关,而是构建一种服务,各个应用程序开发团队能够利用该服务构建各自的API网关。

(平台团队与应用程序开发团队的职责分工)

平台团队要记住,当前实施的机制,应该允许其他团队自行配置,这一点很重要。换句话说,平台团队不应该实施各个应用程序开发团队特定的监控规则。相反,平台团队应该为应用程序开发团队提供API,使其能在平台提供的监控服务上,实施自己的监控规则。同样,平台团队也不会为各个应用程序开发团队构建API网关,而是构建一种服务,各个应用程序开发团队能够利用该服务构建各自的API网关。

必须让应用程序开发团队,而不是平台团队,负责应用程序的可用性。相反,平台的可用性是衡量平台团队的标准。

平台团队需要以聚焦客户为导向,其客户就是应用程序开发人员。这与旧的开发团队与运维团队的划分理念截然不同。

四、在团队级别实现自治

亚马逊公司创始人兼首席执行官Jeff Bezos的“两个比萨团队”规则:每个团队的规模不应该超过能被两张大号比萨喂饱的人数。
但“两个比萨团队”不仅仅是减少团队人数的问题,而是需要减少团队的外部依赖,减少各种上下游的评审和审批,包括架构设计、发布管理、变更管理、

这个概念不仅仅是指为一个项目分配几个编程人员,这实际上是如何让一个小组能够实现自给自足,并能将成果一直推入生产环境的问题。缩小每个团队的规模,需要大量的工具和基础设施的支持。诸如防火墙、负载均衡器和存储区域网络等专业硬件,都必须有API包裹在其周围,这样每个团队才能管理自己的配置,而不会对其他人造成严重破坏。16.2.1节所讨论的平台团队,此时就能扮演重要角色。平台团队的目标,必须是实现和促进上述团队规模的自治。

posted @ 2021-10-31 01:27  liqipeng  阅读(213)  评论(0编辑  收藏  举报