近几年来,“微服务体系结构”这个术语出现了,它描述了将软件应用程序设计为可独立部署的服务套件的特定方式。尽管这种架构风格没有确切的定义,但围绕业务能力,自动化部署,智能终端以及数据的分散控制等方面存在着某些共同特征。
“微服务” - 这一个在软件架构拥挤的街道上的又一个新名词。尽管我们的自然倾向是以轻蔑的眼光来传递它,但这个术语描述了一种我们发现越来越吸引人的软件系统风格。我们已经看到许多项目在过去几年中都采用了这种风格,迄今为止的结果是积极的,因此对于我们的许多人来说,这正成为构建企业应用程序的默认风格。但可悲的是,没有太多的信息去描述了微服务的风格以及如何去做。简而言之,微服务架构风格是一种将单个应用程序作为一套小型服务来开发的方法,每个服务都运行在自己的进程中,并与轻量级机制(通常是HTTP、RPC调用资源API方式出现)进行通信。这些服务是围绕业务功能构建的,可以通过全自动部署机制独立部署。这些服务的集中管理最少,可以用不同的编程语言编写,并使用不同的数据存储技术。
单体应用示例图 微服务示例图
当我们要开始解释什么是微服务风格时,将其与单体应用风格进行比较是非常有用的:我们可以将单一应用程序包含的所有功能构建为单一个单元(如单体应用示例图所示)。企业应用程序通常建立在三个主要部分之上:一个客户端用户界面(由用户计算机上的浏览器中运行的HTML页面和JavaScript组成)数据库(关系数据库管理的许多表系统)和一个服务器端应用程序。服务器端应用程序将处理HTTP请求,执行业务逻辑,从数据库检索和更新数据,选择并填充要发送到浏览器的HTML视图。这个服务器端应用程序是一个庞然大物 - 一个逻辑上可执行文件。系统的任何更改都涉及构建和部署新版本的服务器端应用程序。这种单一的应用是构建这种系统的一种自然方式。处理请求的所有逻辑都在一个进程中运行,允许您使用语言的基本功能将应用程序划分为类库,类,函数和名称空间。谨慎操作时,您可以在开发人员的笔记本电脑上运行和测试应用程序,并使用部署管道来确保更改经过适当测试并部署到生产环境中。您可以通过在负载均器后面运行多个实例来横向扩展整体。虽然单一应用程序取得很多成功案例,但越来越多的人感到沮丧 - 尤其是随着更多应用程序部署到云中。变更周期是连在一起的 - 对应用程序的一小部分进行更改,需要重建和部署整个整体。随着时间的推移,通常很难保持良好的模块化结构,这使得难以保持应该仅影响该模块中的一个模块的变化。扩展需要扩展整个应用程序,而不是扩展需要变更的那部分资源。
单体应用集群 微服务应用集群
这些挫败感导致了微服务架构风格出现:将应用程序构建为服务套件。除了服务可独立部署和扩展之外,每个服务还提供了一个牢固的模块边界,甚至允许用不同的编程语言编写不同的服务。他们也可以由不同的团队管理。我并不认为微服务风格是新颖的或创新的,它的根源至少可以追溯到Unix的设计原则。但目前我认为没有足够多的人考虑微服务架构,并且许多软件开发如果使用它们会更好。
微服务架构的特点
我们不能说微服务架构风格已经有了一个正式的定义,但我可以试图描述我们认为符合标准的架构的共同特征。与其他常见特性的任何定义一样,并非所有微服务架构都具有所有特性,但我们确实期望大多数微服务架构具有大多数特性。我们的目的是试图描述在自己的工作中所看到的以及所了解的团队的类似努力。特别是我们并没有制定一些符合的定义。
通过服务进行组件化
自从我们进入软件行业以来,就一直希望通过将组件整合在一起来构建系统,这与我们在现实世界中看到事物的方式非常相似。在谈论组件时,遇到了构成组件的困难定义。我们的定义是, 组件是可独立更换和升级的软件单元。微服务体系结构也使用类似库包装的概念,但是它们组装自己软件的主要方式是分解为服务。我们将库定义为链接到程序中的组件,并使用内存中的函数调用进行调用,而服务是与Web服务请求或远程过程调用等机制进行通信的进程外组件。(这与许多面向对象程序中服务对象的概念不同。使用服务作为组件(而不是库)的一个主要原因是服务可独立部署。如果您的应用程序由单个进程中的多个库组成,那么对任何单个组件的更改都会导致必须重新部署整个应用程序。但是,如果该应用程序分解为多个服务,则可以期望许多单一服务更改只需要重新部署该部分服务。但这不是绝对的,有些改变会改变服务接口,导致一些协调,但是一个好的微服务架构的目标是通过服务合约中的内聚服务边界和进化机制来最小化这些问题。使用服务作为组件的另一个后果是更明确的组件接口。大多数语言没有一个好的机制来定义一个明确的发布接口。通常只有文档和纪律能够防止客户端破坏组件的封装,导致组件之间过于紧密的耦合。通过使用显式远程调用机制,服务可以更容易地避免这种情况。使用这样的服务确实有缺点。远程调用比进程内调用更加昂贵,因此远程API需要更粗粒度,这通常更难以使用。如果您需要更改组件之间的责任分配,那么当您跨越流程边界时,这种行为的移动难以实现。在第一个近似值中,我们可以观察到服务映射到运行时进程,但这只是第一个近似值。服务可能由多个进程组成,这些进程将始终开发并部署在一起,例如应用程序进程和仅由该服务使用的数据库。
围绕业务能力进行组织
当想要将大型应用程序拆分为多个部分时,管理层往往侧重于技术层,导致UI团队,服务器端逻辑团队和数据库团队协同出现不少问题。当团队沿着这些路线分开时,即使是简单的变更也会导致跨团队项目需要时间和预算批准。一个聪明的团队将围绕这一点进行优化,并为两个弊端中的较小者提供丰富实践 - 只需将逻辑强制到他们可以访问的任何应用程序。换言之,就是无处不在的逻辑。这是康威法则一个例子。
任何设计系统(定义广泛)的组织都将产生一个设计,其结构是该组织通信结构的副本。
- 梅尔文康威,1967年
微服务团队成员组织的方法不同,分解成围绕业务能力进行组织的服务 。这些服务需要对该业务领域的软件进行广泛的实施,包括用户界面,持久性存储和任何外部协作。因此,团队是跨职能的,包括开发所需的全部技能:用户体验,数据库和项目管理。
微边界多大合适?
虽然“微服务”已成为这种架构风格的流行名称,但它的名称确实导致了对服务规模大小的误解以及关于什么构成“微”的争论。在我与微服务从业者的对话中,看到了服务定义时关于“微”的焦虑。在规模较小的规模上,我们已经看到了一个半打团队将支持六种服务的设置。这就导致了这样一个问题:在这个大小范围内是否存在足够大的差异,即每个服务人员和每个人服务的大小不应该被集中在一个微服务标签下。目前我倾向认为将它们组合在一起会更好,但当进一步探索这种风格时,我们肯定会改变主意。跨职能团队负责构建和操作每个产品,并将每个产品分成通过消息总线进行通信的多个单独服务。大型单一应用程序总是可以围绕业务功能模块化,尽管这并不是每个人都能遇见这种情况。当一个庞大的团队构建一个庞大的应用程序,按照业务线分工。我们在这里看到的主要问题是,它们倾向于围绕太多的背景进行组织。如果单体应用跨越了许多这样的模块化边界,那么团队的个别成员就很难将其融入他们的短期记忆中。此外,我们看到模块化生产线需要执行很多规范。因此我认为服务组件设计能够保持团队及业务边界的清晰就是合适的“微”。
是产品不是项目
我们看到的大多数应用程序开发工作都使用项目模型:目标是提供一些随后被认为已完成的软件。完成后,软件被移交给维护组织,并且构建它的项目团队被解散。微服务支持者倾向于避免这种模式,而更倾向于认为团队应该在其整个生命周期内拥有产品。对此的一个共同启发就是亚马逊的“你自己构建,运行它”的概念,即开发团队负责生产软件的全部责任。这使开发人员日常接触到他们的软件在生产中的行为,并增加与用户的联系,因为他们必须承担至少一些支持负担。产品的心态,与企业能力的关联。软件并不是一组待完成的功能,而是一个持续的关系,问题是软件如何帮助其用户提高业务能力。没有理由不能为单一应用程序采用同样的方法,但较小的服务粒度可以更容易地创建服务开发人员与其用户之间的个人关系。
智能端点和哑管道
在构建不同流程之间的沟通结构时,我们看到许多产品和方法都强调将重要的智慧融入沟通机制本身。一个很好的例子就是企业服务总线(ESB),ESB产品通常包含用于消息路由,编排,转换和应用业务规则的复杂设施。
微服务和SOA
当我们谈到微服务时,一个常见的问题是这是否仅仅是我们十年前看到的面向服务的体系结构(SOA)。这一点是有价值的,因为微服务风格与SOA的一些倡导者赞成的非常相似。然而,问题在于SOA意味着太多不同的东西,而且大部分时间我们遇到了一些名为“SOA”的东西,这与我们在此描述的样式显着不同,通常是由于专注于用于ESB的东西集成单片应用程序。特别是我们已经看到了许多拙劣的面向服务的实现 - 从ESB 隐藏复杂性的倾向,到花费数百万美元并且没有价值的失败的多年计划,集中式的治理模式,积极抑制变革,有时很难看到这些问题。当然,微服务社区中使用的许多技术已经从集成大型组织服务的开发人员的经验中发展而来。SOA的这种常见表现已经导致一些微服务提倡者完全拒绝SOA标签,尽管其他人认为微服务是SOA的一种形式,也许服务导向是正确的。无论哪种方式,SOA意味着这些不同的事物的事实意味着有一个更清晰地定义这种架构风格的术语是有价值的。微服务社群倾向于采用另一种方法: 智能端点和哑管。从微服务构建的应用程序的目标是尽可能地解耦和内聚 - 它们拥有自己的领域逻辑,并且更多地充当经典Unix意义上的过滤器 - 接收请求,适当地应用逻辑并产生响应。这些编程使用简单的REST协议而不是复杂的协议,例如WS-Choreography或BPEL或通过中央工具进行编排。
最常用的两种协议是使用资源API和轻量级消息的HTTP请求响应。第一个最好的表达是
成为网络,而不是网络后面
- 伊恩罗宾逊
微服务团队使用万维网(在很大程度上,Unix)构建的原则和协议。开发人员或操作人员可以通过很少的努力来缓存经常使用的资源。常用的第二种方法是通过轻量级消息总线进行消息传递。所选择的基础架构通常是愚蠢的(仅仅是作为一个消息路由器的行为) - 简单的实现,比如RabbitMQ或ZeroMQ,不仅仅是提供可靠的异步结构 - 智能仍然存在于正在生成和消费信息; 在服务中。在整体架构中,组件正在执行中,它们之间的通信通过方法调用或函数调用进行。将巨人变成微服务的最大问题在于改变沟通模式。从内存中方法调用到RPC的天真转换会导致性能不佳的健谈通信。相反,你需要用更粗糙的方法来替换细粒度的通信。
分散治理
集中治理的后果之一是在单一技术平台上实现标准化的趋势。经验表明,这种方法是狭隘的 - 并非每一个问题都是钉子,而不是每一个解决方案。我们更喜欢使用正确的工具来完成这项工作,而单片应用程序可以在一定程度上利用不同的语言,但这并不常见。将整体零件拆分成服务时,我们在构建每个服务时都有选择。你想使用Node.js来站立一个简单的报告页面?去吧。C ++用于特别粗糙的近实时组件?精细。你想交换一个不同的数据库风格,更适合一个组件的读取行为?我们有技术来重建他。当然,仅仅因为你可以做点什么,并不意味着你应该这样做- 但是用这种方式划分你的系统意味着你可以选择。构建微服务的团队也更喜欢采用不同的标准方法。他们更喜欢生产有用的工具,而其他开发人员可以用它来解决他们所面临的类似问题,而不是使用一套定义好的标准。这些工具通常从实施中收获,并与更广泛的群体共享,有时但不完全使用内部开源模型。
许多语言,很多选项
JVM作为平台的发展只是在一个通用平台中混合语言的最新例子。数百年来,为了利用更高层次的抽象,已经普遍采用更高级的语言。。也许分散治理的最高点就是构建它/运行它的亚马逊流行的精神。团队负责构建软件的各个方面,包括全天候运行软件。这种责任级别的转变绝对不是常态,但我们看到越来越多的公司将责任推给开发团队。Netflix是另一个采用这种风格的组织。每个晚上在凌晨3点被传呼机唤醒,无疑是在编写代码时专注于质量的强大动力。这些想法与传统的集中治理模式尽可能相距甚远。
分散数据管理
数据管理的分权化以多种不同的方式呈现。在最抽象的层面上,这意味着世界的概念模型在不同系统之间会有所不同。在整个大型企业中进行整合时,这是一个常见问题,客户的销售视图将与支持视图不同。在销售视图中称为客户的某些内容在支持视图中可能完全不显示。那些确实可能具有不同的属性和(更糟糕的)具有微妙不同语义的共同属性。
经过测试的标准和强制执行的标准
微服务团队倾向于避开企业架构团体规定的严格强制标准,但会乐于使用甚至传播开放标准(如HTTP,ATOM和其他微格式)的使用,这有点二分法。关键的区别在于如何制定标准以及如何实施标准。由IETF等组织管理的标准只有在更广泛的世界中有多个实际实现时才会成为标准,而且这些标准通常是从成功的开源项目中发展而来的。这些标准与企业界的许多企业不同,它们通常由最近没有编程经验或供应商过度影响的团队开发。这个问题在应用程序之间是常见的,但也可能出现在应用程序中,特别是当应用程序被分成不同的组件时。一个有用的思考方式是有界上下文的领域驱动设计概念 。DDD将一个复杂的领域划分为多个有界的上下文,并绘制出它们之间的关系。这个过程对于单体和微服务体系结构都很有用,但服务和上下文边界之间有一个自然的相关性,这有助于澄清,正如我们在业务能力部分中所描述的,加强了分离。除了分散关于概念模型的决策之外,微服务还分散了数据存储决策。尽管整体式应用程序更喜欢单一的逻辑数据库来存储持久性数据,但企业通常更喜欢一系列应用程序中的单一数据库 - 许多这些决策都是通过供应商商业模式围绕许可进行驱动的。微服务更喜欢让每个服务管理自己的数据库,或者是相同数据库技术的不同实例,或者完全不同的数据库系统 - 一种称为Polyglot Persistence的方法。您可以在整体中使用多边形持久性,但对于微服务来说更频繁。在微服务中分散数据责任对于管理更新具有影响。处理更新的通用方法是在更新多个资源时使用事务来保证一致性。这种方法经常用在单体应用中。使用像这样的事务有助于保持一致性,但是会施加显着的时间耦合,这在多个服务中是有问题的。分布式事务非常难以实现,因此微服务架构强调服务之间的无事务协调,明确认识到一致性可能只是最终的一致性,而问题则通过补偿操作来处理。
以这种方式选择管理不一致对于许多开发团队来说是一个新的挑战,但它往往符合商业惯例。通常企业处理一定程度的不一致性,以便快速响应需求,同时采取某种逆转过程来处理错误。只要修正错误的成本低于失败业务的成本,这种折中是值得的。
基础设施自动
基础设施自动化技术在过去几年中发生了巨大变化 - 特别是云和AWS的发展降低了构建,部署和运行微服务的运营复杂性。由微服务构建的许多产品或系统都由具有持续交付丰富经验的团队构建,这是持续集成的先驱。这种构建软件的团队广泛使用基础设施自动化技术。由于这不是一篇关于持续交付的文章,我们将在这里提请注意几个关键功能。我们希望尽可能多的信心使我们的软件能够工作,所以我们进行了大量的自动化测试。推动工作软件“向上”流水线意味着我们可以自动部署 到每个新环境。
轻松做正确的事情
我们发现,由于持续交付和部署而导致自动化程度提高的一个副作用是创建有用的工具来帮助开发人员和操作人员。用于创建工件,管理代码库,站立简单服务或添加标准监视和日志记录的工具现在很常见。网络上最好的例子可能是Netflix的开源工具,但也有其他的包括我们广泛使用的Dropwizard。一个单一的应用程序将被建立,测试,并通过这些环境非常愉快地推动。事实证明,一旦您投资实现了整体生产路径的自动化,那么部署更多的应用程序似乎不再那么可怕。请记住,CD的目标之一就是让部署变得无聊,所以无论它的一个还是三个应用程序,只要它仍然无聊,那并不重要。
我们看到团队使用广泛的基础设施自动化的另一个领域是管理生产中的微服务。与我们之前的断言相反,只要部署无聊,整体架构和微服务之间没有太大差别,每个架构的运营环境可能会有惊人的不同。
设计失败
将服务用作组件的后果是应用程序需要进行设计,以便它们可以容忍服务的失败。任何服务电话都可能由于供应商不可用而失败,客户必须尽可能优雅地回应。与单片设计相比,这是一个缺点,因为它引入了额外的复杂性来处理它。结果是微服务团队不断反思服务失败如何影响用户体验。
断路器和生产就绪代码
断路器出现以及诸如Bulkhead和Timeout等其他模式。在构建通信应用程序时,这些模式一起实施是至关重要的。在生产中进行这种自动化测试足以让大多数运营团队通常在一周之前就开始工作。这并不是说整体建筑风格不具备复杂的监控设置能力 - 这在我们的经验中并不常见。由于服务可能随时发生故障,因此能够快速检测故障并在可能的情况下自动恢复服务很重要。微服务应用程序非常重视应用程序的实时监控,检查架构元素(数据库每秒获得多少请求)和业务相关指标(例如每分钟收到多少订单)。语义监控可以提供一个预警系统,使系统发生错误,从而触发开发团队进行跟踪和调查。这对微服务架构尤其重要,因为微服务对编排和事件协作的偏好 会导致出现紧急行为。尽管许多权威人士称赞偶然出现的价值,但事实是,紧急行为有时可能是一件坏事。监控对迅速发现不良紧急行为至关重要,因此可以进行修复。
同步呼叫是有害的?
任何时候您在服务之间进行多次同步呼叫都会遇到停机时间的倍增效应。简单地说,这是您的系统停机时间成为各个组件停机时间的产物。您面临选择,使您的呼叫异步或管理停机时间。单体应用可以被建造成像微服务一样透明 - 事实上,它们应该是。不同的是,您绝对需要知道在不同进程中运行的服务何时断开连接。由于库在相同的过程中,这种透明度不太可能有用。微服务团队希望看到针对每个服务的复杂的监控和日志记录设置,例如显示增加/停止状态的仪表板以及各种与运营和业务相关的指标。有关断路器状态,当前吞吐量和延迟的详细信息是我们经常遇到的其他示例。
进化设计
微服务从业者通常来自进化设计背景,并将服务分解视为进一步的工具,以使应用程序开发人员能够控制其应用程序中的更改,而不会降低更改速度。变更控制并不一定意味着降低变更 - 通过正确的态度和工具,您可以对软件进行频繁,快速且控制良好的变更。无论您何时试图将软件系统分解为组件,您都面临着如何分割这些组件的决定 - 我们决定切割应用程序的原则是什么?组件的关键属性是独立替换和可升级性的概念 - 这意味着我们寻找可以想象重写组件而不影响其协作者的点。事实上许多微服务集团都明确预计许多服务将被废弃,而不是长远发展。使用单体应用时,任何更改都需要完整构建和部署整个应用程序。但是,对于微服务,您只需重新部署您修改的服务。这可以简化并加速发布过程。缺点是你不得不担心改变一个服务打破它的消费者。传统的集成方法是尝试使用版本控制来解决这个问题,但微服务领域的偏好是仅使用版本控制作为最后的手段。我们可以通过设计服务尽可能容忍供应商的变化来避免大量的版本控制。