微服务是一种非常流行的架构风格,近几年来发展势头迅猛。在这一章中,我们将从拓扑结构和哲学上两个方面概述使这种架构与众不同的重要特性。
历史
大多数架构风格都是由注意到一个特定的模式不断重现的架构师根据这样一个事实来命名 - 没有一个秘密的架构师小组来决定下一个大的发展趋势会是什么。相反,随着软件开发生态系统的转移和变化,许多架构师最终会做出共同的决策。处理这些变化并从中获益的常见最佳方法成为其他人效仿的架构风格。
微服务在这方面有所不同,它在使用的早期就被命名为“Microservices”,并在2014年3月由Martin Fowler和James Lewis撰写的名为“Microservices”的博客文章中得到了普及。他们认识到这种相对较新的架构风格的许多共同特性,并对其进行了描绘。他们的博客文章帮助好奇的架构师定义了架构,并帮助他们理解了底层的哲学。
微服务在很大程度上受到领域驱动设计(Domain Driven Design,DDD)的启发,领域驱动设计是软件项目的一种逻辑设计过程。DDD中的一个概念,即有界上下文(bounded context)显然地启发了微服务。有界上下文的概念代表了一种解耦风格。当开发人员定义一个包含许多实体和行为领域时,这些实体和行为是在构件(如代码和数据库模式)中识别的。例如,一个应用可能有一个名为CatalogCheckout的领域,该领域包含诸如目录项、客户和支付等概念。在传统的单体架构中,开发人员将共享其中的许多概念,构建可重用的类和相关联的数据库。在一个有界上下文中,内部部分如代码和数据模式,被耦合在一起以产生预期的结果;但它们从不耦合到有界上下文之外的任何内容,例如来自另一个有界上下文的数据库或类定义。这允许每个上下文只定义它所需要的,而不容纳其他构成要素。
虽然重用是有益的,但请记住软件架构中关于权衡的第一定律。重用的负面结果是耦合。当一个架构师设计一个偏向重用的系统时,他们也喜欢通过继承或组合来实现重用。
然而,如果架构师的目标需要高度的解耦,那么他们更喜欢复制而不是重用。微服务的主要目标是高度解耦,对有界上下文的逻辑概念进行物理建模。
拓扑结构
微服务的拓扑结构如图17-1所示。
图17-1.微服务架构风格的拓扑结构如图17-1所示,由于其单一用途的性质,微服务中的服务规模比其他分布式架构要小得多,如编制驱动的面向服务架构。架构师期望每个服务都包含独立运行的所有必要部分,包括数据库和其他相关组件。不同的特性在下面的章节中呈现。
分布式
微服务形成了一个分布式架构:每个服务在自己的进程中运行,这最初意味着一台物理计算机,但很快就演变成了虚拟机和容器。将服务解耦到这种程度,可以为架构中的一个常见问题提供一个简单的解决方案,即为托管应用程序所需要的庞大的多租户基础设施。例如,当使用应用服务器管理多个正在运行的应用程序时,它允许在操作上重用网络带宽、内存、磁盘空间和许多其他好处。但是,如果所有受支持的应用持续增长,最终共享基础设施上的某些资源就会受到限制。另一个问题与共享应用之间不适当的隔离有关。
将每个服务分离到各自的进程中可以解决共享带来的所有问题。在免费开源操作系统的进化开发与自动机器资源调配相结合之前,每个领域拥有各自的基础设施是不切实际的。然而,现在,有了云资源和容器技术,团队可以在领域和操作级别上获得极度解耦的好处。
性能往往是微服务分布式特性的负面影响。网络调用比方法调用花费的时间要长得多,每个端点的安全验证都会增加额外的处理时间,这要求架构师在设计系统时仔细考虑粒度大小的影响。
由于微服务是一种分布式架构,有经验的架构师建议不要使用跨服务边界的事务,因此确定服务的粒度是该架构成功的关键。
有界上下文
微服务的驱动哲学是有界上下文的概念:每个服务为一个领域或工作流建模。因此,每个服务都包含在应用中操作所需的一切,包括类、其他子组件和数据库模式。这种理念推动了架构师在这种架构中做出的许多决策。例如,在单体架构中,开发人员在应用程序的不同部分之间共享公共类(如Address)是很常见的。然而,微服务试图避免耦合,因此构建这种架构风格的架构师更喜欢复制而不是耦合。
微服务将按领域划分的架构的概念发挥到了极致。每个服务都代表一个领域或子领域;在许多方面,微服务是领域驱动设计中逻辑概念的物理体现。
粒度
架构师很难在微服务中找到适当的服务粒度,并且经常会犯错误造成服务粒度划分得太小,这就要求他们在服务之间建立通信链路,以执行有用的工作。
术语“微服务”是一个标签,而不是一个描述。 --- Martin Fowler
换言之,这个术语的创始者需要称这种新风格为某种东西,他们选择了“微服务”来将其与当时占主导地位的面向服务的架构风格(service-oriented architecture)进行对比,后者可以被称为“巨型服务”。然而,许多开发人员将术语“微服务”作为命令,而不是描述,并且创建的服务过于细粒度。
微服务中服务边界的目的是捕获领域或工作流。在某些应用中,这些自然边界对于系统的某些部分来说可能很大 - 一些业务流程比其他流程更耦合。以下是架构师可以用来帮助找到适当边界的一些准则:
目的
最明显的边界依赖于架构风格的启发,一个领域。理想情况下,每个微服务都应该在功能上非常内聚,代表整个应用贡献一个重要的行为。
事务
有界上下文是业务工作流,通常需要在事务中进行协作的实体会向架构师显示良好的服务边界。因为事务在分布式架构中会导致问题,如果架构师能够设计他们的系统来避免这些问题,那么他们就会生成更好的设计。
编排
如果一个架构师构建了一组服务提供了出色的领域隔离,但需要大量的通信才能正常工作,那么架构师可以考虑将这些服务绑定回一个更大的服务中,以避免通信开销。
迭代是确保良好服务设计的唯一方法。架构师在第一次使用时很少发现完美的粒度、数据依赖性和通信风格。然而,在对各种选项进行迭代之后,架构师很有可能改进他们的设计。
数据隔离
由有界上下文概念驱动的微服务的另一个需求是数据隔离。许多其他架构风格使用单个数据库来实现持久化。然而,微服务试图避免各种耦合,包括用作集成点的共享模式和数据库。
数据隔离是一个架构师在查看服务粒度时必须考虑的另一个因素。架构师必须警惕实体陷阱(在“实体陷阱”中讨论),而不是简单地将其服务建模为类似于数据库中的各个单独的实体。
架构师习惯于使用关系数据库来统一系统中的值,从而创建一个单一的真相来源,这在跨架构分发数据时不再是一个选项。因此,架构师必须决定如何处理这个问题:要么将一个领域标识为某个事实的真相来源,并与之协调以检索各种值,要么使用数据库复制或缓存来分发信息。
这种级别的数据隔离带来了麻烦,同时也带来了机会。现在团队不再被迫围绕单个数据库进行统一,每个服务都可以根据价格、存储类型或许多其他因素选择最合适的工具。团队在高度解耦的系统去改变他们的想法,选择更合适的数据库(或其他依赖关系)而不影响其他团队的情况下拥有优势,因为其他团队不允许耦合到实现细节。
API层
大多数微服务的描绘中都包含一个API层,位于系统的消费者(用户界面或来自其他系统的调用)之间,但它是可选的。它之所以常见,是因为它在架构中提供了一个很好的位置来执行有用的任务,既可以作为代理来间接执行,也可以连接到操作设施,例如命名服务(在“操作重用”中介绍)。虽然API层可以用于各种各样的事情,但如果架构师希望忠实于此架构的根本理念,则不应将其用作中介器或编制工具:此架构中所有有趣的逻辑都应发生在有界上下文中,而将编制或其他逻辑放在中介器中则违反了这一规则。这也说明了架构中技术划分和领域划分的区别:架构师通常在技术划分的架构中使用中介器,而微服务是严格的领域划分。
操作重用
考虑到微服务更喜欢复制而不是耦合,架构师如何处理架构中真正受益于耦合的部分,比如监控、日志记录和断路器之类的操作问题?传统的面向服务架构中的一个理念是尽可能多地重用功能,包括领域和操作。在微服务中,架构师试图将这两个关注点分开。
一旦一个团队构建了多个微服务,他们就会意识到每个微服务都有共同的元素,这些元素可以从相似性中获益。例如,如果一个组织允许每个服务团队自己实施监控,那么他们如何确保每个团队都这样做?另外他们如何处理升级之类的问题?升级到新版本的监控工具是否成为每个团队的责任,而且需要多长时间
sidecar模式为这个问题提供了一个解决方案,如图17-2所示。
图17-2. 微服务中的sidecar模式在图17-2中,常见的操作问题作为一个单独的组件出现在每个服务中,可以由单个团队或共享基础设施团队拥有。sidecar组件处理团队从耦合中受益的所有操作问题。因此,当需要升级监控工具时,共享基础设施团队可以更新sidecar,每个微服务都会接收到新的功能。
一旦团队知道每个服务都包含一个公共的sidecar,他们就可以构建一个服务网格(service mesh),允许跨架构对日志记录和监视等问题进行统一控制。公共的侧车组件连接起来,在所有的微服务中形成一个一致的操作界面,如图17-3所示。
图17-3. 服务平面在服务网格中连接sidecar在图17-3中,每个sidecar连接到服务平面,这形成了与每个服务的一致接口。
服务网格本身形成了一个控制台,允许开发人员全面访问服务,如图17-4所示。
图17-4.服务网格形成了微服务操作方面的整体视图每个服务在整个网格中形成一个节点,如图17-4所示。服务网格形成了一个控制台,允许团队全局控制操作耦合,例如监视级别、日志记录和其他交叉操作关注点。
架构师使用服务发现作为在微服务架构中构建弹性的一种方法。一个请求不单单是调用单个服务,而是通过可以监视请求的数量和频率的服务发现工具,并启动新的服务实例来处理规模或弹性问题。架构师通常在服务网格中包含服务发现,使其成为每个微服务的一部分。API层通常用于托管服务发现,允许用户界面或其他调用系统在一个地方以弹性、一致的方式查找和创建服务。
前端
微服务偏爱于解耦,理想情况下,它将包含用户界面和后端关注点。事实上,微服务的最初设想包括用户界面作为有界上下文的一部分,忠实于DDD中的原则。然而,web应用所需的分区的实用性和其他外部约束使得这个目标很难实现。因此,两种类型的用户界面通常出现在微服务架构中;第一种出现在图17-5中。
图17-5.具有单体用户界面的微服务架构在图17-5中,单体前端具有单个用户界面,通过API层进行调用以满足用户请求。前端可以是富桌面、移动或web应用。例如,许多web应用现在使用JavaScript web框架来构建单个用户界面。
用户界面的第二个选项使用微前端,如图17-6所示。
图17-6.微服务中的微前端模式在图17-6中,这种方法利用用户界面级别的组件在作为后端服务的用户界面中创建粒度和隔离度的同步级别。每个服务发出该服务的用户界面,前端与其他发出的用户界面组件协调。使用此模式,团队可以将服务边界从用户界面到后端服务进行隔离,从而在单个团队中统一负责整个领域。
开发人员可以通过多种方式实现微前端模式,可以使用基于组件的web框架(如React),也可以使用支持此模式的几个开源框架之一。
通信
在微服务中,架构师和开发人员都在努力寻找合适的粒度,这会影响数据隔离和通信。找到正确的沟通方式有助于团队保持服务解耦的同时仍然以实用的方式进行协调。
基本上,架构师必须决定使用同步或异步通信。同步通信要求调用者等待被调用者的响应。微服务架构通常利用协议感知的异构互操作性。我们将为您分解该术语:
协议感知(Protocol-aware)
因为微服务通常不包括统一的集成中心来避免操作耦合,所以每个服务都应该知道如何调用其他服务。因此,架构师通常将特定服务的相互调用方式标准化:特定级别的REST、消息队列等等。这意味着服务必须知道(或发现)使用哪个协议来调用其他服务。
异构(Heterogeneous)
因为微服务是一种分布式架构,所以每个服务都可以使用不同的技术栈来编写。异构建议微服务完全支持多语言环境,不同的服务使用不同的平台。
互操作性(Interoperability)
这描述相互调用的服务。虽然微服务的架构师试图阻止事务性方法调用,但服务通常通过网络调用其他服务来协作和发送/接收信息。
强制异构性
一位著名的架构师是微服务风格的先驱,他是一家针对移动设备的个人信息管理的初创公司的首席架构。因为他们有一个快速变化的问题领域,架构师希望确保没有一个开发团队会意外地在彼此之间创建耦合点,从而妨碍团队独立运作的能力。结果发现这个架构师在团队中拥有广泛的技术技能组合,因此要求每个开发团队使用不同的技术栈。如果一个团队使用Java,另一个团队使用.NET,那么就不可能意外地共享类!
这种方法与大多数企业治理策略截然相反,后者坚持在单个技术堆栈上实现标准化。微服务世界的目标不是创造尽可能复杂的生态系统,而是为收窄的问题范围选择正确规模的技术。并不是每一项服务都需要一个行业级的关系数据库,将其强加于小型团队会减缓他们的速度,而不是让他们受益。这个概念利用了微服务的高度解耦特性。
对于异步通信,架构师通常使用事件和消息,因此在内部使用事件驱动架构,如第14章所述;代理和中介模式在微服务中表现为编排和编制。
编排(Choreography)和编制(Orchestration)
编排使用与代理事件驱动架构相同的通信风格。换句话说,在这个架构中不存在中心协调器,遵守有界上下文的哲学。因此,架构师发现在服务之间实现解耦的事件是很自然的。
领域/架构同构是架构师在评估一个架构风格对某个特定问题的适用性时应该探索的一个关键特征。这个术语描述了架构的状况如何映射到特定的架构风格。例如,在图8-7中,Silicon Sandwiches的技术分区架构在结构上支持可定制化,而微内核架构风格提供了相同的通用结构。因此,需要高度定制的问题变得更容易在微内核中实现。
类似地,因为架构师在微服务架构中的目标是支持解耦,所以微服务的状况类似于代理EDA,使这两种模式共生。
在编排中,每个服务根据需要调用其他服务,而不需要集中的中介。例如,考虑图17-7所示的场景。
图17-7.在微服务中使用编排来管理协调在图17-7中,用户请求有关用户愿望列表的详细信息。因为CustomerWishList服务不包含所有必要的信息,所以它调用CustomerDemographics来检索缺失的信息,并将结果返回给用户。
由于微服务架构不像其他面向服务的架构那样包含一个全局中介,如果架构师需要跨多个服务进行协调,他们可以创建自己的本地化中介,如图17-8所示。
图17-8.在微服务中使用编制在图17-8中,开发人员创建了一个服务,其唯一职责是协调调用以获取特定客户的所有信息。用户调用ReportCustomerInformation中介器,该中介器调用其他必要的服务。
软件架构的第一定律表明,这两种解决方案都不是完美的,每种解决方案都有利弊得失。
在编排中,架构师保留了高度解耦的架构风格哲学,从而获得了该风格所宣扬的最大利益。然而,像错误处理和协调这样的常见问题在编排的环境中变得更加复杂。
考虑一个更复杂工作流的例子,如图17-9所示。
图17-9.对一个复杂业务流程使用编排在图17-9中,第一个被调用的服务必须跨各种各样的其他服务进行协调,除了它的其他域职责外,基本上还充当一个中介。这种模式称为front controller模式,其中一个名义上经过编排的服务成为解决某些问题的更复杂的中介。这种模式的缺点是增加了服务的复杂性。
或者,架构师可以选择对复杂的业务流程使用编制,如图17-10所示。
图17-10.对一个复杂业务流程使用编制在图17-10中,架构师构建了一个中介来处理业务工作流所需的复杂性和协调性。虽然这会在这些服务之间产生耦合,但它允许架构师将协调的重点放在单个服务中,而不影响其他服务。通常,领域工作流是内在耦合的,架构师的工作需要找到以支持领域和架构目标的方式表示这种耦合的最佳方式。
事务与Sagas
架构师渴望在微服务中实现极端的解耦,但随后经常会遇到如何跨服务进行事务性协调的问题。因为架构中的解耦鼓励数据库也达到相同的级别,所以在单体应用中微不足道的原子性在分布式应用中成为一个问题。
跨服务边界构建事务违反了微服务架构核心的解耦原则(也创造了最糟糕的动态共生性,价值的共生)。对于希望跨服务进行事务处理的架构师,最好的建议是:不要这样做!应该通过修改组件的颗粒度来代替。通常,构建微服务架构的架构师发现需要将服务与事务连接在一起时,他们的设计会变得过于精细。事务边界是服务粒度的常用指标之一。
提示
不要在微服务中执行事务,而是调整粒度!
例外总是存在的。例如,可能出现这样的情况:两个不同的服务需要非常不同的架构特性,需要清晰的服务边界,但仍然需要事务协调。在这些情况下,存在处理事务编制的模式,需要进行认真的权衡。
微服务中一种流行的分布式事务模式是saga模式,如图17-11所示。
图17-11.微服务架构中的saga模式在图17-11中,一个服务充当多个服务调用的中介,并协调事务。中介器调用事务的每个部分,记录成功或失败,和协调结果。如果一切按计划进行,服务中的所有值和它们所包含的数据库会同步更新。
在错误情况下,中介必须确保如果某个部分失败,则事务的任何部分都不会成功。考虑图17-12所示的情况。
图17-12.Saga模式补偿错误条件下的事务在图17-12中,如果事务的第一部分成功,而第二部分失败,则中介必须向事务的所有成功部分发送请求,并告诉它们撤消先前的请求。这种类型的事务协调称为补偿事务框架。开发人员实现此模式的方法通常是让来自中介的每个请求进入挂起状态,直到中介指示整体成功为止。但是,如果必须处理异步请求,特别是如果出现新的请求取决于挂起的事务状态,则此设计将变得复杂。这也会在网络级别创建大量的协调通信量。
补偿事务框架的另一种实现是让开发人员为每个潜在的事务操作构建do和undo。这使得事务期间的协调更少,但undo操作往往比do操作复杂得多,使设计、实现和调试工作增加了一倍多。
虽然架构师可以构建跨服务的事务行为,但这与选择微服务模式的原因背道而驰。例外总是存在的,所以对架构师最好的建议是谨慎地使用saga模式。
提示
一些跨服务的事务有时是必要的;如果它是架构的主要特性,那么就犯了错误!
架构特性评级
微服务架构风格在我们的标准评级规模上提供了几个极端,如图17-13所示。特性评级表中的一星级评级意味着特定的架构特性在某种架构中没有得到很好的支持,而五星评级意味着架构特性是某种架构风格中最强大的特性之一。记分卡中确定的每个特性的定义见第4章。
值得注意的是对现代工程实践的高度支持,如自动化部署、可测试性和其他未列出的实践。如果没有DevOps革命和不断向自动化操作迈进,微服务就不可能存在。
由于微服务是一种分布式架构,它遭受由运行时连接在一起的片段构成的架构中存在许多固有的缺陷。因此,当使用过多的服务间通信时,容错性和可靠性会受到影响。然而,这些评级只指出了架构中的趋势;开发人员通过服务发现中的冗余和扩展能力修复了许多这些问题。然而,在正常情况下,独立的、单一用途的服务通常会得到高容错性,因此在微服务架构中对该特性的评级很高。
图17-13.微服务评级这种架构得分高的是可扩展性、弹性和进化性。一些最具可扩展性的系统已经利用这种风格取得了巨大的成功。类似地,由于架构在很大程度上依赖于与操作的自动化和智能集成,开发人员还可以在架构中构建弹性支持。因为架构支持增量级别的高度解耦,所以它支持进化变化的现代业务实践,甚至在架构级别上也是如此。
现代商业发展迅速,软件开发往往难以跟上节奏。通过构建一个具有高度解耦的极小部署单元的架构,架构师拥有了一个能够支持更快变化速率的结构。
在微服务中,性能常常是一个问题:分布式架构必须进行许多网络调用才能完成工作,这会带来很高的性能开销,并且必须调用安全检查来验证每个端点的身份和访问。微服务世界中存在许多提高性能的模式,包括智能数据缓存和复制以防止过多的网络调用。性能是微服务经常使用编排而不是编制的另一个原因,因为较少的耦合允许更快的通信和较少的瓶颈。
微服务无疑是一种以领域为中心的架构,其中每个服务边界都应该对应于领域。它也拥有现代架构中最独特的量子:在许多方面,它是量子尺度评估的典型。极度解耦的驱动哲学在这种架构中造成了许多麻烦,但如果做得好,会产生巨大的好处。就像在任何架构中一样,架构师必须理解规则以聪明地打破它们。
附加参考文献
虽然我们在本章中的目标是触及这种架构风格的一些重要方面,但是有许多优秀的资源可以进一步和更详细地了解这种架构风格。有关微服务的更多详细信息,请参阅以下参考资料:
-《Building Microservices》- Sam Newman(O’Reilly)
-《Microservices vs Service-Oriented Architecture》- Mark Richards(O’Reilly)
《Microservices AntiPatterns and Pitfalls》- Mark Richards(O’Reilly)
网友评论