在当今诸多企业解决方案中,集成系统数量日益增长,迫使我们以系统的方式处理依赖项和环境的故障。通过在架构阶段对依赖项的故障进行建模,我们可以交流、测试并实现系统对故障的响应,以此减少业务的风险与成本。
在本文中,我们将介绍系统故障建模。系统故障建模是一种用以帮助提前发现依赖项和环境故障,并在解决方案的架构早期采取主动措施的技术。
我们将使用四步的流程来创建故障模型,而每一步都将循序渐进的揭示创建故障模型的必要信息。在本文的最后,我们将探讨不同的工具和模式对架构产生的效果,使得系统在面对不同故障模式时都可以设计和实现出我们需要的系统响应。
场景 / 服务
订单执行服务
库存服务
客户数据库
商品目录
客户评论服务
搜索商品
X 阅读客户评论 X
添加商品到购物车
X X 结账
X
X
X
以下列举的是每一步的概览:
- 第一步:我们将从场景的层面发现解决方案中的功能性依赖并介绍依赖项的矩阵模型,该矩阵模型将在后续的步骤中用到。
- 第二步:我们将从集成系统的视角查看解决方案的执行环境,并定义服务品质协议 (SLAs)。
- 第三步:在这一步中,我们将会识别出那些可被系统收集用于了解解决方案当前运行状态的关键数据点。这些数据稍后将被用于识别出解决方案所在的故障场景。
- 第四步:我们将关注集成服务可能失败的多种不同的方式。我们会建立解决方案故障模型并确定我们的解决方案应该在这些故障场景中以何种方式应对。
第一步. 理解功能性依赖
当我们开始在新的解决方案中启动处理依赖性故障的计划流程时,一个关键的问题是要理解该解决方案的功能划分以及每个功能区对于第三方系统的依赖。对于架构师来说,理解这些根本的依赖关系是构架一个可以应付依赖项及环境故障的解决方案的前提。
我们使用的第一个模型是依赖项矩阵。该模型是系统依赖项矩阵,它概括并跟踪了系统的各种依赖。在这个模型中,系统功能划分 (或场景) 被拆解提取,并被关联到他们所依赖的第三方系统。
下图展示了系统依赖矩阵的示例。
场景 / 服务
订单执行服务
库存服务
客户数据库
商品目录
客户评论服务
搜索商品
X 阅读客户评论 X
添加商品到购物车
X X 结账
X
X
X
系统依赖项矩阵帮助解决方案架构师理解各功能划分分别依赖于哪些集成服务。本文始终会使用矩阵衡量计算响应时间、可用性、故障模型和系统对依赖项故障的响应。同样,系统依赖项矩阵也是本文后续将谈到的各模型的基础。
在下一节中,我们将介绍解决方案的一些特性。我们需要将这些特性整合到解决方案的架构中,并且需要在创建故障模型时考虑这些属性。
第二步. 建立运作指标
在识别出解决方案的各种场景以及它们所依赖的不同系统 / 服务后,我们需要明白系统的正常运作模式是什么。只有这样,我们才能够识别出系统是否存在异常的情况,并可以及时采取主动的应对措施。在这一节中,我们将检查系统的资源约束并通过对解决方案的质量特性进行建模,从而概括出正常的运作模型。
解决方案特性可以被划分为两个高层次的分类:解决方案的功能特性和解决方案的运作特性。解决方案的功能特性描述的是该方案提供的实实在在的功能。而解决方案的运作特性有时被称为非功能性需求,它只是确定了该系统会如何运作。
这些质量特性包括系统的可扩展性范围,系统在正常或错误条件下的响应时间,系统的可用性等。虽然对最终用户是不可见的,但是解决方案的质量需求将决定系统在业务中的长期价值。
我们通常将解决方案质量的一个子集归为解决方案服务品质协议(SLAs)。通常的 SLAs 包括但不限于:响应时间,可用性,系统支持的并发用户数。
你的系统需要满足的 SLAs 通常取决于业务需要和市场需求。有的时候,达成 SLA 的期望过早,而在后期对依赖系统的分析中会发现,预先达成的 SLAs 其实是不可行的。
在这一节中,我们将考察从 SLAs 中挑选出最常见的三项。展示我们如何通过着眼于它们的依赖,从而使我们的解决方案可以支持的 SLAs 更加清晰合理。
系统可用性模型
对于指定的解决方案来说,可用性往往是 SLA 列表里排在首位的。当然,所有的软件解决方案都努力的希望达到 100% 的可用性。但不幸的是,在复杂的解决方案中,系统的功能实现都由一系列其他系统集成而成,这使得要达到 100% 的可用性极其困难。
取决于不同的功能依赖,要计算整个系统的可用性数值非常复杂。然而,我们可以使用以下的公式,来粗略地计算计划中解决方案的可用性。
总可用性 = 依赖系统 1 的可用性 * 依赖系统 2 的可用性 * 依赖系统 N 的可用性 (译者注:这里原文中可能漏掉了一个省略号,个人感觉应该是如下公式, 总可用性 = 依赖系统 1 的可用性 * 依赖系统 2 的可用性 *…* 依赖系统 N 的可用性) 为了能更真实的理解解决方案的功能可用性,并为依赖系统的响应制定计划。可以使用如下图所示的模型来计算基于依赖服务可用性的场景可用性。
场景 / 服务
订单执行服务
库存服务
客户数据库
商品目录
客户评论服务
系统响应时间
搜索商品
99.50% 99.50%
阅读客户评论
98.00%
98.00%
添加商品到购物车
99.90% 99.50% 99.40%
结账
99.00%
99.90%
99.99%
98.89%
我们注意到,上面的例子中,依赖服务各自的可用性将通过应用 / 服务所属的分组获取到。在大多数的企业场景中,直接从运维团队获取到数据是最精确最理想的方法。
系统响应时间模型
随着复杂软件解决方案中依赖项数目的增长,系统的响应时间将被串联起来。其中系统响应时间的波动将对整个开发中的解决方案的总体响应时间产生负面影响。
最常见的一种容易被忽视的情况是具有父子处理关系的一些场景。当接收到一个包含了很多子项需要被进一步处理的请求时,做出的响应往往有很多的情况。尽管这个问题看起来很琐碎,但是在理解每个调用链的总体响应时间之前,有很多的数据依赖问题需要去解答。
为了简便起见,我们将只集中精力于所有参与服务的调用链依赖而不是数据依赖。
为了对期望的响应时间建模,我们将重新使用服务依赖项矩阵。这一次,我们不再简单的展示依赖关系,而是清算出每个服务的调用次数。如果再加上各自服务响应时间的指标,表格将如下所示:
场景 / 服务
订单执行服务
库存服务
客户数据库
商品目录
客户评论服务
系统响应时间
搜索商品 2 1000
毫秒
阅读客户评论
1
1000
毫秒
添加商品到购物车
2 2 1300
毫秒
结账
1
2
2
1500
毫秒
通过使用该模型,我们可以识别出指定的场景 / 功能中期望的响应时间。一旦监控的响应时间开始恶化,解决方案将可以努力采取类似如下主动架构措施:借助于多层缓存,用并行的解决方案处理调用结果以及使用重叠IO (overlapped I/O)。我们将在本文的后续部分探索这些方法中的部分方案。
系统吞吐量模型
我们在本文中讨论的最后一项SLA 是解决方案的吞吐能力。该能力大致可以理解为每秒钟可以处理的请求数。简单起见,我们将不考虑数据有效负载的规模或任何计算中的其他的因素。解决方案的吞吐量依赖于调用链中每个依赖系统每秒的事务处理量(TPS)。我们可以将事务理解为任何在调用链中的指定系统的一次操作执行。
如何得出依赖系统的TPS 往往被证明是一件非常有挑战的事,因为系统相关的文档往往很缺乏。在这种情况下,我们就有必要编写简单的驱动应用去监控每个系统独立的TPS。
如果这些个依赖系统被多个其他应用所共享,就像一个数据库会为三个不同的应用提供服务一样,该系统的TPS 只有部分对该开发中的解决方案来说是有效的。整体依赖的利用百分比需要在计算中作为因素被考虑进去。以下是系统容量模型的例子,一个简化的在线零售商,通过整合跨越订单执行,库存,商品目录,客户评价和客户数据库等多个服务的调用提供相关功能。
服务
吞吐量(TPS)
当前解决方案的专用容量
整体可用性吞吐量(TPS)
订单执行服务
350
100%
350
库存服务
500
75%
375
客户数据库
2000
50%
1000
商品目录
2000
100%
2000
客户评论服务
150
55%
82.5
通过这些数字,直观的就能看到的是:对于指定的服务调用链,该解决方案的最大吞吐量仅仅是该调用链中 TPS 最低的服务的吞吐量。在解决方案中,这条关键信息可以被用来识别潜在的扩展计划,并可以采取主动措施以确定没有超过吞吐量的阈值。我们将会在本文的后续部分就如何可以做到这一点来探讨一些相关的技术。
在这一节中,我们讨论了 SLAs 中的一些在达成前需要被彻底分析的内容。我们同样也讨论了一些工具来帮助我们评估解决方案中哪些 SLAs 是符合实际并可以达成的。一旦这些被确定,我们将开始关注设计和计划如何使我们的系统达到这些 SLAs,并主动地设计好系统如何在规定的约束条件内可以正常的运作。
第三步. 测量关键数据点
通过着眼于依赖关系,我们确定了期望的 SLAs。在此之后,我们需要去搜集那些用于检测期望运作模型的必要数据点。在这一节中,我们将讨论在系统运行时可以搜集到的不同的数据点。 虽然每个项目需要搜集不同的关键指标,但实际上花 90% 的时间收集以下最小的指标集就足以用于适当的调整系统行为。
- 响应时间: 该值记录了请求方向所依赖的服务发起请求至请求方接收到服务响应之间的总时间。这个响应时间考虑了所有网络延迟以及系统所依赖的服务真正的处理时间。
- 数据负载规模: 服务调用返回响应的总规模。尤其是在处理粗粒度服务的时候,大数据量会影响整个处理管道。所以,掌握数据负载规模是非常有帮助的。
- 吞吐量: 该值可以是每秒服务处理的请求数或每秒服务处理返回的数据规模。该关键数据点可以被用来控制服务的请求流。
- 异常的类型 / 数量: 该值是一个固定的时间周期内根据类型分组的异常总数。该数据点可以被用来基于集成的服务的状态临时关闭系统的部分功能。 在下一节中,我们将使用前面提到的数据点创建一种可以适应生存的架构。换句话说,我们将讨论请求流的控制 (有时称为限流) 和断路器 (circuit breaker) 的实现
第四步. 创建系统故障模型
目前我们已经理解了所有解决方案集成的服务以及解决方案可以满足的合理 SLAs,并且识别出了那些需要被测量(以用于检测系统的实时运作质量是否符合了已达成的 SLAs)的数据点。解决方案架构师还需要确认方案是否可以经受的起依赖系统的故障并且以一种可预见的方式响应故障。
每个被依赖的服务都可能因为不同的原因或以完全不同的方式发生故障。不幸的是,这些故障场景并没有在系统所要求的这些功能的需求文档里被良好的描述。 因此,需要为所有的依赖故障制定计划并提出可行的恢复或限制功能的手段以供系统继续以最优的方式运作,而这个重任就落在了架构师的肩上。
尽管不同的图表技术都可以用来表述系统的故障模型,我们将会在后续描述一种特殊的信息记录方式。然而,只要你交流的对象 (开发团队、业务分析师和项目利益相关者) 能够在没有你特别解释的情况下阅读懂该图,那么图表工具或方法的选择将不会像内容本身那样重要。
对于解决方案集成的每一个系统来说,都有一系列的组件可能发生故障。本文到目前为止,我们已经创建了可用性,响应时间和吞吐量等模型。因此,我们将在咱的所有故障模型中仅仅考虑以下类型:
- 响应时间变长
- 重复的应用错误
- 系统不可用
一个系统的故障模型创建如下,其中搜集了在我们的电子商务解决方案中的不同类型的故障。请记住,这个表格需要简洁但并不要求很全面。
场景 / 服务
订单执行服务
库存服务
客户数据库
商品目录
客户评论服务
系统行为
搜索商品
响应时间变长 提供缓存的版本
阅读客户评论
系统不可用
关闭功能
添加商品到购物车
X 响应时间变长 结账
重复的应用错误
响应时间变长
X
对请求限流
在上面的例子中,每个场景都会依赖一些列的集成服务。在每一行中,我们提供了一种在集成服务可能失败时可用的应对方法。简单的“X”符号用来表示这些服务可以正确的工作。
在对本模型更加详尽的实现中,我们需要考虑不同故障的组合情况。因此,这将是一个更加复杂的模型,它针对每个场景将拥有多行来展示不同的故障组合。 最后,我们期望的针对每个场景或功能的系统故障响应将以列表的形式展示在右手边。令人遗憾的是,在表格中定义的每个系统故障响应都是有利有弊的。
当务之急是架构师需要考虑清楚定义系统故障响应后带来的全部影响。在我们的例子中,当商品目录服务响应时间较高时,商品将在缓存的版本中搜索。使用该特定的缓存后可能最终将导致用户的得到的商品目录是略微过时的版本。不管怎样,这总比简单的抛出一种异常然后终止用户的搜索来的更好。
如上面的例子所见,系统行为的决策带来的影响对业务来说意义深远。因此,强烈建议与关键的业务利益相关者共同鉴定和确认系统响应来获得针对业务目标的更好的优化。
一种用来加速决策过程的方法是将所有可行的系统故障响应作为选项列举出来,根据业务风险评出相应的等级,并辅以简短的解释作为说明。一些业务风险的例子比如:运营成本,开发时间或花费,简化的功能。
在下一节中,我们将看看解决方案架构师如何主动设计解决方案来实现在系统故障模型中指定的所需行为,并测试该实现。
定义系统故障响应的可用工具和模式
在本文的上一节中,我们讨论了一些不同的步骤来帮助定义故障模型以及在应对解决方案依赖的第三方服务 / 系统出现故障时,如何选择需要的系统响应。 在这一节中我们将着眼于不同的模式和方法论,这些内容可以帮助我们在设计阶段和运行阶段确保指定解决方案的故障响应被实现了,同时还可以正确的被测试。
通过依赖注入模拟故障
尽管这听起来并不需要架构师高度关注,但是使用依赖注入模式对于设计一种能在服务中断的情况下,系统依然可用的解决方案来说是极其重要的。 依赖注入使得解决方案的各种服务的不同实现可以以插件的形式插拔。这对在模拟依赖系统的行为作为测试保护措施时特别有用。
通过依赖注入,开发者可以在不重新编译代码的情况下注入那些精心设计用以触发已知场景的服务。可以通过简单的改变配置来模拟不同的服务故障组合。该模式对于在单元测试和集成测试(我们将简短的涉及到)时测试我们要求的系统行为极其有帮助。
不管怎样,该模式应该在整个项目中被强制执行。个别违规的开发可能会破坏依赖注入的一些前提条件。通用的措施是解决方案架构师可以通过代码复查来确保该模式被正确的实施了。这是一种工具并仅当在整个项目中被统一使用时才会有效:明智的做法是将它整合到开发团队可以使用的应用开发框架中去。
从单元测试和持续集成中获益
反复的测试和确认,当某一故障场景发生时,解决方案的行为是否会以规定的方式执行是非常重要的。达到这一点最有效的方式就是自动化执行这些测试。 一种可以达到该目的的方式是通过将故障模型和独立的测试进行映射,并创建一整套用于模拟所依赖服务特定行为的测试套件。因为错误的不同组合可以并且应该在测试套件中被测试,所以测试套件执行的总时间可能比单元测试套件更长。 作为解决方案架构师,我们需要确认这些受益于依赖注入的长时间运行的集成测试并没有跑在开发者的工作站上。
上述工作最合适的候选人就是构建服务器。不幸的是,当今大多数的开发小组仅仅通过利用构建服务器的构建能力,是不能将持续集成的全部潜力发挥出来的。当今很多的构建服务器具备了很多功能来执行上述测试套件。但是如果你的构建服务器不能提供这些能力或者你根本就没有构建服务器,那么目前已有很多的开源解决方案供你选择。
除了在构建服务器上执行常规单元测试之外,执行上述的测试套件,将给予架构师平和的心态,并在特定场景中的某些情况发生时确保系统行为。
尽管架构师的“兵工厂”(arsenal)里有如此伟大的一件工具,但持续集成测试并不能确保系统可以一直以期望的行为工作。他们只提供了在面对单一故障场景时的一种特定行为。如果要关注系统的长期行为,我们将在下一节中讨论测试装置(test harnesses)的用途。
创建测试装置
在某些情况下,仅仅建立个别用于返回预定义异常的回执(stubs)可能就足够了。然而大多数时候,能够逐渐改变系统对依赖的响应以及观察解决方案在环境变化时的行为是非常重要的。尤其是在测试到包含断路器(circuit breaker)实现的场景时。
为了测试在变化的故障模式中的系统行为,有必要花较少的精力建立一个可以模拟独立或组合服务故障的可控环境。一些时候,这还需要创建可以输入脚本的应用,并可以在一段时间内按控制返回一系列录制的响应。
通过当今操作系统提供的虚拟化能力和第三方的解决方案,通过创建虚拟化来达成这项任务是可行的。这些环境可以在每次迭代中的某一段时间内被使用,并可以在不使用时撤下。
需要注意的一件事是,创建测试装置并不是一次性的活动,它需要在解决方案的每个迭代中重新考虑。一旦引入了更多的依赖,那么就需要更多的精力去保持测试装置与当前代码基础的相关性。
结束语
作为解决方案架构师,我们日益面临交付解决方案的挑战。这些解决方案需要集成已存在的那些部署在云中,在不同的客户端数据中心或相同的数据中心中的其他解决方案。不管这些服务在哪里,集成系统的行为将会以很多种方式影响到我们的解决方案。
我们作为架构师的一部分责任是识别,设计和测试如何应对这些系统中的故障和异常现象。在本文中,我们讨论了如何能更好的为集成其他服务做好计划。我们着眼于识别出我们的 SLAs 去理解目标系统的性能。然后我们又通过创建故障模型来识别出系统会如何故障并且针对不同的故障组合解决方案该如何响应。最后,我们讨论了一些可以整合到解决方案中的通用实践,使得实现和测试期望系统的行为更将简单。
我们在本文中涉及到工具和技术只是我们作为解决方案架构师用于确保交付高质量软件可选工具的冰山一角。除了技术方面,在项目团队组织方面多花精力也是不容忽视的。对于所有服务团队中的人力与团队工作动力方面的内容值得在另一篇独立的文章中来讲述。
关于作者
Mr. Cetinkaya(森汀卡亚)是美国 Netsoft 公司(一家主营策略技术与设计的公司,总部设立于美国纽约市)的首席解决方案架构师。他专注于设计和指导创新性 解决方案的开发,致力于使 Netsoft 具备一流的解决方案交付能力。基于对重构和创新的关注,森汀卡亚专注于利用先进的软件架构体系设计可伸缩系统,该 架构体系包括软件工厂,基于区域的应用框架设计及可重用组件。森汀卡亚曾获得纽约布鲁克林理工大学的计算机科学学士学位以及纽约大学的管理学硕士学位。
原文链接: Modeling Failure Scenarios in Systems
感谢侯伯薇对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。
评论