Microservices-Not A Free Lunch!

最近入职了新的公司,新公司所使用的技术栈和我之前接触的东西有很多不同,现在开始要疯狂补课了:),本文是翻译自Benjamin Wootton在 2014 年发表的一篇文章,主要阐述了微服务的一些优势以及因此带来的劣势。

微服务,不是免费的午餐

微服务是一种软件架构,它将系统作为一系列非常小的,细粒度的,相互独立且互相合作的服务交付。

虽然微服务并不是一个全新的概念,但是近几年 (2014) 却变得格外火爆,在各类社交媒体、文章推特中都宣扬使用这种软件架构构建系统的优势。

这种流行趋势在一定程度上得益于云计算、DevOps 和持续交付,部分原因是 Netflix 这类大公司使用了这种模式并获得了很好的成果。

首先声明,我是微服务架构的粉丝,它确实带来了很多真实且有意义的好处:

  • 每个服务本身都非常简单,且专注于将一件事情做好;
  • 每种服务都能够使用最合适的方式和工具构建;
  • 使用这种方式构建的系统本身耦合度低;
  • 在这个模型下多开发者和多团队能够相对独立地交付自己的部分;
  • 它是持续交付的极大促成者,能够在保持其余系统可用稳定的情况下频繁地更新某个 releases;

但是,微服务并非免费的午餐!

我目前在参与一个使用微服务的系统的架构设计,由于每个服务都非常简单,就管理这些服务和在整个服务中编排业务流程而言,在更高的级别上存在很多复杂性。

微服务的一些想法在尝试的时候非常好,但是一旦面对现实问题,很多复杂的情况就会出现。因此,我想要写下这篇文章,描述一些现象并尝试维护平衡。

重大的操作支出

微服务架构通常带来更大的操作支出。

当一个集成的应用被分成多个小的应用服务组成的集群,你就需要构建、测试、部署、运行成百上千的分离的服务。

所有的这些服务还可能需要一个小集群用于故障切换和弹性恢复,将你的单一集成系统变成由 40-60 个进程组成的 20 个服务。

在这些微服务之间加入负载均衡和消息处理层使得整个系统相较于交付同等性能的单一集成系统更为庞大。

运行这样的微服务架构应用需要高质量的监控和操作基础架构。保持一个应用服务运行可是一个全职工作,但是现在我们要确保成百上千个进程运行,不要耗尽磁盘空间,不要死锁,保持效率。这是一项令人生畏的工作。

在物理上,通过您的管道将这些过多的微服务交付到生产环境中,还需要非常高的健壮性、发布和部署自动化程度。

目前,在框架和开源工具方面还没有太多的东西可以从操作的角度支持这一点。因此,推出微服务的团队可能需要在自定义脚本或开发方面进行重大投资,以便在编写交付业务价值的代码行之前管理这些流程。

操作是对模型最明显和最常见的反对意见,尽管它很容易被这种体系结构的支持者所忽视。

需要大量的 DevOps 技能

如果一个开发团队能够提出一个 Tomcat 集群并使其可用,那么保持微服务的可用性所面临的操作挑战就意味着您肯定需要在开发团队中嵌入高质量的 DevOps 和发布自动化技能。

隐式接口

一旦将系统分解为协作组件,就在它们之间引入了接口。接口充当契约,双方需要交换相同的消息格式,并对这些消息具有相同的语义理解。

更改契约一方的语法或语义,所有其他服务都需要理解该更改。在微服务环境中,这可能意味着简单的横切更改最终需要更改许多不同的组件,所有这些组件都需要以协调的方式发布。

当然,我们可以通过向后兼容的方法来避免这些变化,但是您经常会发现业务驱动的需求无论如何都禁止分段发布。例如,发布一个新的产品线或一个外部强制的法规变更可以迫使我们一起发布大量的服务。这表示由于集成点的存在,在替代的单片应用程序上存在额外的发布风险。

如果我们让协作服务向前发展并变得不同步 (也许是金丝雀式的发布风格),那么更改消息格式的效果就会变得非常难以想象。

再次声明,bckwards 的兼容性并不是万能药,正如微服务倡导者所说的那样。

重复的劳动

假设有一个新的业务需求,需要对某个产品线使用不同的方式计算税收。我们有几个选择来实现这一点。

我们可以引入一个新服务,并允许其他服务在需要时调用它。然而,这确实会将更多潜在的同步耦合引入到系统中,所以我们不会轻易做出这样的决定。

我们可以重复这项工作,将税务计算添加到所有需要它的服务中。除了重复的开发工作,以这种方式重复我们自己通常被认为是一个坏主意,因为代码的每个实例都需要继续测试和维护。

最后一种选择是在服务之间共享资源,如计算税收的库。这可能很有用,但它并不总是在多语言环境中工作,并引入了耦合,这可能意味着必须并行地发布服务,以维护它们之间的隐式接口。这种耦合本质上减少了微服务方法的很多好处。

在我看来,这三个选项都是次优的,而不是一次性编写一段代码并使其在整个单片应用程序中可用。我所见过的以这种方式工作的团队倾向于选择 2,复制业务逻辑,这违背了许多优秀软件工程的原则。是的,这甚至发生在分解良好和设计良好的系统中——这并不总是糟糕的服务边界的标志。

分布式系统的复杂性

微服务架构意味着分布式系统,以前我们可能有一个作为子系统边界的方法调用,而现在我们引入了大量的远程过程调用、REST APIs 或者消息去将不同的进程和服务粘合在一起。

一旦我们分发了整个系统,我们需要考虑一系列我们以前没有考虑过的问题。网络延迟、容错、消息序列化、不可靠的网络、异步、版本化、应用程序内部的负载变化等等。

对其中一些进行编码是一件好事。向后兼容性和优雅的降级是很好的特性,我们可能没有在单片替代中实现这些特性,这有助于保持系统正常运行,并且比单片应用程序具有更高的可用性。

然而,这样做的代价是应用程序开发人员必须考虑所有这些他们以前不需要考虑的事情。分布式系统的开发和测试难度要大上一个数量级,因此构建不吸引人的单片应用程序的难度再次提高

异步性是困难的

更具前面的观点,采用微服务方式构建的系统更倾向于异步。依赖消息传递和并行来交付它们的功能。

当我们能够将工作分解成顺序无关的任务时,使用异步系统是一种更好的选择。

但是,当事情需要同步进行或者在异步体系中需要进行事务处理,随着我们需要管理相关 id 和分布式事务来将各种操作联系在一起,事情变得复杂起来。

可测试性的挑战

如此多的微服务在以不同的步调进化,不同的服务在内部发布 canary 版本,无论是手动测试还是自动测试,都很难以一致的方式重新创建环境。当我们添加异步性和动态消息负载时,就很难测试以这种方式构建的系统,也很难对即将发布到生产环境中的服务集有信心。

我们能够测试独立的服务,但是在动态的环境中,非常微妙的行为可以从服务的交互中产生,这很难想象和推测,更不用说全面的测试了。

惯用的微服务较少地强调测试,而更多地强调监视,因此我们能够定位产品中的异常并且快速回滚或者采取恰当的措施。我非常相信这种方法——降低发布的障碍,通过持续交付来加速精益交付。然而,作为一个在发布之前已经花了数年时间应用测试自动化来获得信心的人,任何降低这种能力的东西都是要付出高昂代价的,特别是在风险厌恶的受管制的环境中,bug 可能会产生重大的影响。

最后

这些都是我早期构建运行基于微服务的系统时所遇到的问题。

我依旧是微服务的粉丝,我相信对正确的项目和正确的团队来说这是一个能美妙的架构,相比额外的消耗能够带来更多的好处。

但是,当你考虑类微服务架构的时候,重要的是不要被这方面的炒作所吸引,因为挑战和成本与收益一样真实。