微服务(Microservices)

微服务(Microservices)

出处

本文是于2014年由James Lewis和Martin Fowler共同编写的文章-微服务(Microservices)的译文
原文网址:https://martinfowler.com/articles/microservices.html

前言

"微服务架构"这个术语在过去几年中频繁被提到,用来描述一种将软件应用程序设计为可独立部署的服务套件。虽然这种体系架构没有明确的定义,但是围绕业务功能,自动化部署,端点智能,语言和数据的分散控制中存在明确的特征。

微服务的概念

"微服务"(Microservices)-在众多软件架构中又一个新的术语。尽管我们通常对此不屑一顾,但这个术语描述了一种我们发现越来越有吸引力的软件系统风格。在过去的几年中,我们已经看到很多项目使用此种风格,到目前为止效果都不错,以至于对于我们很多的同事来说,这正在成为构建企业级应用程序的默认风格。然而遗憾的是,没有多少信息描述微服务风格是什么与如何实现。

简而言之,微服务架构风格(microservice architectural style)是一种将单个应用程序拆分为一组小型服务,每一个小型服务在自己的进程中运行,并且通过轻量级机制进行相互通信,通常使用HTTP API。这些小型服务是围绕业务功能构建的,可以通过全自动化部署机制独立部署。这些服务可以使用不同编程语言编写并使用不同数据存储方式,因此可以最低限度的集中式管理服务。

在解释微服务风格(microservice style)之前,比较一下单体风格(monlithic style)是很有必要的。单体风格:作为单个单元构建的整体应用程序。企业级应用程序通常由三个部分组成:客户端用户界面(由在用户机器上的浏览器中运行的HTML页面和javascript组成);数据库(数据库管理系统);服务端应用。服务端应用处理HTTP请求,执行领域逻辑(domain logic),从数据库中检索与更新数据,最后选择和填充要发送给浏览器的HTML视图。这个服务端应用是一个整体-一个单独可执行程序,对系统的任何改动都涉及构建和部署服务端应用的新版本。

这种单体架构的服务是构建这种系统很自然的选择。处理请求的所有逻辑都在单个进程中运行,从而运行你使用语言的基本特性,将应用程序划分为类、函数、命名空间。值得注意的是,你可以在开发者电脑上运行测试用例,并使用流水线方式部署来确保改动的代码能够经过适当的测试并部署到生产环境当中。你可以通过负载均衡来水平扩展单体架构服务,使单体服务部署在多台服务器上。

单体架构服务很成功,但越来越多的人对此种架构感到失望-尤其是当越来越多的应用程序被部署到云上时。变更周期被捆绑在一起-对应用程序的一小部分的改动,就需要对整体应用程序进行重新构建与部署。随着时间的推移,很难去保持一个良好的模块化结构,使一个模块的变更很难不影响到其他模块。扩展需要扩展整个应用程序而不是只扩展需要扩展的部分。

这导致了微服务架构风格(microservice architectural style)的出现:将应用程序构建为一套服务,除了这些服务可以独立部署与扩展之外,每个服务也提供了坚实的模块边界,甚至允许用不同的编程语言编写不同的服务。这些服务同样可以由不同的团队来管理。

我们并不是说微服务风格(microservice style)是一个新的架构风格,此种风格最早可以追溯到Unix的设计原则。但当时确实没有太多人考虑使用微服务架构(microservice architecture),如果使用此种架构风格,很多软件会更好。

微服务架构的特征

我们不能说这是微服务架构风格的正式定义,但我们尝试描述此架构所具备的共同特征。与任何概述所描述共同特征的定义一样,并不是所有微服务架构具备所有的特征,但我们期望大多数微服务架构具备这些特征。我们的意图是尝试描述我们自己的工作中看到的以及理解的微服务。强调一下,我们不是在制定一些一定要遵守的定义

通过服务组件化

自从我们涉总软件行业以来,就一直有一种通过将组件组装在一起来构建系统的愿望,就像我们在现实世界所看到的东西一样。在过去的几十年里,我们看到了公共库的巨大进步,这些库是大多数语言平台的一部分。

在讨论组件时,我们遇到了一个难题-如何定义组件。我们对组件(component)的定义是:可独立替换与升级的软件单元。

微服务架构会用到库,但此种架构把自己软件组件化的方式是将其拆解为服务。我们将库定义为链接到程序中(linked into a program)的组件,被内存中的函数所调用。而服务则是进程外的(out-of-process)组件,利用如web服务请求或远程过程调用的机制来进行通信(这与很多面向对象编程(OO programs)所讲的服务对象(service object)的概念不同)。

使用服务作为组件(而不是库)的一个主要原因是服务是可独立部署的。如果你的应用程序是由单个进程中的多个库组成,那么对于任何一个单一组件的更改都将导致整个应用程序的重新部署。但是,如果应用程序被分解为多个服务,你只要重新部署需要变更的服务。这并不是绝对的,一些改变会变更服务的接口会导致其他服务做出调整,但是一个好的微服务架构的目标是通过内聚服务边界和对服务约束的不断优化来最小化变更服务所带来的影响。

使用服务作为组件的另一个原因是更清晰的组件接口。大多数语言没有一个好的机制来定义清晰的发布接口(Published Interface)。通常只有文档和规范才能防止客户端破坏组件的封装(会导致组件间耦合度过高)。通过使用明确的远程调用机制,服务可以更容易地避免这种情况。

使用这样的服务确实有缺点。远程调用比进程内调用开销更大,因此远程API需要粗颗粒度(coarser-grained),这通常更难以使用。如果你需要改变组件之间的职责分配,当你跨越进程边界时,这种行为将很难去执行。

有一种可能是,我们可以看到服务映射到运行中的进程。一个服务可能由多个一起开发和部署的进程组成,比如一个应用程序和一个仅被该服务使用的数据库。

围绕业务功能组织

当考虑将大型应用程序拆分成几个部分时,管理层通常会关注技术层,引导UI团队、服务端逻辑团队、数据库团队。当团队沿着这些线分开时,即使是简单的更改也会导致跨团队项目花费时间和预算批准。一个明智的团队将围绕这一点进行优化,并选择两害相权取其轻-只是将逻辑强制放入团队能够访问的任何应用程序中。换句话说,逻辑无处不在。这是康威定律的一个例子。

康威定律: 设计系统的架构受制于产生这些设计的组织的沟通结构(产品必然是其(人员)组织沟通结构的缩影)

微服务的划分方法不同,它围绕业务功能(business capability)将应用程序划分为多个有组织的服务。这些服务面向业务领域的全栈实现,包括用户界面,持久化存储以及任何外部协作。因此,团队是跨职能的,包括用户体验、数据库、项目管理。

www.comparethemarket.com就是以这种组织形式的公司。跨职能团队负责构建和运营每个产品,每个产品被划分成多个通过消息总线通信的独立服务。

大型单体架构的应用系统也可以围绕业务功能进行模块化,尽管这种场景并不常见。当然,我们鼓励一个大型团队构建一个单体架构应用程序按照业务线进行划分。我们看到的主要问题是,这种方式会导致组织上有太多的依赖。如果大型单体架构跨越了很多模块的边界,那对于团队中成员来说短期内是很难修复的。除此之外,我们看到模块化需要大量的规范来进行约束。服务组件拆分的越明确,团队的边界就会越清晰。

产品不是项目

我们看到的大多数应用程序开发工作都使用项目模型:其目的是交付一些被认为已经完成的软件。软件完成后移交给运维组织,最后解散构建软件的项目团队。

微服务(Microservice)的支持者倾向于避免这种模式,而是倾向于团队应该负责产品整个生命周期的概念。这个灵感来自于亚马逊(Amazon)的理念-你构建,你运维(you build,you run it),即开发团队对生产中的软件承担全部的责任。这使得开发人员每天都要了解他们的软件在生产环境中的表现,并增加了与用户的联系,因为他们至少要承担一部分的支持。

产品思维与业务能力的联系紧密。与其将软件视为一组待完成的功能,不如视为一种持续的关系-关于软件如何帮助用户增强业务能力。

同样的方法也可以用在大型单体架构的应用程序上,但是更小粒度的服务可以更容易的在服务开发者与用户之间建立起个人联系。

强化终端与弱化管道

当构建不同进程间的通信架构时,我们看到很多产品和方案强调在通信机制本身中进行重要的设计。企业服务总线(ESB)就是一个很好的例子,ESB产品通常包含用于消息路由、编排、转换和应用业务规则的复杂工具。

微服务更倾向于另一种方法:强化终端与弱化管道(smart endpoints and dumb pipes)。由微服务构建的应用程序的目标是尽可能的解耦与内聚-他们拥有自己的领域逻辑,更像是经典Unix意义上的过滤器-接收请求,适当地应用逻辑并产生响应。他们使用简单的REST协议进行编排,而不是使用WS-Choreography、BPEL复杂的协议或者集中式工具进行编排。

最常用的两种协议是带有资源API的HTTP请求-响应和轻量级消息传递。

善于利用网络,而不是限制(Be of the web,not behind the web) --Ian Robinson

微服务团队使用基于万维网(还有很大程度上基于Unix)构建的原则和协议。经常使用的资源可以在很少的开发成本下被缓存。

第二个常用的方法是通过轻量级消息总线传递消息。所选择的设备通常是单一的(单一,指的是设备只充当消息路由器)-简单的实现,如RabbitMQ或ZeroMQ,除了提供可靠的异步结构之外,并没有做更多的工作。重点还是在产生与消费的终端中,在服务中。

在单体架构中,组件在进程内执行,组件之间的通信是通过方法调用或函数调用进行的。将单体架构改变为微服务架构的最大问题在于改变通信模式。单纯地从内存中的方法调用转换成RPC协议会导致性能不佳的通信会话。相反地,需要用粗颗粒度方式替代细颗粒度通信。

分散式治理

集中式治理的其中一个后果就是在单一技术平台上有标准化的趋势。经验表明,这种方法是有局限性的-不是每个问题都是钉子,也不是每个解决方案都是锤子(not every problem is a nail and not every solution a hammer)。我们更倾向于使用合适的工具来完成任务,虽然单体架构的应用程序可以在一定程度上利用不同的语言,但这种情况并不常见。

将单体的组件拆分为服务,我们在构建每个组件时可以选择。你想用Node.js来建立一个简单的报表页面?大胆去做吧。C++做一个特别粗糙的近实时组件?很好。你想用更换一种更适合某个组件读取行为的不同类型的数据库?我们有技术可以重建它。

当然,仅仅因为可以做拆分,并不意味着应该做拆分-但是以这种方式对系统进行拆分意味着你有选择的余地。

构建微服务的团队更喜欢采用不同于标准化的方法。比起使用一组写在纸上的定义标准,他们更倾向于开发有用的工具,其他开发人员可以使用这些工具来解决他们面临的类似问题。这些工具通常是从实现中获得的,并与更广泛的团队共享,但有时并不完全使用内部开源代码模版。当前git和github事实上已经成为了版本控制系统的选择,开源实践在内部变得越来越普遍。

Netflix就是遵循这一理念的一个很好的例子。作为库共享有用、经过实战测试的代码,鼓励其他开发人员以类似的方式解决类似的问题,同时也为必要时选择不同的方法敞开了大门。共享库倾向于关注数据存储、进程间通信以及基础设施自动化等常见问题。

对于微服务社区来说,日常开销(overheads)是尤其没有吸引力的。这并不是说社区不重视服务合同。恰恰相反,因为服务合同的数量往往更多。只是他们在寻找管理这些合同的不同方式。像宽容读者(Tolerant Reader)和消费者驱动契约(Consumer-Driven Contracts)这样的模式经常应用于微服务。这些模式帮助服务合同独立发展。将消费者驱动的契约作为构建的一部分执行可以提升信心,并提供关于服务是否正常运行的快速反馈。事实上,我们知道澳大利亚有一个团队,他们通过消费者驱动契约(consumer driven contracts)模式来驱动新服务的构建。他们使用简单的工具来定义服务的契约。在编写新服务代码之前,这将成为自动构建的一部分。然后,服务只构建到它满足契约的那一点-这是一种在构建新软件时避免"YAGNI"(You Aren't Going To Need It-你不需要它)困境的优雅方法。这些技术和围绕他们发展起来的工具,通过减少服务之间的临时耦合,限制了集中式管理的需求。

也许去中心化治理的巅峰是亚马逊(Amazon)推广的"构建它/运维它"(build it/run it)的理念。团队负责他们构建的软件的所有方面,包括全天候操作软件。这种级别的责任下发肯定不是常态,但我们确实看到越来越多的公司将责任推给开发团队。Netflix是另一家采用这种理念的公司。每天凌晨3点被传唤机叫醒,这无疑是在编写代码时关注质量的强大动力。这些理念与传统的集中式治理模式相去甚远。

分散的数据管理

数据管理的分散化以多种不同方式呈现。在最抽象的层面上,这意味着世界的概念模型在不同系统之间会有所不同。在跨大型企业进行集成时,这是一个常见的问题,客户的销售视图将不同于帮助视图。在销售视图中称为客户的列数据可能根本不会出现在帮助视图中。他们可能具有不同的属性或者更糟糕的是具有微妙不同语义的共同属性。

这个问题在应用程序之间很常见,但也可能发生在应用程序内部,特别是当应用程序被拆分为单独的组件时。考虑这个问题一个有用方法是限界上下文的领域驱动设计(Domain-Driven Design)概念。领域驱动设计(DDD)将一个复杂的领域划分为多个有界的上下文,并绘制出他们之间的关系。这个方法对单体和微服务架构都有用,但是服务和上下文边界之间存在一个自然的关联,这有助于澄清和加强分离,正如我们在业务功能部分所描述的那样。

除了分散了关于概念模型的定义外,微服务还分散了数据存储定义。虽然单体应用程序更喜欢使用单一逻辑数据库来存储持久性数据,但企业通常更喜欢跨一系列应用程序使用单一数据库-其中很多数据定义是由供应商在许可情况下的商业模式驱动的。微服务更倾向于让每个服务管理自己的数据库,要么是同一数据库技术的不同实例,要么是完全不同的数据库系统-一种称为多语言持久化(Polyglot Persistence)的方法。你也可以在单体架构上使用多语言持久化(polyglot persistence),但它在微服务中出现得更加频繁。

分散跨微服务的数据职责意味着管理更新。处理更新的常用方法是更新多个资源时使用事务来保证一致性。这种方式通常用于单体架构。

使用这样的事务有助于保持一致性,但会带来严重的时间耦合,这在跨多个服务是有问题的。众所周知,分布式事务很难实现,因此微服务架构强调服务之间的无事务协调(emphasize transactionless coordination between services),明确地认识到一致性可能只是最终的一致性,问题时通过补偿操作来处理的。

选择这种管理不一致性对很多开发团队来说是一个新的挑战,但它通常与业务实践相匹配。企业经常处理一定程度的不一致,以便快速响应需求,同时具备回滚流程来处理错误。只要在一致性更强的情况下,修复错误的成本将低于失去业务的成本,这种权衡是值得的。

基础设施自动化

在过去的几年里,基础设施自动化技术已经有了巨大的发展-云的发展,特别是AWS已经降低了构建、部署、操作微服务的操作复杂性。

很多使用微服务构建的产品或系统都是由在持续交付(Continuous Delivery)以及其前身持续集成(Continuous Integration)方面拥有丰富经验的团队构建的。以这种方式构建软件的团队广泛地使用了基础设施自动化技术。下面的构建流水线说明了这一点

由于这不是一篇关于持续交付的文章,我们这里只关注几个关键特性。我们希望我们的软件能够正常工作,因此我们运行了大量的自动化测试(automated tests)。将工作软件"向上"推到流水线意味着我们将自动部署到每一个新环境。

一个单体架构的应用程序将被构建、测试并顺利地通过这些环境。事实证明,一旦你投资于将生产路径自动化,那么部署更多的应用程序似乎就不再那么可怕了。请记住,CD(持续交付)的目的之一是让部署变得无聊(公式化,降低复杂性),所以无论是一个还是三个应用程序,只要它仍然无聊就没关系。

我们看到团队广泛地使用基础设施自动化的另一个领域是在生产环境中管理微服务。与我们上面的断言相反,只要部署是无聊的(公式化),那么单体服务和微服务之间就没有太大区别,两者的运营环境可能截然不同。

容错性设计

使用服务作为组件的一个后果是应用程序需要被设计成能够容忍服务的故障。由于供应商的不可用,任何服务调用都有可能失败,客户端必须尽可能优雅地做出响应。与单体架构设计相比,这是一个缺点,因为它引入了额外的复杂性来处理它。其结果是微服务团队不断反思服务故障是如何影响用户体验的。Netflix的项目(Simian Army)在工作日内诱导服务甚至数据中心的故障,以测试应用程序的监控与恢复力。

这种在生产环境中的自动化测试总以让大多数运维团队感到不寒而栗。这并不是说单体架构风格不能实现复杂的监控配置-只是在我们的经验中不太常见。

由于服务可能随时出现故障,因此能够快速检测故障并在可能的情况下自动恢复服务非常重要。微服务应用程序非常强调对应用程序的实时监控,检查体系结构元素(数据库每秒收到请求量)和业务相关指标(比如每分钟收到的订单量)。语义监控可以提供一个对将要出错的问题早期预警的系统,从而触发开发团队进行跟踪和调查。

这对微服务架构尤其重要,因为微服务对编排和事件协作(event collaboration)的偏好导致了紧急行为。虽然很多权威人士称赞这个意外出现的价值,但事实上,意外出现的行为有时可能是一件坏事。监管对于快速发现不良的突发行为并加以修复显得至关重要。

单体架构可以构建得像微服务一样透明-事实上,它们应该如此。不同之处在于,你绝对需要知道在不同进程中运行的服务何时断开。对于处于同一进程中的库,这种透明度不大可能有用。

微服务团队希望看到针对每个单独的服务的精密监控和日志记录配置,例如显示启动/停止状态的仪表板以及各种操作和业务相关的指标。关于断路器(circuit breaker)状态、当前吞吐量、时延也是我们在外厂中经常关注的指标。

进化性设计

微服务的实践者通常具有进化设计背景,并将服务分解视为一种推进工具,使应用程序开发人员能够在不减缓变化的情况下控制应用程序中的变更。控制变更并不一定意味着减少改变-在具备正确的态度和工具的情况下,你可以对软件进行频繁、快速、良好的控制变更。

每当你尝试将软件系统分解为组件时,你都将面临这如何分割这些部分的决策-我们决定分割应用程序的原则是什么?组件的关键属性是独立替换和可升级性(upgradeability)的概念-这意味着我们寻找可以在不影响共同开发同事的情况下重写组件的点。事实上,很多微服务组在这方面走得更远,他们明确地期望很多服务被废弃,而不是长期发展。

The Guardian website(卫报网站)就是一个很好的例子,它被设计和构建为一个整体(单体架构),但一直向微服务方向发展。单体架构仍然是网站的核心,但他们更喜欢通过调用单体架构的API构建的微服务来添加新功能。这种方法对于本质上是临时的功能显得十分方便,比如处理体育赛事的专用页面。网站的这一部分可以使用快速开发语言快速地组合在一起,并在事件结束后删除。我们在一家金融机构也看过类似的做法,为了获得市场机会而增加的新服务,几个月后甚至几周后就放弃了。

这种对可替换性的强调是更一般地模块化设计原则的一个特例,即通过变化模式来驱动模块化。你希望将同时更改的内容保存在同一模块中。对于目前正在经历大量变动的服务中,系统中很少变化的部分应该在不同的服务中。如果你发现自己在一起反复更改两个服务,这是应该合并它们的信号。

将组件放入服务中为更细粒度的发布计划提供了机会。对于单体架构,任何更改都需要完整构建和部署整个应用程序。然而,使用微服务,您只需要重新部署你修改过的服务。这可以简化和加快发布过程。缺点是你不得不担心一个服务的改变会破环它的消费者。传统的集成方式是尝试使用版本控制来处理这个问题,但是在微服务领域,人们倾向于将版本控制作为最后的手段。我们可以通过将服务设计得尽可能容忍其提供者的变更来避免大量的版本控制。

微服务是未来吗?

我们写这篇文章的主要目的是解释微服务的主要思想和原则。通过花时间做这些,我们清楚地认为微服务架构风格是一个重要的想法-值得企业应用认真考虑。我们最近使用这种风格构建了几个系统,并且知道其他使用并支持这种方式的系统。

我们所知道的在某种程度上开创了这种架构风格的公司包括:Amazon(亚马逊)、Netflix(网飞)、The Guardian(卫报)、the UK Government Digital Service(GDS-英国政府数字服务局)、realestate.com.au、Forward、comparethemarket.com。在2013年会议上,有很多公司正在转向微服务,包括Travis CI。此外,还有很多组织长期以来一直在做我们可以归类为微服务的事情,但从未使用过这个名称(通常这被称为SOA(Service-Oriented Architecture 面向服务的架构)-尽管,正如我们所说,SOA有很多相互矛盾的形式)。

然而,尽管有这些积极的经验,我们并不是说我们确定微服务是软件架构的未来方向。虽然到目前为止,我们的经验与单体应用程序相比是积极的,但我们意识到,没有足够的时间让我们做出全面的判断。

通常,你的体系结构决策的真正后果只有在您做出决策几年后才会显现出来。我们看到过这样的项目:一个优秀的团队,怀着对模块化的强烈渴望,构建了一个随着时间流逝而衰败的单体架构。很多人认为微服务不太可能出现这种衰退,因为服务的边界是明确的,很难修补。然而,在我们看到足够多的系统持续足够的年限之前,我们无法真正评估微服务架构的成熟程度。

当然,人们有理由认为微服务不会成熟。在组件化的任何努力中,成功取决于软件与组件的匹配程度。很难确定组件边界的确切位置。进化性设计(Evolutionary design)认识到正确设置边界的困难,从而认识到易于重构边界的重要性。但是,当你的组件是具有远程通信的服务时,重构要比使用进程内的库要困难得多。跨服务边界移动代码是困难的,任何接口变更都需要再参与者之间进行协调,需要添加向后兼容性层,并且测试变得更加复杂。

另一个问题是,如果组件组合不干净,那么你所做的就是将复杂性从组件内部转移到组件之间的连接。这不仅改变了复杂性,还把它转义到了一个不那么明确和难以控制的地方。当你看到一个组件内部小而简单,而忽略了服务之间混乱的连接时,很容易会认为这样更好。

最后,还有团队技能的因素。新技术往往被更熟练的团队采用。但是对于技术较好的团队来说更有效的技术并不一定使用于技术较差的团队。我们已经看到了很多不太熟练的团队构建混乱的整体架构的案例,但是当这种混乱发生在微服务上时,我们需要时间来观察会发生什么。一个糟糕的团队总是会创造出一个糟糕的系统-很难说微服务在这种情况下是减少了混乱还是使情况变得更糟。

我们听到的一个合理的论点是,不应该从微服务架构开始。相反,从一个单体架构开始,保持它的模块化,一旦单体架构成为问题,就把它拆分成微服务。(尽管这个建议并不理想,因为好的进程内接口通常不是好的服务接口)。

因此,我们以谨慎乐观的态度写这篇文章。到目前为止,我们已经对微服务风格有了足够的了解,觉得这是一条值得探索的道路。我们不能肯定地说我们将在哪里结束。但是软件开发的挑战之一是你只能根据目前掌握的不完全信息做出决策(you can only make decisions based on the imperfect information that you currently have to hand)。

参考资料

Martin Fowler个人博客网址:https://martinfowler.com/
微服务(Microservices)原文网址:https://martinfowler.com/articles/microservices.html

posted @ 2023-12-06 21:30  柯南。道尔  阅读(42)  评论(0编辑  收藏  举报