Microservices
a definition of this new architectural term
The term "Microservice Architecture" has sprung up over the last few years to describe a particular way of designing software applications as suites of independently deployable services. While there is no precise definition of this architectural style, there are certain common characteristics around organization around business capability, automated deployment, intelligence in the endpoints, and decentralized control of languages and data.
翻译是:
“微服务架构”一词在过去几年中出现,用于描述将软件应用程序设计为可独立部署的服务套件的特定方式。虽然没有对这种架构风格的精确定义,但围绕业务能力、自动化部署、端点智能以及语言和数据的分散控制的组织具有某些共同特征。
💡单体应用
在介绍微服务之前,我先简单说说我们常见的单体应用,单体应用程序可以取得成功,但越来越多的人对它们感到沮丧——尤其是随着越来越多的应用程序被部署到云中。更改周期是紧密相连的——对应用程序的一小部分进行更改,需要重新构建和部署整个单体。随着时间的推移,通常很难保持良好的模块化结构,这使得保持应该只影响该模块中的一个模块的更改变得更加困难。扩展需要扩展整个应用程序,而不是需要更多资源的部分。
这些挫折导致了微服务架构风格:将应用程序构建为服务套件。除了服务可独立部署和可扩展这一事实外,每个服务还提供了牢固的模块边界,甚至允许使用不同的编程语言编写不同的服务。它们也可以由不同的团队管理。
Microservices大白话
简而言之,微服务架构风格[1]是一种将单个应用程序开发为一组小服务的方法,每个小服务都在自己的进程中运行并与轻量级机制(通常是 HTTP 资源 API)进行通信。这些服务是围绕业务能力构建的,并且可以通过全自动部署机制独立部署。这些服务的集中管理最少,可以用不同的编程语言编写并使用不同的数据存储技术。
微服务和 SOA
当我们谈到微服务时,一个常见的问题是这是否只是我们十年前看到的面向服务的架构 (SOA)。这一点是有好处的,因为微服务风格与一些 SOA 倡导者所支持的风格非常相似。然而,问题在于 SOA 意味着太多不同的东西,而且大多数时候我们遇到一种叫做“SOA”的东西,它与我们在这里描述的风格有很大的不同,通常是因为关注 ESB集成单片应用程序。
特别是,我们已经看到了许多面向服务的拙劣实现——从在 ESB [6]中隐藏复杂性的趋势,到花费数百万且没有带来任何价值的多年计划失败,再到积极抑制变化的集中治理模型,有时很难看清这些问题。
当然,微服务社区中使用的许多技术都是从开发人员在大型组织中集成服务的经验中发展而来的。Tolerant Reader模式就是一个例子。使用网络的努力做出了贡献,使用简单的协议是从这些经验中衍生出来的另一种方法 - 一种远离已经达到复杂性的中央标准的反应,坦率地说,令人叹为观止。(任何时候你需要一个本体来管理你的本体,你知道你遇到了很大的麻烦。)
SOA 的这种普遍表现导致一些微服务倡导者完全拒绝 SOA 标签,尽管其他人认为微服务是 SOA 的一种形式[7],也许服务导向做得对。无论哪种方式,SOA 意味着如此不同的事物这一事实意味着有一个更清晰地定义这种架构风格的术语是有价值的。
微服务架构的特征
通过服务组件化
微服务架构将使用库,但他们将自己的软件组件化的主要方式是分解成服务。我们将库定义 为链接到程序中并使用内存中函数调用调用的组件,而服务是与 Web 服务请求或远程过程调用等机制进行通信的进程外组件。
使用服务作为组件(而不是库)的一个主要原因是服务是可独立部署的。如果您的应用程序在单个进程中包含多个库,则对任何单个组件的更改都会导致必须重新部署整个应用程序。但是,如果该应用程序被分解为多个服务,您可以预期许多单个服务更改只需要重新部署该服务。这不是绝对的,一些变化会改变服务接口导致一些协调,但是一个好的微服务架构的目标是通过服务契约中的内聚服务边界和演化机制来最小化这些。
使用服务作为组件的另一个结果是更明确的组件接口。大多数语言没有定义显式发布接口的良好机制。通常只有文档和规范才能防止客户端破坏组件的封装,从而导致组件之间的耦合过紧。服务通过使用显式远程调用机制更容易避免这种情况。
使用这样的服务确实有缺点。远程调用比进程内调用更昂贵,因此远程 API 需要更粗粒度,这通常使用起来更尴尬。如果您需要更改组件之间的职责分配,那么当您跨越流程边界时,这种行为移动就更难做到了。
围绕业务能力组织
在将大型应用程序拆分为多个部分时,管理层通常侧重于技术层,从而导致 UI 团队、服务器端逻辑团队和数据库团队。当团队沿着这些线分开时,即使是简单的更改也可能导致跨团队项目需要时间和预算批准。一个聪明的团队将围绕这一点进行优化,并为两害取其轻——只需将逻辑强加到他们有权访问的任何应用程序中。换句话说,到处都是逻辑。这是康威定律的一个实例。
任何设计系统(定义广泛)的组织都将产生其结构是组织通信结构副本的设计。
-- 梅尔文.康威
微服务的划分方法是不同的,分成围绕 业务能力组织的服务。此类服务为该业务领域采用广泛的软件实现,包括用户界面、持久存储和任何外部协作。因此,团队是跨职能的,包括开发所需的全部技能:用户体验、数据库和项目管理。
以这种方式组织的一家公司是www.comparethemarket.com。跨职能团队负责构建和运营每个产品,每个产品都被拆分为多个通过消息总线进行通信的单独服务。
大型单体应用程序也总是可以围绕业务功能进行模块化,尽管这种情况并不常见。当然,我们会敦促构建单体应用程序的大型团队按照业务线划分自己。我们在这里看到的主要问题是,它们往往围绕太多的上下文进行组织。如果单体架构跨越了这些模块化边界中的许多,那么团队中的各个成员可能很难将它们融入他们的短期记忆中。此外,我们看到模块化线需要大量的纪律来执行。服务组件所需的必然更明确的分离使得保持团队边界清晰变得更容易。
去中心化治理
集中治理的后果之一是在单一技术平台上标准化的趋势。经验表明,这种方法具有局限性——不是每个问题都是钉子,也不是每个解决方案都是锤子。我们更喜欢使用正确的工具来完成工作,虽然单体应用程序可以在一定程度上利用不同的语言,但这并不常见。
将单体应用程序的组件拆分为服务,我们在构建每个服务时都有选择。您想使用 Node.js 建立一个简单的报告页面吗?去吧。C++ 用于一个特别粗糙的近实时组件?美好的。您想换一种更适合某个组件的读取行为的不同风格的数据库吗?我们拥有重建他的技术。
当然,仅仅因为您可以做某事并不意味着您应该这样做——但是以这种方式对系统进行分区意味着您可以选择。
构建微服务的团队也更喜欢采用不同的标准方法。与其使用写在纸上某处的一组定义的标准,他们更喜欢生成有用的工具,其他开发人员可以使用这些工具来解决与他们面临的问题类似的问题。这些工具通常是从实现中获取的,并与更广泛的群体共享,有时但不完全使用内部开源模型。现在 git 和 github 已经成为事实上的版本控制系统选择,开源实践在内部变得越来越普遍。
Netflix 是遵循这一理念的组织的一个很好的例子。将有用的、最重要的是经过实战测试的代码作为库共享可以鼓励其他开发人员以类似的方式解决类似的问题,但如果需要,可以选择不同的方法。共享库倾向于关注数据存储、进程间通信以及我们在下面进一步讨论的基础设施自动化等常见问题。
对于微服务社区来说,开销尤其没有吸引力。这并不是说社区不重视服务合同。恰恰相反,因为它们的数量往往更多。只是他们正在寻找管理这些合同的不同方式。Tolerant Reader和Consumer-Driven Contracts等模式 通常应用于微服务。这些帮助服务合同独立发展。在构建过程中执行消费者驱动的合同可以增加信心,并就您的服务是否正常运行提供快速反馈。事实上,我们知道澳大利亚有一个团队通过消费者驱动的合同来推动新服务的构建。他们使用简单的工具来定义服务合同。在编写新服务的代码之前,这将成为自动构建的一部分。然后将服务构建到满足合同的程度 - 一种避免“YAGNI”的优雅方法[9]构建新软件时的困境。这些技术和围绕它们发展起来的工具通过减少服务之间的时间耦合来限制对中央合同管理的需求。
去中心化数据管理
据管理的去中心化以多种不同的方式呈现。在最抽象的层面上,这意味着世界的概念模型在系统之间会有所不同。这是跨大型企业进行集成时的常见问题,客户的销售视图将不同于支持视图。在销售视图中称为客户的某些事物可能根本不会出现在支持视图中。那些确实可能具有不同的属性和(更糟糕的)具有微妙不同语义的共同属性。
此问题在应用程序之间很常见,但也可能在应用程序内发生,特别是当该应用程序分为单独的组件时。考虑这一点的一种有用方法是 Bounded Context的领域驱动设计概念。DDD 将一个复杂的领域划分为多个有界上下文,并绘制出它们之间的关系。这个过程对于单体架构和微服务架构都很有用,但是服务和上下文边界之间存在自然的关联,这有助于澄清,正如我们在业务能力一节中描述的那样,加强了分离。
除了分散有关概念模型的决策外,微服务还分散了数据存储决策。虽然单体应用程序更喜欢使用单个逻辑数据库来存储持久数据,但企业通常更喜欢跨一系列应用程序使用单个数据库——其中许多决策是由供应商围绕许可的商业模型驱动的。微服务更喜欢让每个服务管理自己的数据库,可以是相同数据库技术的不同实例,也可以是完全不同的数据库系统——一种称为Polyglot Persistence的方法。您可以在单体应用中使用多语言持久性,但它在微服务中出现的频率更高。
跨微服务分散数据责任对管理更新有影响。处理更新的常用方法是在更新多个资源时使用事务来保证一致性。这种方法通常在单体应用中使用。
使用这样的事务有助于保持一致性,但会带来显着的时间耦合,这在多个服务之间是有问题的。众所周知,分布式事务难以实现,因此微服务架构强调服务之间的无事务协调,明确认识到一致性可能只是最终的一致性,问题通过补偿操作来处理。
基础设施自动化
基础设施自动化技术在过去几年中发生了巨大的发展——云的发展,尤其是 AWS,降低了构建、部署和运营微服务的运营复杂性。 许多使用微服务构建的产品或系统是由具有丰富持续交付经验的团队构建的,它的前身是持续集成。以这种方式构建软件的团队会广泛使用基础设施自动化技术。这在下面显示的构建管道中进行了说明。
图 5:基本构建管道
一个单体应用程序将在这些环境中构建、测试和推送。事实证明,一旦您为单体应用程序的生产路径自动化进行了投资,那么部署更多应用程序似乎就不再那么可怕了。请记住,CD 的目标之一是让部署变得无聊,所以无论是一个还是三个应用程序,只要它仍然无聊就没有关系。
为失败而设计
使用服务作为组件的一个结果是,应用程序需要被设计成能够容忍服务的失败。由于供应商不可用,任何服务调用都可能失败,客户必须尽可能优雅地对此做出响应。与单片设计相比,这是一个缺点,因为它引入了额外的复杂性来处理它。结果是微服务团队不断反思服务故障如何影响用户体验。Netflix 的Simian Army 在工作日期间会引发服务甚至数据中心的故障,以测试应用程序的弹性和监控。
这种生产中的自动化测试足以让大多数运营团队在下班前打个寒颤。这并不是说单体架构风格不能进行复杂的监控设置——这在我们的经验中并不常见。
由于服务随时可能出现故障,因此能够快速检测故障并在可能的情况下自动恢复服务非常重要。微服务应用非常重视应用的实时监控,检查架构元素(数据库每秒收到多少请求)和业务相关指标(例如每分钟收到多少订单)。语义监控可以提供一个错误的预警系统,触发开发团队跟进和调查。
这对于微服务架构尤为重要,因为微服务对编排和事件协作的偏好 会导致紧急行为。虽然许多权威人士称赞偶然出现的价值,但事实是,紧急行为有时可能是一件坏事。监控对于快速发现不良紧急行为至关重要,因此可以对其进行修复。
可以将单体构建为像微服务一样透明——事实上,它们应该如此。不同之处在于您绝对需要知道在不同进程中运行的服务何时断开连接。对于同一进程中的库,这种透明度不太可能有用。
微服务团队希望看到每个单独服务的复杂监控和日志记录设置,例如显示启动/关闭状态的仪表板以及各种运营和业务相关指标。有关断路器状态、当前吞吐量和延迟的详细信息是我们在野外经常遇到的其他示例。
进化设计
微服务从业者通常具有进化设计背景,并将服务分解视为进一步的工具,使应用程序开发人员能够控制其应用程序中的更改,而不会减慢更改速度。变更控制并不一定意味着减少变更——通过正确的态度和工具,您可以对软件进行频繁、快速且可控的变更。
每当您尝试将软件系统分解为组件时,您都会面临如何分割这些部分的决定——我们决定分割应用程序的原则是什么?组件的关键属性是独立替换和可升级性的概念[13] - 这意味着我们寻找可以想象重写组件而不影响其协作者的点。事实上,许多微服务团队通过明确预期许多服务将被废弃而不是长期发展,从而更进一步。
Guardian 网站是一个很好的应用程序示例,它被设计和构建为一个整体,但一直在向微服务方向发展。单体仍然是网站的核心,但他们更喜欢通过构建使用单体 API 的微服务来添加新功能。这种方法对于本质上是临时的功能特别方便,例如处理体育赛事的专用页面。网站的这样一部分可以使用快速开发语言快速组合在一起,并在活动结束后删除。我们已经在一家金融机构看到过类似的方法,在这些方法中添加新服务以获取市场机会并在几个月甚至几周后被丢弃。
这种对可替换性的强调是模块化设计更一般原则的一个特例,即通过变化的模式来驱动模块化[14]。您希望将同时更改的内容保存在同一模块中。系统中很少发生变化的部分应该与那些目前正在经历大量流失的服务处于不同的服务中。如果您发现自己反复同时更改两个服务,则表明它们应该合并。
将组件放入服务中为更精细的发布计划增加了机会。对于单体应用,任何更改都需要完整构建和部署整个应用程序。但是,使用微服务,您只需要重新部署您修改的服务。这可以简化和加快发布过程。缺点是您必须担心对一项服务的更改会破坏其消费者。传统的集成方法是尝试使用版本控制来处理这个问题,但在微服务世界中,偏好是仅将版本控制作为最后的手段。我们可以通过将服务设计为尽可能容忍其供应商的变化来避免大量的版本控制。
微服务权衡
许多开发团队发现微服务架构风格是一种优于单体架构的方法。但其他团队发现它们是一种降低生产力的负担。与任何架构风格一样,微服务带来成本和收益。要做出明智的选择,您必须了解这些并将它们应用到您的特定环境中。
微服务有多大?
尽管“微服务”已经成为这种架构风格的流行名称,但它的名字确实导致了对服务规模的不幸关注,以及关于什么是“微”的争论。在我们与微服务从业者的对话中,我们看到了各种规模的服务。报告的最大尺寸遵循亚马逊的两个披萨团队的概念(即整个团队可以吃两个披萨),这意味着不超过十二个人。在较小的规模上,我们已经看到了一个由六个团队组成的团队将支持六个服务的设置。
这导致了一个问题,即在这个规模范围内是否存在足够大的差异,以至于不应该将每十人服务和每人服务规模归为一个微服务标签。目前我们认为最好将它们组合在一起,但随着我们进一步探索这种风格,我们肯定会改变主意。
🌈微服务是未来吗?
我们并不认为我们确定微服务是软件架构的未来方向。虽然到目前为止我们的经验与单体应用程序相比是积极的,通常,架构决策的真正后果仅在做出决策几年后才会显现出来。看到过一些项目,一个对模块化有着强烈渴望的优秀团队已经构建了一个多年来已经腐朽的单体架构。许多人认为微服务不太可能出现这种衰减,因为服务边界是明确的并且难以修补。然而,在我们看到足够多的系统足够成熟之前,我们无法真正评估微服务架构是如何成熟的。
当然,人们可能期望微服务成熟度不佳是有原因的。在组件化的任何努力中,成功取决于软件与组件的匹配程度。很难确定组件边界的确切位置。进化设计认识到正确设置边界的困难,因此易于重构它们的重要性。但是,当您的组件是具有远程通信的服务时,重构比使用进程内库要困难得多。跨服务边界移动代码很困难,任何接口更改都需要在参与者之间进行协调,需要添加向后兼容的层,并且使测试变得更加复杂。
另一个问题是如果组件组合不干净,那么您所做的就是将复杂性从组件内部转移到组件之间的连接。这不仅只是移动了复杂性,而且将其移动到了一个不太明确且难以控制的地方。当您查看一个小而简单的组件的内部时,很容易认为事情会变得更好,而忽略了服务之间的杂乱连接。