弹性工程设计:Actor模型与微服务架构比较 - ufried


弹性有点像安全性:它有助于在某些事情没有按预期进行时不至于损失钱财。
它也有一个概率成分:因为意外事件和失败只发生在一定的概率下。
它也有一个间接的组成部分:如果你的IT系统过于频繁地出现故障,将会导致次要的影响,如失去恼人的客户。
这意味着,你的损失不仅是故障的直接影响,也是脱离事件本身的次要影响。

商业案例倡导弹性、韧性者的关键信息是:
弹性不是为了赚钱;弹性是不输钱。

如果我们理解了这一点,我们就有了必要的基础来更好地评估弹性的经济可行性:不是通过追逐短期的投资回报率,而是通过使用更合适和可持续的商业案例计算模式。

过度弹性
一旦您开始意识到分布式 IT 系统环境中可能出现的问题,您会在应用程序中发现越来越多的地方您认为需要通过弹性措施来增强。
然后你会找出可以应用的越来越复杂的措施。可能性是无穷无尽的,一切都感觉很重要。
但是,沿着这条路走下去,弹性软件工程就会变成一个无底洞,本身就是一个终点。您可以将整个公司的钱都投入其中,但仍然无法完成。

这里的关键信息是:
弹性本身并不是目的,弹性是达到目的的一种手段。

此外,向您的应用程序添加越来越多的弹性措施会使其变得越来越复杂。但是,您的代码和基础设施越复杂,就越难理解和运行您的应用程序——从长远来看,这会降低我们的应用程序的弹性。

需要平衡弹性措施和解决方案的可理解性,以优化解决方案的整体弹性增益。


弹性工程设计的最佳点
弹性工程的最佳点在哪里,如何避免支出不足和超支。我们如何为弹性软件设计计算合理的商业案例,确定我们应该花多少钱以及何时停止?

只要适用,我更喜欢简单的解决方案。它们可能并不完美,但只要它们明智且“足够接近”,我更喜欢简单而不是任何复杂的解决方案。

关于弹性软件设计,我的建议包括两个步骤:

  • 了解有多少资金和风险处于危险之中
  • 确定投资于哪些弹性措施

定义弹性预算
第一步是了解如果您的 IT 系统出现故障,将面临多少钱。该总和通常由两部分组成:

  • 直接、直接的损失
  • 间接的、延迟的损失

直接损失是指在故障发生时你没有赚到的钱。例如,由于客户下订单所需的系统发生故障,你的客户无法下订单。根据恢复的时间,你会损失更多或更少的钱。

你也可能因为故障造成的数据丢失而损失金钱。例如,你把下的订单保存在一个数据库中。由于故障,数据库崩溃了,需要从备份中恢复。这意味着你失去了所有的更新,包括从最后一次数据库备份到故障发生时的订单,也就是说,由于数据丢失而造成的金钱损失。

如果你的公司做业务连续性规划,很有可能这些数字已经知道了。通常情况下,它们是在业务影响分析中计算出来的。搜索恢复时间目标(RTO)和恢复点目标(RPO)(例如,见维基百科上描述灾难恢复的这篇文章,以了解更多信息)。

如果你的公司没有做业务连续性规划,你可以找你的产品经理或其他业务线的所有者。他们应该能够估计出,如果发生故障,他们每单位时间会损失多少钱。

你可能还想用一些风险数字来增加这些数字。例如,一个小时或更短的停机时间可能是昂贵的。超过一小时的停机时间可能被认为是关键的,任何超过四小时的停机时间都可能使公司的生存受到威胁。3

这样的风险数字提供了有用的额外信息,因为它们指出了必须通过一切手段来减轻的不可承受的风险。

间接损失是一种延迟的、次要的影响。例如,如果你的电子商务网站经常受到可用性问题的困扰,你的客户就会感到恼火。他们中的一些人决定从你的竞争者那里购买。你的流失率就会上升。更少的客户从你这里购买。你失去了金钱。

这些损失通常不包括在业务连续性计划等方法中。他们往往专注于眼前的风险和损失。不过,你的产品经理或其他业务线的所有者应该能够对间接损失做出一些合理的估计。

把直接和间接的损失估计,用它们的发生概率来衡量,你就有了一个复原力预算的第一近似值。此外,你一路走来发现的难以承受的风险必须通过各种手段来缓解,不管它们是否符合预算。

正如所有的商业案例和预算计算一样,这不是一门精确的科学4。无论我们是否喜欢(或承认),都会涉及很多心理学。关键的一点是,所有参与方都同意这些数字,并认为它们是合理的。

确定预算花在哪里
现在你有了预算。如何花费它最好?预算并不是无限的,即使你的企业主已经理解了复原力的价值和必要性。但是,你有无穷的可能性来花费预算。

预算的计算已经包含了简单方法所需的一切,以确定以何种顺序处理哪些复原力措施。

  • 把无法承受的风险首先解决(不管它们是否符合预算)。
  • 然后按照加权损失对剩余的风险进行排序。
  • 按照这个顺序处理它们,直到你的预算用完。

这是一个非常简单的,但在决定在哪里和以何种顺序投资于复原力措施方面的IMO有用的方法。当然,如果有一些额外的因素指向一个不同的、更好的顺序,你可以调整和微调这个计划。例如,你可能会决定实施一项基础设施级别的措施来解决其中一个高风险地区的问题,另外你还可以在几乎没有成本和努力的情况下将其应用到许多低风险的地方。

你也可以使用像故障模式和影响分析(FMEA)这样的方法来更好地了解你的应用程序中哪些未被覆盖的潜在突破点可能是安全的好候选者。

或者你可以利用像混沌工程这样的经验方法来更好地了解你的应用程序的故障模式,以及它们如何影响你的业务。这也可以帮助确定如何使用你的弹性预算的优先次序。

最后,我不认为有一个单一的最佳方法来决定如何最好地使用预算。我认为有几种明智的方式,由你来决定哪种方式最适合你。

不过,还是要注意,你将需要确定优先次序。你不会有预算来同时实施你想到的所有措施--而且从经济角度来看,这样做往往甚至是无稽之谈。因此,明智地使用你的预算。

正确进行功能设计
根据我的经验,首先你应该重新审视你的功能设计。你应该评估你是如何在功能层面上对不同的应用部分进行分片和解耦的,因为这决定了你的应用可以变得多么有弹性。如果你在功能层面的耦合很紧,所有的技术措施,包括弹性模式,都不会有帮助。

在这篇文章中,让我们假设我们已经得到了正确的功能设计。请注意,通常这不是一个必然的结果。

了解你的选择
下一件事是了解你的选择--存在哪些弹性模式以及它们能提供哪些帮助。
不幸的是,一个好的和全面的集合并不存在--至少我没有意识到

 尽管如此,如果你四处寻找,你会发现这里有一些模式,那里有一些模式,很快你会发现自己有一个令人惊讶的大模式集合。


超越
虽然理解所有这些模式及其权衡(或至少是一个合理的子集)是相当有挑战性的,但紧接着的问题是。我们要用所有这些模式做什么?

我们应该实现多少个模式?其中哪些?以及我们应该如何组合它们?

这时,我们心中的工程师就会激动:我们学习所有这些新的令人兴奋的东西,所有这些迷人的模式,并理解它们如何使我们的应用程序更有弹性。我们看到它们能做什么。现在,我们想去做。我们想应用所有这些模式。我们想看看它们是如何提高应用程序的弹性的。我们想把它们全部实现!

我曾经有过一次令人印象深刻的经历,那就是如果你这样做,如果你让好奇心和冒险精神的工程师接管,会发生什么。
那是在1997年或1998年。我把一本当时还很新的 "四人帮 "设计模式的书交给了我项目中的一个高级开发人员。他非常兴奋,想用它来改进代码库。由于这是一个有用的想法,当时我没有多想什么。

我在那个项目中担任架构师和项目经理(基于个人经验的提示。永远不要做这种组合!这是个麻烦事)。因为管理项目和客户需要相当长的时间,我没有那么多的时间从事架构工作,因为我希望(也需要)。我仍然做了一些概念性的架构工作,也和主要的开发人员讨论过,但我只是偶尔在代码库中工作一下。

几周后,这位同事来找我,自豪地告诉我,他已经在我们的代码库中实现了书中的所有模式。这时,我的警钟响了。我的意思是,3个或5个模式当然可以帮助改善我们的代码库。但是所有的23个?尤其是因为我知道这本书中的一些模式在我们的代码库中没有任何意义。我们的代码库并没有为这些模式提供所需的上下文背景。

所以,我检查了代码库......结果是一团糟!我们的代码库已经变得无法理解。我们已经无法理解代码中发生了什么。太多的模式的应用--往往在错误的地方--并没有改善代码库。它导致了相反的效果。代码变得难以理解,而且很脆。

毫不奇怪,团队在稳定代码的同时也开始遇到了巨大的问题。错误的数量激增,感觉修复一个错误就会产生三个新的错误。没有人知道,如果他们应用一个变化,代码会有什么反应。简而言之:代码库已经变得不可维护了。

最后,我们需要从头开始重写整个代码库,因为这比恢复所有与模式有关的变化,同时试图保留我们在此期间添加的其他应用逻辑要更快、更安全。
(banq:这与Gof模式无关,而是初次使用模式的原因,需要重写,但是重写时你的思想境界已经比不知道设计模式时高多,至少视角多一些考量,不能因为吃了第三口馒头,前两口就否定了)

关于这个例子和它所发生的项目背景,还有很多东西可以说。很多细节都没有提到。但这里的关键信息是。

应用太多的模式是一个坏主意。

这对所有类型的模式都是如此,包括弹性模式。

(banq:为用模式而使用模式是一个坏主意,巧 是应用模式的一个关键指标,但是没有左右极端的测试,你怎么知道你的项目应该用哪些模式是合适的,所以,作者这里是正确废话)

寻找正确的剂量
这仍然给我们留下了一个问题。我们应该实施多少种模式?

坦率地说:我不认为这个问题有一个简单的答案--至少就我的理解而言。不过,我还是想根据我的经验向你提供一些想法,以引导正确的方向。(banq:这是一个事后诸葛亮的特定于上下文背景的经验,无法移植复制)

  • 模式是选项,不是义务--首先,要始终牢记,你永远不会需要你知道的所有模式。它们只是你可以选择的选项,以使你的应用程序更有弹性,这就是它们应该被视为的。
  • 每个模式都会增加应用程序的复杂性--如果你选择了太多的模式,就会影响到你最初的目标--使应用程序更加可靠,因为每个模式都会增加解决方案的复杂性。增加的复杂性使解决方案的行为更难理解,导致在运行时出现更多的意外问题。增加的复杂性也使得代码更难理解,使得维护和进化代码库更加困难,导致更多的错误,也影响了可靠性。或者正如Tony Hoare爵士在他1980年的图灵奖演讲中所说的那样。"可靠性的代价是追求最大的简单性"。
  • 每个模式在开发和运营中都要花钱 - 你还需要记住,每个模式在开发和生产中都要花钱。就比如说,想想冗余,即运行同一应用部分的几个实例,这使应用的运行时间成本成倍增加。这些开发和运行时间成本需要与你所拥有的弹性预算相平衡。

这就引出了核心建议:

不要使用太多的模式

(banq:又绕回来了)
你最终会遇到两种相互竞争的力量。一方面,你需要实施弹性模式来提高你的应用程序的可靠性。另一方面,如果你实现了太多的模式,你的应用程序的复杂性将会爆炸,导致可靠性下降。

不实施任何弹性措施,会让你因为应用程序的可用性降低以及由此带来的业务层面的影响而蒙受损失(更多细节见 "弹性软件设计的商业案例 "博文)。
实施太多的模式也会让你赔钱,因为你花在弹性措施上的钱比它们为你节省的钱还要多--而且由于前面讨论的影响,你还会因为过度的复杂性而损害你的可用性而赔钱。

因此,甜蜜点--和以往一样--是在两个极端之间。(两者取之”中“)

"恰到好处的模式 "是我们应该努力争取的。

你应该寻找互补的模式,以最大限度地扩大影响,同时保持使用的模式总数尽可能少。

就我的理解,有用的模式组合总是取决于给定的环境。一组模式在一种情况下表现非常好,但在另一种情况下却表现不佳。因此,我不能为你提供一些预先包装好的模式集合,使其总是形成一个伟大的组合。这一点很抱歉。

但为了让你大致了解这在实践中可能意味着什么,我将简要介绍两个相当知名的成功模式组合的例子。

案例1
第一个例子是关于模式的,Erlang/OTP应用程序通常会实现——至少是我知道的模式:

  • 基本构建块是通过异步消息进行通信的参与者,实现参与者Actor模型。
  • 然后 Erlang 实现了let it crash模式,这是一种特殊形式的worker-supervisor模式,由基本模式escalation、monitor和restart组成。
  • 此外,Erlang 虚拟机 (VM) 使用心跳协议来检测失败的远程 VM,从而使 Erlang 集群能够跨越多个计算节点。
  • Erlang 还将热部署作为标准平台功能实现,以最大限度地提高可用性。

这几个弹性模式使爱立信能够构建运行 Erlang 的 ATM 交换机,其可用性达到“9 个九”,这是一个令人印象深刻的数字。在 20 年的时间里,这些交换机的停机时间不到 1 秒。
诚然,底层交换机硬件具有高可用性(冗余硬件组件),实现的用例并不太复杂。但是,所实现的可用性仍然令人印象深刻——软件方面,它结合了一些很好地支持用例的弹性模式。

(banq:因果性与相关性需要区分清楚,爱立信可用性达到”9个九“的原因到底是什么?或者与软件与硬件相关,可能与硬件相关性更多一些,因为爱立信是一家硬件为主的公司,当然,不否认Actor模型也能高弹性,这也是很多人当初去学习Erlang原因,也许以后的新语言在处理Actor模型的易用性和可调试性上还有新的智慧。这也是下面Netflix没有选择Actor模型,而是选择微服务的一个原因)

案例2
第二个例子是Netflix,他们在2015年左右的模式是众所周知的,当时Hystrix和Netflix操作系统非常流行。我确信,我错过了Netflix实施的一些弹性措施,但这些模式,他们在2015年就已经很出名了。

  • Netflix选择了基于HTTP的(微)服务和请求/响应通信作为其基本构建模块。
  • 由于HTTP的同步性,他们在延迟管理方面做了很多努力,捆绑在他们的Hystrix库中(同时处于维护模式):超时、断路器、重试、有界队列、限制和回退,以优雅地降低其服务质量。
  • 他们在许多地方使用冗余:使用共享负载模式的自动缩放只是他们的冗余使用之一,但仍然是最受欢迎的之一,因为Netflix因在高峰期造成美国互联网下行流量高达1/3而闻名,这只有在优秀的自动缩放实现下才能发挥作用。
  • 他们在实施伟大的监控方面投入了大量的精力,以始终了解他们巨大的分布式系统景观的状态。
  • 他们在金丝雀版本和滚动部署的基础上实现了零停机时间的部署。
  • 最后,他们曾经(现在仍然)以他们的模拟混乱测试(现在已经退役)而闻名,他们在生产环境中运行时进行各种错误注入,以确保他们的弹性措施不仅在理论上有效,而且在实践中也是如此。

Netflix的模式列表比Erlang/OTP的列表要长一些。但Netflix实现的用例也比爱立信ATM交换机的用例更复杂。另外,Netflix建立在基于HTTP的同步通信之上,如果你的目的是为了实现真正的高可用性,就需要全面的延迟管理。

(banq注:作者没有提到为何Netflix的微服务用例复杂?那是他从上帝视角来看,确实复杂了,但是作为身在其中的一名程序员,他的认知负荷降低了,只需要按照常识编写自己认知范围内的微服务即可,不必按照异步消息模型怪异地编写Actor模型实现,而且消息传递的调试非常,但是如果将Netflix这些工作由一两个团队去实施,确实复杂了,这是一个系统工程,是全公司全组织参与的过程,其中没有一个人能够或者在认知中全部掌握这些复杂系统工程,这才是软件工程,否则几个关键架构师都全部掌握了,那还是软件作坊。上帝视角让自己自大,从而形成与公司抗衡,人的认知范围和负荷是有限的)

同时,Netflix继续前进,实施不同的、更复杂的弹性措施。这并不奇怪,因为我在这里展示的模式是Netflix在大约十年前实施的。他们有十年的时间来不断学习和改进。

尽管如此,我认为这份清单仍然非常有用,因为Netflix十年前所做的事情仍然比今天大多数公司在应用弹性方面所做的要多得多。所以,这个清单仍然可以作为许多公司的一个良好的起点。

总结
为您的应用程序找到合适的弹性权衡设计并不容易。我们知道在弹性软件设计上投入太少是一个坏主意——尤其是在当今高度分布式的系统环境中。但是添加太多模式也是一个坏主意。它不仅要花太多钱,还会使系统环境过于复杂,从而降低可靠性和脆弱性。

因此,找到正确的平衡点是关键:
实现尽可能少的模式,但不能少。

( banq: 要实现这个目标,首先你需要学习所有模式,并熟练掌握它们,然后才知道如何使用尽可能少的模式,这是一个认知知识掌握的基础常识)

正确数量的模式取决于您的上下文和用例,不存在一刀切的建议。但正如 Erlang/OTP 和 Netflix 的两个示例所示,您无需实现数十种模式即可创建高可用性和可靠的系统。