Mrwind 2019-01-19
关键要点
写这篇文章的起因是我在 2018 年GOTO 柏林大会上所做的演讲,我在演讲中分享了在进行弹性软件设计时会面临哪些挑战。我将简要地介绍弹性软件设计的“why”和“what”,中间部分是我近年来经常遇到的挑战,最后,我添加了一些关于如何在组织中实现弹性软件设计的最佳实践。阅读完本文之后,希望你能够更好地了解弹性软件设计所面临的挑战,并知道如何解决这些问题。
弹性软件设计(Resilient Software Design,简称 RSD)是一个无法用一两句话解释清楚的概念——有时候很难向习惯于“电梯游说”(在很短时间内说清楚一个问题)的人解释这个概念。尽管如此,我仍然试着尽可能简短地解释我所知道的 RSD,一个是“why”,一个是“what”。
让我先从“why”开始。通常,我们试图通过系统来实现某种商业价值:赚钱或者让客户满意。显然,只有当系统在生产环境中看用时,我们才有可能实现这一价值。这一直以来都是这样的。
但是,现在有所不同的是,几乎每个系统都是分布式的。系统之间相互通信,并且系统本身也可能被分成几个部分,而这些部分进一步被拆分,并以此类推。微服务、移动计算和物联网的发展使得协作系统之间的连接变得更加复杂,将这些系统的开发提升到新的水平。
系统及其部件之间相互交互所需的远程通信会引入一些故障模式,这些故障模式只存在于跨进程边界的情况,不会在进程内部出现。如果我们忽略它们的存在,这些故障模式,例如非响应性、等待时间、不完整或无序消息将在应用程序级别上引起所有类型的非预期故障。换句话说,如果你需要一个健壮高可用的系统,就不应该忽略分布式所带来的影响。
这让我想到了 RSD 的“what”:我倾向于将弹性软件设计定义为“在理想情况下,如果发生意外故障(由分布式的非确定性行为造成的),用户根本不会注意到,或者用户至少可以继续使用应用程序已定义范围的部分功能(即服务的优雅降级)”。
请注意定义中的“已定义”一词。这意味着,计划在发生意外情况时该做些什么,这个与你通常在不采用弹性软件设计时遇到的意外系统行为有很大不同。
我的旅程通常从在代码级别设计和实现弹性开始。但随着时间的推移,我意识到,在代码层面实现弹性是最容易的部分,实际的挑战其实是在其他方面。
虽然在事后看来,这并不足为奇(大型项目的问题从来都不是在编码层面),但我不得不承认,我刚开始还是感到有些奇怪。你只有长期接触这个问题,才有可能清楚地了解这些挑战。因此,我在“弹性软件设计的 7 个任务”演讲中分享了我的一些经验,其中每个“任务”代表了我遇到过的一个挑战。以下是从我的演讲中挑选出来的挑战。
当然,还有其他更多的挑战,但从我的角度来看,这些是最应该引起我们注意的。
根据我的经验,理解分布式系统以及如何进行好的功能设计是创建健壮应用程序的最大障碍。因此,让我们更深入地探讨这两个主题。
理解分布式故障模式的含义是非常困难的。单进程系统中一些很简单的事情到了分布式系统中有会变得非常困难,甚至是不可能的,而且基础设施无法为应用程序隐藏掉所有这些影响。另一方面,大多数(如果不是全部)大学和大学后的 IT 教育都是基于本地计算。此外,试图掌握分布的非确定性影响对我们的大脑来说并不是一件容易的事。
根据我的经验,IT 领域之外的大多数人几乎不可能真正理解分布式,因为他们把计算机理解为“可以完成人类布置的任务的机器”,教会他们有关分布式计算的概念和所面临的挑战需要很长时间——我们通常没有那么多时间。
但其实对大多数开发人员来说也是非常困难的。开发人员在面对不可用的分布式系统时,他们通常也会不知所措。由于他们的 IT 教育完全忽略了分布式系统,他们甚至会避免处理与分布式有关的问题。这导致在设计和实现系统时忽略了分布式的影响,而这反过来让系统变得既脆弱又慢。
我之前说过,我不知道这个问题有什么简单的解决方案,我其实认为并不存在所谓的简单的解决方案。我能做的就是建议在我们的 IT 教育(大学期间和大学之后)中增加更多有关分布式系统设计的课程,因为我们的系统环境变得越来越分散,我们需要更好地了解设计和编码的实际效果。
另一个巨大的阻碍是功能设计。如果功能传播的方式是错误的,最终只会得到一个脆弱的系统。举个简单的例子:服务 A 接收外部请求,为响应该请求,它需要来自服务 B 的一些信息,这也就是所谓的在服务之间传播功能。如果服务 B 被关闭,服务 A 就无法响应外部请求。
这就是所谓的级联故障。弹性软件设计的主要任务之一是避免级联故障。通常,你可以使用简单的超时检测机制或断路器来检测服务 B 是否已关闭,然后回退使用服务 A 的备份计划。但由于功能在服务之间传播,不可能有备份计划,即断路器只会让级联故障可见,不会提供任何绕过它的方法。
这只是其中的一个例子,类似的情况还有很多。如果你将通常的“设计最佳实践”应用于分布式系统,通常会出现这类问题。在给定的示例中,服务 B 是“可重用服务”。可重用性是单进程的理想属性,但它也会带来非常强的耦合性,在跨进程的环境中表现出非预期的特性。
在过去,如果我们的功能设计出现错误,到最后系统会变得难以维护——这已经够糟糕的了。但是,在分布式系统中,糟糕的功能设计在运行时就会体现出脆弱性、不可靠和性能问题,这个更糟糕。问题是大多数有关如何做出“正确设计”的建议只适用于进程内设计。如果将这些建议应用在分布式系统上,大多数都无法正常工作。
我从过去的经历中学到的是,我们需要重新学习如何设计系统,即如何在分布式环境中传播功能。
然后,大多数人会提到领域驱动设计(或简称“DDD”),但根据我的经验,这也不是灵丹妙药。不要误会我的意思,实际上,DDD 为更好的设计提供了很多非常好的建议。但是当谈到分布式系统的设计时,单靠 DDD 是不够的,它还缺少了一些额外的建议。好的方面是:据我所知,人们正在尝试扩展 DDD 的原始思想,加入分布式系统因素。因此,我对这一领域的未来发展非常期待。
如果你想将 RSD 引入到你自己的公司,你可能会要求制定一个可以达到最佳效果的计划。根据我的经验,并不存在完美的计划。我的建议是实现通用的“意识—能力—可持续性”模式。
首先,了解为什么需要 RSD 以及如何将其传达给不参与软件开发的人员。这涉及理解和接受分布式系统的不可用性(包括避免“100%可用性”陷阱)和弹性软件设计的业务案例。此外,你还需要学习如何在不使用深奥的 IT 知识的情况下将其传达给人们。即使你知道需要通过 RSD 来构建健壮的系统,但却不能与你的经理或你的企业主讨论它,并帮助他们更好地理解这个主题,然后做出正确的决策,那么这一切将无济于事。
获得知识可能是最容易的部分。同时,还可以找到一些有关这个主题的资源和培训——只需要注意与分布式系统或微服务相关的会议的研讨会部分,或者从阅读文末参考部分提供的两本书开始。当然,你需要在工作中应用它们。再强调一下,做出正确的功能设计是一项艰巨的任务,但弹性模式本身相对容易学习和应用。
要建立可持续性,首先需要一个有效的 OpsDev 反馈循环。如果没有这种循环,任何弹性倡议都将注定失败,因为你的弹性度量在实践中缺少了反馈。
此外,你可能希望建立混沌工程计划。混沌工程不仅有助于揭示系统缺陷,它还通过持续、可控的学习(了解系统的健壮性并进一步提高系统健壮性)帮你实现可持续的弹性。
“混沌工程”这个词有点容易被误解,它不是为了制造混乱,而是为了避免混乱。在混沌工程中,你可以设计受控的实验,以便更好地了解系统的实际健壮性以及需要做出哪些额外的弹性措施。混沌工程师总是小心翼翼地控制实验潜在的影响范围,并在执行实验之前与所有受影响的人进行沟通。
它从一个假设开始,例如“如果我们切断与此服务器的连接,将发生自动故障转移,最终用户不会察觉到任何差异”。然后,与受影响的开发人员和运营人员讨论该假设。假设是有效的吗?我们怎么测试它呢?我们如何衡量正确性?如果我们错了,怎样才能以安全的方式停止实验?
在讨论和定义好所有内容之后,就可以进行实验。根据试验结果,可能需要定义(RSD)度量。除了帮你找出应用程序中之前未被发现的问题之外,混沌工程也会显著提高你对系统的信心——这是一种很好的感觉。
总的来说,在今天的分布式系统环境中,RSD 是一个必选项。虽然学习如何设计和实现弹性模式相对容易,但 RSD 所面临的实际挑战通常不在于编码方面。
分布式系统本身的复杂性和分布式系统的功能设计让实现可持续的弹性变得更加困难。同时,Ops 和 Dev 之间缺少反馈循环、过于复杂的弹性设计,或者缺乏对 RSD 业务案例的理解,等等,通常都会带来阻碍。不过,了解挑战是成功掌握它们的第一步……
参考
关于作者
Uwe Friedrichsen在 IT 领域有多年经验。作为 codecentric(https://codecentric.rs/)的 CTO 和合伙人,他总是在寻找创新的想法和概念。他目前关注的领域是(分布式)系统设计、深度学习和未来的 IT。通常,你可以在一些分享大会上看到他的身影。他也喜欢写文章、发推文,等等。
查看英文原文:https://www.infoq.com/articles/towards-resilient-software-design