Hope is a good thing, and maybe the best thing of all

编程不止是一份工作,还是一种乐趣!!!

反稳定性模式

集成点


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

  • 小心必然会出现的恶魔

    每个集成点最终都会以某种方式发生系统失效,所以需要为系统失效做好准备。

  • 为各种形式的系统失效做好准备

    集成点的系统失效有多种形式,如各种网络错误和语义错误。通过明确的协议获得良好的错误响应是不现实的,相反,某种协议违规、缓慢响应或停止响应等,这些情况更为常见。

  • 知道何时应该揭开抽象

    调试集成点的系统失效,通常需要透过抽象看问题。由于系统失效大多违反了高层协议,因此往往很难在应用层调试。此时可以求助于数据包嗅探器和其他网络诊断工具。

  • 系统失效会迅速蔓延

    若系统代码缺乏一定的防御性,那么远程系统失效会以层叠失效的方式迅速演变为系统问题。

  • 采用一些模式来避免集成点问题

    利用断路器、超时、中间件解耦和握手等模式进行防御性编程,以防止集成点出现问题。第5章将详细介绍这些模式。

同层连累反应


尽管集群水平扩展不易遭遇单点系统失效,但它们可能会出现与负载相关的系统失效。例如,高负载比低负载更易导致竞态条件的并发缺陷。当负载均衡组中的一个节点发生故障时,其他节点必须额外分担该节点的负载。如果应用程序存在缺陷(通常是资源泄漏或与负载相关的崩溃),就会发生同层连累反应。

  • 记住:一台服务器的停机会波及其余服务器

    由于一台服务器停机,其他服务器必须负担其工作负载,这样就会发生同层连累反应。增加的负载使得剩余的服务器更易发生系统失效。同层连累反应会迅速让整层系统停机。依赖该层系统的其他层级必须做好防护措施,否则将会陷入层叠失效。

  • 寻找资源泄漏

    大多数情况下,如果应用程序发生内存泄漏,便会发生同层连累反应。当一台服务器耗尽内存并停机时,其他服务器不得不负担它的工作负载,但所增加的流量会加快内存泄漏。

  • 寻找难以捕捉的时序缺陷

    流量状况也可能引发难以捕捉的竞态条件。同样,如果一台服务器陷入死锁,其他服务器所增加的负载也极易使它们陷入死锁。

  • 采用自动扩展

    应该为云端的每个自动扩展组创建健康状况检查机制。自动扩展将关闭未通过健康状况检查的服务器实例,并启动新的实例。只要自动扩展机制的响应速度比同层连累反应的蔓延速度快,那么系统服务就依然可用。

  • 利用“舱壁模式”进行保护

    使用舱壁模式分隔服务器,可以防止同层连累反应毁掉整个系统服务。但当被分隔的服务器停机时,它们无法帮助其调用方确定哪个服务器分隔区停止了工作,不过这时调用方可以使用断路器模式。

层叠失效


系统失效始于出现裂纹。某些环境下导致的缺陷,发生内存泄漏,或者某个组件超负荷运行等,这些基本问题常会导致裂纹。当某一层系统崩溃导致其调用层也发生裂纹时,就发生了层叠失效。层叠失效通常源于枯竭的资源池。资源池枯竭的原因往往是较低层级所发生的系统失效。没有设置超时时间的集成点,必定会导致层叠失效。

系统失效的跨层机制,通常以线程阻塞的形式表现。但也有例外,即过分积极的线程。此时,调用层会迅速收到一个错误报告。但考虑到之前类似的情况,调用层会假定这只是下层中不可重现的瞬态错误。某些时候,下层会遭遇竞态条件,导致在一段时间内无故抛出错误。此时上游系统的开发工程师会重新尝试调用请求,不幸的是,下层提供的细节不足以区分是瞬态错误还是更严重的错误。因此,一旦下层开始出现一些真正的问题,如由于交换机发生故障,造成来自数据库的数据包丢失,调用层就开始越来越频繁地访问下层。下层越难以响应,调用层访问越频繁。最终,调用层会倾尽全部CPU资源调用下层,并把调用失败记录到日志中。

  • 阻止裂纹跨层蔓延

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

  • 仔细检查资源池

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

  • 用超时模式和断路器模式实现保护

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

一窝蜂


停电后常见的情形是,送电几秒钟后又再次断电。当前数百万台空调和冰箱的用电需求,使刚刚恢复的电力供应发生过载。在炎热的季节,这种现象在大城市里尤为常见。一窝蜂引发故障通常有几种场景:

  1. 某种原因导致的业务浪涌
  2. 某些时间点的定时任务集中运行
  3. 某些事件促发的资源集中重置

这些场景还会互相叠加,如应用重启后的业务浪涌,引发系统出现瓶颈。严重时还会陷入循环反复出现。

应对方法

  1. 一定要有完善的限流和熔断机制,保证在外部浪涌情况下系统仍然可以正常处理请求
  2. 将定时任务和资源重置事件随机打散,不要集中运行
  3. 应用集中重启以后等资源加载完成以后再放入流量或者平滑的将流量放入

出现误判的机器


系统中的自动监控程序和自动调度程序也会导致故障:

  1. 自动控制系统的操作和手工控制相冲突

  2. 自动控制系统自身出现误判

建议措施

  1. 自动控制系统的检测机制一定要有多周期检查和多节点确认机制,最大限度避免产生误判。

  2. 滞后原则,启动实例可以快速,但是停止实例一定要谨慎,并且有抑制机制

  3. 当涉及大范围的操作的时候,建议引入人工确认机制,避免直接自动化

要点

  • 如果软件观测器显示系统中80%以上的部分不可用,那么与系统出问题相比,软件观测器出问题的可能性更大。

  • 运用滞后原则,快速启动机器,但要慢慢关机,启动新机器要比关闭旧机器更安全。

  • 当期望状态与观测状态之间的差距很大时,要发出确认信号,这相当于工业机器人上的大型黄色旋转警示灯在报警。

  • 那些消耗资源的系统应该设计成有状态的,从而检测它们是否正在试图启动无限多个实例。

  • 构建减速区域,缓解势能。假想一下,控制层虽然每秒都能感知到系统已经过载,但它启动一台虚拟机处理负载需要花费5分钟。所以在大量负载依然存在的情况下,要确保控制层不会在5分钟内启动300个虚拟机。

缓慢的响应


生成响应较慢比拒绝连接或返回错误更糟,在中间层服务中尤为如此。快速返回系统失效信息,能使调用方的系统快速完成事务处理,最终成功或是系统失效,取决于应用程序的逻辑。但是,缓慢的响应会将调用系统和被调用系统中的资源拖得动弹不得。

  • 缓慢的响应会触发层叠失效

    一旦陷入响应缓慢,上游系统本身的处理速度也会随之变慢,并且当响应时间超过其自身的超时时间时,会很容易引发稳定性问题。

  • 对网站来说,响应缓慢会招致更多的流量

    那些等待页面响应的用户,会频繁地单击重新加载按钮,为已经过载的系统施加更多的流量。

  • 考虑快速失败

    如果系统能跟踪自己的响应情况,那么就可以知道自己何时变慢。当系统平均响应时间超出系统所允许的时间时,可以考虑发送一个即时错误响应。至少,当平均响应时间超过调用方的超时时间时,应该发送这样的响应。

  • 搜寻内存泄漏或资源争夺之处

    争着使用已经供不应求的数据库连接,会使响应变慢,进而加剧这种争用,导致恶性循环。内存泄漏会导致垃圾收集器过度运行,从而引发响应缓慢。低层级的低效协议会导致网络停顿,从而导致响应缓慢。

无限大的结果集


带着怀疑的态度来设计,系统才会具有韧性。常问自己:“某某系统会如何危及我的系统?”然后设计一种方法,躲过“队友”挖的坑。代码中的常见结构如下所示:查询数据库,然后遍历结果集,处理每一行结果。通常,处理每一行意味着将新的数据对象添加到集合中。但是当数据库突然返回500万行,而不是通常的100多行时会发生什么?除非应用程序明确限制了其可以处理的结果数量,否则系统就可能会耗尽内存。

  • 使用切合实际的数据量

    典型的开发数据集和测试数据集都太小了,不能呈现“无限长结果集”的问题。当查询返回100万行记录并转成对象时,使用生产环境规模大小的数据集查看会发生什么情况。这样做还有一个额外的好处:当使用生产环境规模的测试数据集时,性能测试的结果更可靠。

  • 在前端发送分页请求

    前端在调用服务时,就要构建好分页信息。该请求应包含需要获取的第一项和返回总个数这样的参数。服务器端的回复应大致指明其中有多少条结果。

  • 不要依赖数据生产者

    即使认为某个查询的结果固定为几个,也要注意:由于系统某个其他部分的作用,这个数量可能会在没有警告的情况下发生变化。合理的数量只能是“零”“一”和“许多”。因此除非单单查询某一行,否则就有可能返回太多结果。要想对创建的数据量加以限制,不要依赖数据生产者。他们迟早会疯狂起来,无端地塞满一张数据库表,而那个时候该找谁说理去?

  • 在其他应用程序级别的协议中使用返回数量限制机制

    服务调用、RMI、DCOM、XML-RPC以及任何其他类型的请求-回复调用,都容易返回巨量的对象,从而消耗太多内存。