什么时候应该转向微服务?


什么时候仍然选择微服务是正确的?

微服务架构是一种将软件拆分成小型独立服务组成的架构,它可以提供更好的扩展性和快速开发能力。

微服务需要按照业务功能划分,实现自动化部署和独立部署,还需要具备封装、去中心化、故障隔离和可观察性等特点。

但是,在实践中处理微服务事务状态和全局复杂性是比较困难的。因此,在决定是否采用微服务时,需要考虑到这些因素并进行权衡。

什么是微服务
微服务是一种软件开发的架构和组织方法,其中软件由通过明确定义的 API 进行通信的小型独立服务组成。这些服务由小型、独立的团队拥有。
微服务架构使应用程序更容易扩展并更快地开发,从而实现创新并加快新功能的上市时间。

一个常见的误解是微服务只是分解的单体:

  • 单体应用的问题(和优点)是单点故障。
  • 如果我们将单体服务分解为更小的部分,我们实际上会创建分布式故障点。如果链条中的某一部分发生故障,整个架构就会崩溃。
  • 微服务不是小型的单体架构,分解单体架构不仅仅涉及较小的项目。这是关于改变我们的工作方式。

是什么造就了微服务?
一个好的微服务需要遵循以下稳健性和可扩展性原则:

  • 按业务功能划分——这是一个逻辑划分。微服务是一个独立的“产品”,提供完整的包。这意味着负责微服务的团队可以在没有依赖关系的情况下进行业务所需的所有更改。
  • 通过 CI/CD 实现自动化——如果没有持续交付,更新成本将消除微服务的所有优势。
  • 独立部署——这是隐含的,因为对一个微服务的提交只会触发该特定服务的 CD。我们可以通过 Kubernetes 和基础设施即代码 (IaC) 解决方案来实现这一目标。
  • 封装——它应该隐藏底层的实现细节。服务充当独立产品,为其他产品发布 API。我们通常通过 REST 接口以及消息中间件来完成此任务。API 网关进一步增强了这一点。
  • 去中心化,没有单点故障——否则,我们就会分散故障。
  • 故障应该被隔离——否则,单个服务的故障可能会产生多米诺骨牌效应。断路器可能是隔离故障最重要的工具。为了满足这种依赖性,每个微服务都会处理自己的数据。这意味着许多数据库有时可能具有挑战性。
  • 可观察——这是处理大规模故障所必需的。如果没有适当的可观察性,我们实际上是盲目的,因为各个团队可以自动部署。

这些在实践中意味着什么
它的大部分含义是,我们需要对处理一些重大想法的方式做出一些重大改变。我们需要将更多的复杂性转移给 DevOps 团队。我们需要以不同的方式处理跨微服务的事务状态。这是在处理微服务时最难掌握的概念之一。这是在处理微服务时最难掌握的概念之一。

在理想情况下,我们的所有操作都将非常简单,并包含在一个小型微服务中。围绕微服务的服务网格框架将处理所有全局复杂性,并为我们管理单个服务。但现实世界并非如此。在现实中,我们的微服务可能会在服务之间传输事务状态。外部服务可能会失败,为此,我们需要采取一些独特的方法。

依赖 DevOps 团队
如果您的公司没有优秀的 DevOps 和平台工程团队,微服务就不是一种选择。我们可能会因为迁移而部署数百个应用程序,而不是部署一个应用程序。虽然单个部署是简单和自动化的,但您仍将在运营方面投入大量工作。

当某些东西无法工作或无法连接时。需要集成新服务或采用服务配置时。在使用微服务时,运营部门的负担会更重。这需要良好的沟通和协作。这也意味着管理特定服务的团队需要分担一些运营服务的负担。这并不是一项简单的任务。

作为开发人员,我们需要了解许多用于将独立服务绑定到单一统一服务的工具:

  • 服务网格让我们可以将不同的服务结合起来,并有效地充当它们之间的负载平衡器。它还提供安全、授权、流量控制等功能。
  • 应使用 API 网关而不是直接调用 API。这有时可能会很麻烦,但对于避免成本、防止速率限制等来说,这往往是必不可少的。
  • 功能标志和秘密在单体中也很有用。但在微服务规模中,如果没有专用工具,就无法对其进行管理。
  • 断路功能可让我们杀死断开的网络服务连接并优雅地恢复。如果没有这个功能,单个中断的服务就可能导致整个系统瘫痪。
  • 身份管理必须独立。在处理微服务环境时,数据库中的身份验证表是不可或缺的。

我将跳过协调、CI/CD 等工具,但它们也需要针对每项服务进行调整。其中一些工具对开发人员来说是不透明的,但我们在所有阶段都需要 DevOps 的帮助。

Saga模式
无状态服务是最理想的,因为携带状态会使一切变得更加复杂。如果我们将状态存储在客户端,就需要一直来回发送。如果将状态存储在服务器上,我们就需要不断获取、缓存或本地保存状态,然后所有的交互都要针对当前系统进行。这样一来,系统的可扩展性就不复存在了。

典型的微服务将存储在自己的数据库中,并处理本地数据。需要远程信息的服务通常会缓存一些数据,以避免往返其他服务。这也是微服务可以扩展的最大原因之一。在单体中,数据库应成为应用程序的瓶颈,这意味着单体的效率受限于我们存储和检索数据的速度。这有两个主要缺点:

  • 大小--数据越多,数据库就越大,性能也会同时影响到所有用户。试想一下,为了查找您的具体购买记录,您需要查询亚马逊上所有购买记录的 SQL 表。
  • 领域 - 数据库有不同的使用情况。有些数据库针对一致性、写入速度、读取速度、时间数据、空间数据等进行了优化。跟踪用户信息的微型服务可能会使用针对时间相关信息进行优化的时间序列数据库,而购买服务则会侧重于传统保守的 ACID 数据库。

请注意,单体可以使用一个以上的数据库。这可以很好地工作,而且非常有用。但这只是例外。而不是规则。

Saga模式的工作原理是,如果传奇失败,则使用补偿事务来消除传奇的影响。当传奇失败时,补偿事务会被执行,以撤销前一个事务所做的更改。这样,系统就能从故障中恢复并保持一致的状态。

我们可以使用 Apache Camel 等工具来实现这一目标,但这并非易事,所需的参与度远高于现代系统中的典型事务。这意味着,对于每一个重要的跨服务操作,你都需要进行等效的撤消操作来恢复状态。这并非易事。

了解 Saga 最重要的一点是,它避开了经典的 ACID 数据库原则,而专注于 "最终一致性"。这意味着操作将使数据库在某个时刻达到一致状态。这是一个非常困难的过程。想象一下,只有当系统处于不一致状态时,才能调试出问题...

下面概括地展示了这一想法。假设我们有一个转账流程。

  • 对于转账,我们需要先分配资金。
  • 然后我们验证收件人是否有效且存在。
  • 然后我们需要从我们的账户中扣除资金。
  • 最后,我们需要将钱添加到收款人的帐户中。

这就是一次成功的交易。对于常规数据库,这将是一个事务,我们可以在左侧的蓝色栏中看到这一点。但如果出现问题,我们需要运行相反的过程。
  • 如果分配资金失败,我们需要取消分配。我们需要创建一个单独的代码块来执行分配的逆操作。
  • 如果验证收件人失败,我们需要删除该收件人。但随后我们还需要删除分配。
  • 如果扣除资金失败,我们需要恢复资金、删除接收者并删除分配。
  • 最后,如果向接收者添加资金失败,我们需要运行所有撤消操作!

CAP 定理说明了 Saga 中的另一个问题。CAP 代表一致性、可用性和分区容错性。问题是我们需要选择任意两个......别误会我的意思,你可能拥有全部三个。但万一失败,你只能保证两个。

  • 可用性意味着请求收到响应。但不能保证它们包含最新的写入内容。
  • 一致性意味着每次读取都会收到最新的错误写入。
  • 容错性意味着即使许多消息在此过程中丢失,一切都会继续工作。

这与我们历史上处理交易事务失败的方法有很大不同。

我们应该选择微服务吗?
希望您现在已经了解正确部署微服务有多么困难。我们需要做出一些重大妥协。这种新方法并不一定更好,在某些方面反而更糟。但是,微服务的支持者还是有道理的,我们可以通过微服务获得很多好处,也应该关注这些好处。

我们在前面提到了第一个要求:DevOps。拥有一支优秀的 DevOps 团队是考虑微服务的先决条件。我曾看到一些团队在没有 OPS 团队的情况下,试图通过黑客手段来解决这个问题,结果他们花在操作复杂性上的时间比编写代码的时间还多。这样的努力是不值得的。

微服务的最大优势在于团队。
因此,拥有一个稳定的团队和范围至关重要。将团队拆分成独立工作的垂直团队是一个巨大的好处。世界上最模块化的单体也无法与之抗衡。当我们拥有数百名开发人员时,仅跟踪 git 提交和代码变更的规模就会变得难以维持。微服务的价值只有在大型团队中才能实现。这听起来很有道理,但在初创公司的环境中,情况会突然发生变化。我的一位同事在一家初创公司工作,该公司拥有数十名开发人员。他们决定采用微服务架构,并构建了大量的微服务......后来,他们缩编了,用多种语言维护几十种服务成了一个问题。

拆分单体很难,但可以做到。将微服务统一到单体上可能更难,我不知道有谁认真尝试过,但我很想听听他们的故事。

规模化
为了转向微服务架构,我们需要转变一下思路。数据库就是一个很好的例子。用户跟踪微服务就是一个很好的例子。在单体架构中,我们会将数据写入表格,然后继续工作。但这是有问题的...

随着数据规模的扩大,用户跟踪表最终会包含大量数据,很难在不影响操作系统其他部分的情况下进行实时分析。有了微服务,我们可以提供以下几个优势:

  • 微服务的接口可以使用消息传递,这意味着发送跟踪信息的成本将降到最低。
  • 跟踪数据可以使用时间序列数据库,这对本用例来说效率更高。
  • 我们可以对数据进行流式处理和异步处理,以便从数据中获取更多价值。

这里有一些复杂的问题,数据将不再本地化。因此,如果我们以异步方式发送跟踪数据,我们就需要发送所有必要的数据,因为跟踪服务无法返回原始服务获取额外的元数据。但它也有本地化的优势,如果有关跟踪存储的规定发生变化,就会在一个地方存储这些数据。

动态控制和发布
您是否曾按下一个按钮而导致生产中断?

我按过,而且不止一次(太多次了)。这种感觉糟透了。微服务仍然可能在生产中失败,仍然可能出现灾难性的故障,但它们的故障往往更加局部化。将它们推广到系统的特定子集(Canary)并进行验证也更容易。所有这些策略都可以由实际掌握用户脉搏的人员(OPS)进行深度控制。

微服务的可观察性是必要的、昂贵的,但也是更强大的。由于一切都发生在网络层,因此都暴露在可观察性工具面前。SRE 或 DevOps 可以更详细地了解故障。但开发人员可能需要面对更高的复杂性和有限的工具,这是以开发人员为代价的。

应用程序可能变得太大而无法失败。即使采用了模块化,一些最大的单体也会有大量代码,需要数小时才能完成一个完整的 CI/CD 循环。如果部署失败,恢复到上一个良好版本也可能需要一段时间。


划分
过去,我们根据层级来划分团队。客户端、服务器、数据库等。这是有道理的,因为每一层都需要一套独特的技能。如今,纵向团队更有意义,但我们仍有专长。

通常情况下,移动开发人员不会在后台工作。但假设我们有一个移动团队希望使用 GraphQL 而不是 REST。如果使用单体,我们要么告诉他们 "接受它",要么我们就得做这项工作。有了微服务,我们就可以用很少的代码为他们创建一个简单的服务。这是核心服务的一个简单界面。我们不需要担心移动团队编写服务器代码,因为这将是相对独立的。我们可以为每个客户层提供同样的服务,这样就能更容易地纵向整合团队。

太大
很难说哪种规模的单体不实用,但你应该这样问自己:

我们有多少个团队或想要多少个团队?
如果只有几个团队,那么单体系统可能很好。如果你有十几个团队,那么你可能会面临一个问题。

衡量拉取请求和问题解决时间。
随着项目的发展,您的拉取请求等待合并的时间会越来越长,问题的解决时间也会越来越长。这是不可避免的,因为项目的复杂性往往会增加。请注意,新项目会有更大的功能,这可能会影响结果,一旦您在项目统计中考虑到这一点,生产率的下降应该是可以衡量的。

请注意,这只是一个指标。在很多情况下,它还能说明其他问题,例如需要优化测试管道、审查流程、模块化等。

我们有了解代码的专家吗?
在某些时候,一个庞大的项目变得如此之大,以至于专家们开始忽略细节。当错误变得站不住脚时,这就成了一个问题,因为没有权威人士可以在不征求意见的情况下做出决定。

你愿意花钱吗?
微服务的成本会更高。这是没有办法的事。在一些特殊情况下,我们可以调整规模,但最终可观察性和管理成本将消除任何潜在的成本节约。由于人员成本通常会超过云托管成本,如果规模足够大,这些成本可能会降低,因此总成本可能仍然对您有利。

权衡
请注意,微服务可为大型项目带来容错性和团队独立性方面的优势。但他们也付出了成本代价。微服务可以减少研发支出,但大部分都转移到了 DevOps 上,因此这并不是主要的好处。

项目越小,单体服务的情况就越好。

总结
微服务的复杂性是巨大的,有时会被实施团队所忽视。开发人员将微服务作为一种工具,丢弃他们不想维护的系统部分,而不是构建一个可持续、可扩展的架构,以取代单体架构。

我坚信,项目应该从单体开始。微服务是对团队规模的优化,而过早优化是万恶之源。问题是,何时才是进行这种优化的正确时机?

我们可以使用一些指标来简化决策。归根结底,变革不仅仅是拆分一个单体。它意味着重新思考事务和核心概念。从单体开始,我们就有了一个蓝图,随着新实施的加强,我们可以用它来调整我们的新实施。