您的微服务是分布式整体吗? -软件工程日报


转移到微服务不仅涉及将整体应用程序重新包装到容器中。架构上存在根本差异,影响到从传输数据到故障恢复的所有方面。无法解决这些差异可能导致可扩展性受限,性能下降以及意外中断。
您的团队已决定将您的整体应用程序迁移到微服务架构。您已经对业务逻辑进行了模块化,对代码库进行了容器化,允许开发人员进行多语言编程,用API调用替换了函数调用,构建了Kubernetes环境,并微调了部署策略。
但是在部署之后不久,您就开始注意到问题。服务需要很长时间才能启动,故障从一个容器级联到另一个容器,小的更改涉及重新部署整个应用程序。微服务不应该解决这些问题吗?
如果听起来很熟悉,那么您可能已经陷入了常见的微服务反模式:分布式整体(distributed-monolith)。在本文中,我们将解释什么是分布式整体,为什么要避免使用它们,以及如何使用Chaos Engineering来验证您的应用程序是否属于这种反模式。
 
分布式整体,为什么不好?
分布式整体是一种像微服务一样部署但却像整体一样构建的应用程序。它利用Kubernetes之类的平台和分布式系统架构,但无法高效或可靠。
在单体/整体(monolith)架构中,整个应用程序被捆绑到一个单独的程序包中,该程序包包含源代码,库,配置以及运行它所需的所有其他依赖性。整体结构并不是天生就不好,但是它们有局限性:

  • 部署工件通常很大,启动速度很慢,并且会占用大量资源。
  • 更改应用程序的一部分意味着重新构建和重新部署整个应用程序,从而降低了开发人员的工作效率。
  • 水平缩放效率不高,因为整体不是为分布式系统设计的。

相比之下,微服务将应用程序分为离散的单元,这些单元具有明确定义的服务边界,资源密集度较低且易于扩展。这些好处是以部署复杂性,管理开销和可观察性挑战为代价的。除了管理我们的应用程序之外,我们现在还需要管理图像和容器,编排工具,网络和安全策略,分布式数据等等。
有了分布式整体,我们将拥有整体的繁重和灵活性,微服务的复杂性以及这两种体系结构的诸多好处。我们的部署速度仍然很慢,可伸缩性很差,但是现在我们增加了运维的复杂性并消除了服务之间的隔离。
我们的工程师需要学习新的体系结构,采用新的工具并重建其应用程序,以适应动态变化的容器世界,从而增加了大量的时间和劳力。
您如何判断微服务是否是分布式整体,这对您的开发策略和应用程序可靠性意味着什么?我们将列出此反模式的一些共同特征,并向您展示如何使用Chaos Engineering测试它们。
 
标志#1:服务紧密耦合
耦合是指两个功能之间的可分离程度。在整体中,由于共享公共代码库并在相同的处理空间中运行,因此不同的功能紧密耦合。紧耦合可以采用不同的形式,例如:
  • 要求依赖项可用于完成任务(行为耦合)。
  • 要求与其他服务进行快速,低延迟的通信(时间耦合)。
  • 由于更改单个服务(实现耦合)而必须更改多个服务。

考虑一个电子商务应用程序。当客户查看产品页面时,我们需要:
  • 查询数据库以获取有关产品的信息。
  • 在后端处理该信息。
  • 在页面上渲染它。

使用传统的整体应用程序,我们可能会创建一个模型视图控制器(MVC)框架,以逻辑方式将后端逻辑(获取产品详细信息)与前端逻辑(呈现网页)分离。这是由两个不同的开发团队开发的两个不同的功能,但是它们属于相同的整体代码库。如果不对另一个做同样的事情,我们将无法部署或修改一个。
现在,让我们将该应用程序重组为微服务。我们将创建两个服务:一个用于前端,另一个用于产品目录。我们将用API调用替换直接函数调用,并通过网络连接服务。一切看起来都不错,并且运行良好,但是随后我们的产品目录服务崩溃了。前端发生了什么?尽管出现故障,它是否仍可以继续工作,还是我们的服务如此紧密地耦合在一起,以至于前端也出现故障?如果我们的后端团队将修复程序部署到产品目录,是否还需要将修复程序部署到前端?最重要的是,发生这种情况时,客户会感到什么?
 
如何测试紧密耦合?
在测试紧密耦合时,我们需要确定更改一项服务的状态是否会影响另一项服务。为了说明这一点,我们将使用一个基于微服务的开源电子商务网站Online Boutique。Online Boutique使用十项独特的服务来提供诸如前端,付款处理和购物车管理之类的功能。我们要验证产品目录是否与前端分离。
我们将使用企业SaaS Chaos Engineering平台Gremlin进行此测试。我们将创建一个混乱的实验,这是有意,主动地将伤害注入到工作系统中,以测试其恢复能力,以达到改善的目的。通过故意在产品目录服务中造成故障并观察对前端的影响,我们将了解这些服务的紧密耦合或松散耦合。
我们可以通过降低超时阈值或使我们的API调用异步来采取快速措施来减轻耦合这种情况,以便我们的前端在响应用户之前不必等待产品目录。通过使用域驱动设计(DDD)在微服务之间设置明确定义的边界(称为有界上下文),我们可以降低与其他服务紧密耦合的风险。开始向微服务迁移之前,最好执行此步骤,但是即使开发已经开始,它仍然是有益的。
 
标志#2:服务扩展不容易
假设我们的网站流量激增,我们需要水平扩展前端以处理增加的连接数。对于整体,我们需要部署整个应用程序及其所有依赖项的新实例。与真正的微服务相比,此过程不仅复杂而且容易失败,而且花费的时间更长且使用资源的效率更低。
在测试分布式整体时,我们想知道应用程序扩展所需的时间,扩展时潜在的故障点以及对其他服务(特别是依赖项)的影响。
对于此测试,我们将使用Bitnami Helm图表WordPress(一种开源CMS)部署到Kubernetes集群。此图表将WordPress应用程序部署在一个Pod中,并将其附加的MariaDB数据库部署在另一个Pod中。
请注意新Pod的部署需要多长时间。将流量平衡到新Pod的负载后,请监视数据库上的负载量。在需要扩展数据库的地方,是否还有足够大的增加?如果是这样,您可能有一个分布式的整体。
无论您是否认为应用程序都是整体应用程序,这都是一个有用的实验,因为它可以帮助您为流量高峰​​做准备,并在意外负载期间保持较高的吞吐量。

标志#3:服务饱和
由于增加了网络呼叫的延迟,因此在分布式系统之间共享数据具有挑战性。在整体中,数据几乎可以在各个功能之间即时流动。但是在微服务中,服务之间的广泛通信将大大降低应用程序的吞吐量,可能会导致超时和其他意外故障。
例如,WordPress使用MySQL(或MariaDB)数据库存储数据,例如用户帐户,页面内容,配置设置等。一次页面加载可以生成多个MySQL查询。因此,WordPress最好与低延迟(理想情况下是本地)数据库连接一起使用,但这会在两个服务之间造成时间依赖性。
在Kubernetes中,一种选择是将WordPress和MySQL一起部署在同一Pod中。但这会创建一个Pod,该Pod具有多种功能,需要大量的系统资源,启动时间更长,需要数据复制和同步,并且由应用程序和数据库团队共同拥有。换句话说,它是作为微服务打包的整体。
在我们的网络饱和或降级之前,将两个服务联网似乎不是问题。使用Chaos Engineering,我们可以看到这可能会对我们的应用程序产生影响。
为了减轻延迟的影响,请减少服务之间的往返网络调用次数。考虑使调用异步,或使用消息队列(如Apache Kafka)将这些服务解耦。与紧密耦合一样,这是为什么要使用领域驱动设计将服务和数据拆分到不同域的另一个很好的例子。