微服务架构10个最重要的设计模式

Posted 肉眼品世界

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了微服务架构10个最重要的设计模式相关的知识,希望对你有一定的参考价值。

自从软件开发的早期(1960年代)以来,解决大型软件系统中的复杂性一直是一项艰巨的任务。多年来,软件工程师和架构师为解决软件系统的复杂性进行了许多尝试:David Parnas的模块化和信息隐藏(1972),Edsger W. Dijkstra的关注分离(1974),面向服务的体系结构(1998)。

他们所有人都使用了久经考验的成熟技术来解决大型系统的复杂性:分而治之。自2010年代以来,这些技术不足以解决Web规模应用程序或现代大型企业应用程序的复杂性。结果,架构师和工程师开发了一种新方法来解决现代软件系统的复杂性:微服务架构。它也使用了相同的旧"分而治之"技术,尽管采用了新颖的方式。

软件设计模式是解决软件设计中常见问题的通用,可重用的解决方案。设计模式可帮助我们共享通用词汇,并使用经过实战检验的解决方案,而不是重新发明轮子。今天描述的是一组设计模式,以帮助您实现这些最佳实践。

本文主要内容:

· 微服务架构

· 微服务架构的优势

· 微服务架构的缺点

· 何时使用微服务架构

· 微服务架构设计模式


请注意,此清单的大多数设计模式都有几种上下文,可以在非微服务体系结构中使用。但是我将在微服务架构的背景下对其进行描述。

微服务架构

微服务体系结构:简要概述以及为什么要在下一个项目中使用它以及模块化单片软件体系结构真的死了吗?

我的微服务架构定义是:

微服务架构旨在将大型,复杂的系统垂直(按功能或业务要求)划分为较小的子系统,这些子系统属于流程(因此可独立部署),并且这些子系统之间通过与语言无关的轻量级网络通信相互通信(例如REST,gRPC)或异步(通过消息传递)方式。

这是具有微服务架构的业务Web应用程序的组件视图:


> Microservice Architecture by Md Kamaruzzaman

微服务架构的重要特征

· 整个应用程序分为多个单独的进程,每个进程可以包含多个内部模块。

· 与模块化Monoliths或SOA相反,微服务应用程序是垂直拆分的(根据业务能力或领域)微服务边界是外部的。结果,微服务通过网络调用(RPC或消息)相互通信。

· 由于微服务是独立的流程,因此它们可以独立部署。他们以轻巧的方式交流,不需要任何智能交流渠道。


微服务架构的优势

· 更好的开发规模。

· 更高的发展速度。

· 支持迭代或增量现代化。

· 充分利用现代软件开发生态系统(云,容器,DevOps,无服务器)的优势。

· 支持水平缩放和粒度缩放。

· 由于尺寸较小,它降低了开发人员的认知复杂度。


微服务架构的缺点

· 大量的活动部件(服务,数据库,流程,容器,框架)。

· 复杂性从代码转移到基础架构。

· RPC调用和网络流量的激增。

· 管理整个系统的安全性具有挑战性。

· 设计整个系统比较困难。

· 介绍分布式系统的复杂性。


何时使用微服务架构:

· Web规模应用程序开发。

· 当多个团队处理应用程序时,进行企业应用程序开发。

· 长期收益优先于短期收益。

· 该团队拥有能够设计微服务架构的软件架构师或高级工程师。


微服务架构的设计模式

每个微服务独占数据库

一旦公司用许多较小的微服务替换了大型的单片系统,它面临的最重要的决定就是关于数据库。在整体架构中,使用大型中央数据库。许多架构师都喜欢保留数据库原样,即使他们转向微服务架构也是如此。尽管它提供了一些短期好处,但它是一种反模式,尤其是在大规模系统中,因为微服务将紧密耦合在数据库层中。转向微服务的整个目标将失败(例如,团队授权,独立开发)。

更好的方法是为每个微服务都提供自己的数据存储,以使数据库层中的服务之间不存在强耦合。在这里,我使用数据库一词来表示数据的逻辑分离,即微服务可以共享同一物理数据库,但是它们应该使用单独的架构/集合/表。它还将确保根据域驱动设计正确隔离微服务。

> Database per Microservice by Md Kamaruzzaman

优点:

· 数据对服务的完全所有权。

· 开发服务的团队之间的松耦合。


缺点:

· 在服务之间共享数据变得充满挑战。

· 提供应用程序范围的ACID事务保证变得更加困难。

· 将Monolith数据库分解为较小的零件需要仔细设计,这是一项艰巨的任务。


每个微服务何时使用数据库:

· 在大型企业中的应用。

· 当团队需要其微服务的完全所有权以进行开发扩展和提高开发速度时。


什么时候不使用每个微服务的数据库:

· 在小型应用中。

· 如果一个团队开发所有微服务。


启用技术示例

所有SQL和NoSQL数据库都提供逻辑上的数据分离(例如,分离的表,集合,模式,数据库)。


事件源 Event Sourcing

在微服务架构中,尤其是在每个微服务使用数据库的情况下,微服务需要交换数据。对于有弹性,高度可扩展和容错的系统,它们应通过交换事件进行异步通信。

在这种情况下,可能需要进行原子操作,例如,更新数据库并发送消息。如果您有SQL数据库,并且希望为大量数据分配分布式事务,则不能使用两阶段锁定(2PL),因为它无法扩展。

如果使用NoSQL数据库并希望具有分布式事务,则不能使用2PL,因为许多NoSQL数据库不支持两阶段锁定。

在这种情况下,请结合使用基于事件的体系结构和事件源。在传统数据库中,具有当前"状态"的业务实体被直接存储。在事件源中,将存储任何状态更改事件或其他重要事件,而不是实体。

这意味着业务实体的修改将保存为一系列不可变的事件。通过在给定时间重新处理该业务实体的所有事件,可以扣除该业务实体的状态。因为数据存储为一系列事件,而不是通过直接更新数据存储来存储,所以各种服务可以从事件存储中重播事件以计算其各自数据存储的适当状态。

> Event Sourcing by Md Kamaruzzaman

优点:

· 为高度可扩展的系统提供原子性。

· 实体的自动历史记录,包括时间旅行功能。

· 松散耦合和事件驱动的微服务。


缺点:

· 从事件存储中读取实体变得具有挑战性,通常需要额外的数据存储(CQRS模式)

· 系统的整体复杂性增加,通常需要域驱动设计。

· 系统需要处理重复事件(幂等)或丢失事件。

· 迁移事件模式变得具有挑战性。


何时使用事件来源:

· 具有SQL数据库的高度可扩展的事务系统。

· 带有NoSQL数据库的事务系统。

· 高度可扩展且具有弹性的微服务架构。

· 典型的消息驱动或事件驱动系统(电子商务,预订和预订系统)。


何时不使用事件来源:

· 具有SQL数据库的低伸缩性事务系统。

· 在简单的微服务架构中,微服务可以同步交换数据(例如,通过API)。


启用技术示例

· 事件存储:EventStoreDB,Apache Kafka,Confluent Cloud,AWS Kinesis,Azure事件中心,GCP发布/订阅,Azure Cosmos DB,MongoDB,Cassandra。Amazon DynamoDB,

· 框架:Lagom,Akka,Spring,akkatecture,Axon,Eventuate


命令查询职责隔离(CQRS)

如果我们使用事件源,那么从事件存储中读取数据将变得充满挑战。要从数据存储中获取实体,我们需要处理所有实体事件。另外,有时我们对读写操作有不同的一致性和吞吐量要求。

在这种用例中,我们可以使用CQRS模式。在CQRS模式中,系统的数据修改部分(命令)与数据读取(查询)部分分开。CQRS模式有两种形式:简单和高级,这导致软件工程师之间产生一些混淆。

以简单的形式,不同的实体或ORM模型用于读取和写入,如下所示:

> CQRS (simple) by Md Kamaruzzaman

它有助于实施"单一责任原则"和"关注点分离",从而使设计更简洁。

在其高级形式中,不同的数据存储区用于读取和写入操作。高级CQRS与事件来源一起使用。根据使用情况,使用不同类型的写入数据存储和读取数据存储。写入数据存储区是"记录系统",即整个系统的黄金来源。

> CQRS (advanced) by Md Kamaruzzaman

对于重读应用程序或微服务体系结构,将OLTP数据库(任何提供ACID事务保证的SQL或NoSQL数据库)或分布式消息平台用作写存储。对于繁重的写程序(高写可伸缩性和吞吐量),使用了水平可写伸缩的数据库(公共云全局数据库)。规范化的数据保存在写入数据存储中。

为搜索(例如Apache Solr,Elasticsearch)或读取(键值数据存储,文档数据存储)而优化的NoSQL数据库用作读取存储。在许多情况下,在需要SQL查询的地方使用可伸缩的SQL数据库。归一化和优化的数据将保存在读取存储中。

数据从写入存储异步复制到读取存储。结果,读存储区滞后于写存储区,并且最终保持一致。

优点:

· 在事件驱动的微服务中更快地读取数据。

· 数据的高可用性。

· 读写系统可以独立扩展。


缺点:

· 读取数据存储弱一致性(最终一致性)

· 系统的整体复杂性增加。货运培训CQRS可能会严重危害整个项目。


何时使用CQRS:

· 在使用事件源的高度可扩展的微服务体系结构中。

· 在读取数据需要查询到多个数据存储区的复杂域模型中。

· 在读写操作具有不同负载的系统中。


何时不使用CQRS:

· 在微事件数量微不足道的微服务体系结构中,使用事件存储快照来计算实体状态是更好的选择。

· 在读写操作具有相似负载的系统中。


启用技术示例

· 写存储:EventStoreDB,Apache Kafka,Confluent Cloud,AWS Kinesis,Azure Event Hub,GCP发布/订阅,Azure Cosmos DB,MongoDB,Cassandra。亚马逊DynamoDB

· 阅读商店:Elastic Search,Solr,Cloud Spanner,Amazon Aurora,Azure Cosmos DB,Neo4j

· 框架:Lagom,Akka,Spring,akkatecture,Axon,Eventuate


SAGA

如果您将微服务体系结构与每个微服务的数据库一起使用,那么通过分布式事务管理一致性就具有挑战性。您不能使用传统的两阶段提交协议,因为它无法扩展(SQL数据库)或不被支持(许多NoSQL数据库)。

您可以将Saga模式用于Microservice Architecture中的分布式事务。Saga是一种旧模式,于1987年开发,作为SQL数据库中长期运行的数据库事务的概念替代方案。但是,这种模式的现代变体对于分布式事务也非常有效。Saga模式是一个本地事务序列,其中每个事务在单个微服务中更新数据存储中的数据并发布事件或消息。传奇中的第一个事务由外部请求(事件或操作)启动。一旦本地事务完成(数据存储在数据存储中,并且发布消息或事件),发布的消息/事件将触发Saga中的下一个本地事务。

> Saga by Md Kamaruzzaman

如果本地事务失败,则Saga执行一系列补偿事务,以撤消先前本地事务的更改。

Saga交易协调主要有两种变体:

· 分散的协调,每个微服务生成并收听其他微服务的事件/消息,并决定是否应该采取措施。

· 统筹协调,协调器告诉协调的微服务哪些本地事务需要执行。


优点:

· 通过高度可扩展的或松散耦合的,事件驱动的微服务架构中的事务来提供一致性。

· 通过使用没有2PC支持的NoSQL数据库的微服务体系结构中的事务来提供一致性。


缺点:

· 需要处理短暂故障,并应提供幂等性。

· 难以调试,并且随着微服务数量的增加,复杂性也随之增加。


何时使用

· 在使用事件源的高度可扩展的,松散耦合的微服务架构中。

· 在使用分布式NoSQL数据库的系统中。


什么时候不使用

· 具有SQL数据库的低伸缩性事务系统。

· 在服务之间存在循环依赖性的系统中。


启用技术示例:

Axon,Eventuate,Narayana


前端的后端(BFF)

在现代业务应用程序开发中,尤其是在微服务体系结构中,前端和后端应用程序是分离的和独立的服务。它们通过API或GraphQL连接。如果应用程序还具有Mobile App客户端,则对Web和Mobile客户端使用相同的后端微服务将成为问题。移动客户端的API要求通常与Web客户端不同,因为它们具有不同的屏幕大小,显示,性能,能源和网络带宽。

后端的后端模式可用于每个UI都有为特定UI定制的单独后端的场景。它还提供了其他优势,例如充当下游微服务的外观,从而减少了UI与下游微服务之间的闲聊通信。同样,在高度安全的情况下,下游微服务部署在DMZ网络中,BFF用于提供更高的安全性。

> Backends for Frontends by Md Kamaruzzaman

优点:

· BFF之间的关注点分离。我们可以针对特定的UI优化它们。

· 提供更高的安全性。

· 减少UI与下游微服务之间的交流。


缺点:

· BFF之间的代码重复。

· 如果使用其他许多UI(例如,智能电视,Web,移动设备,台式机),BFF的数量也会激增。

· BFF不应包含任何业务逻辑,而应仅包含特定于客户的逻辑和行为,因此需要仔细设计和实施。


何时将后端用于前端:

· 如果应用程序具有多个具有不同API要求的UI。

· 如果出于安全原因在UI和下游微服务之间需要额外的一层。

· 如果在UI开发中使用微前端。


何时不使用后端作为前端:

· 如果应用程序具有多个UI,但是它们使用相同的API。

· 如果未在DMZ中部署核心微服务。


启用技术示例:

任何后端框架(Node.js,Spring,Django,Laravel,Flask,Play等)都支持它。


API网关

在微服务架构中,UI通常与多个微服务连接。如果微服务是细粒度的(FaaS),则客户端可能需要连接许多微服务,这变得很繁琐且具有挑战性。而且,服务(包括其API)可以发展。大型企业还希望拥有其他跨领域的问题(SSL终止,身份验证,授权,限制,日志记录等)。

解决这些问题的一种可能方法是使用API网关。API网关位于客户端APP和后端微服务之间,并充当外观。它可以用作反向代理,将客户端请求路由到适当的后端微服务。它还可以支持将客户端请求的扇出扩展到多个微服务,然后将汇总的响应返回给客户端。它还支持基本的跨领域关注。

> API Gateway by Md Kamaruzzaman

优点:

· 提供前端和后端微服务之间的松散耦合。

· 减少客户端和微服务之间的往返呼叫次数。

· 通过SSL终止,身份验证和授权实现高安全性。

· 集中管理的跨领域问题,例如日志记录和监视,节流,负载平衡。


缺点:

· 可能导致微服务架构中的单点故障。

· 由于额外的网络呼叫,延迟增加了。

· 如果不进行扩展,它们很容易成为整个企业的瓶颈。

· 额外的维护和开发成本。


何时使用API网关:

· 在复杂的微服务架构中,这几乎是强制性的。

· 在大型公司中,必须使用API网关来集中安全性和跨领域问题。


何时不使用API网关:

· 在安全性和中央管理不是最高优先级的私人项目或小型公司中。

· 如果微服务的数量很小。


启用技术示例:

Amazon API Gateway,Azure API管理,Apigee,Kong,WSO2 API管理器


扼杀者

如果要在棕地项目中使用微服务架构,则需要将旧版或现有的Monolithic应用程序迁移到微服务。将现有的大型生产单片式应用程序迁移到微服务中具有很大的挑战性,因为这可能会破坏应用程序的可用性。

一种解决方案是使用Strangler模式。Strangler模式意味着通过逐步用新的微服务替换特定功能,将Monolithic应用程序逐步迁移到微服务架构。此外,新功能仅在微服务中添加,绕过了传统的Monolithic应用程序。然后将Facade(API网关)配置为在旧版Monolith和微服务之间路由请求。一旦功能从Monolith迁移到微服务,Facade就会拦截客户端请求并路由到新的微服务。一旦所有旧版Monolithic功能都已迁移,旧版Monolithic应用程序将被"勒死",即退役。


> Strangler by Md Kamaruzzaman

优点:

· 将Monolithic应用程序安全迁移到微服务。

· 迁移和新功能开发可以并行进行。

· 迁移过程可以有自己的进度。


缺点:

· 在现有的Monolith和新的微服务之间共享数据存储变得充满挑战。

· 添加外观(API网关)将增加系统延迟。

· 端到端测试变得困难。


何时使用Strangler:

将大型后端单片应用程序增量迁移到微服务。


何时不使用Strangler:

· 如果后端整体组件较小,则批量替换是一个更好的选择。

· 如果客户端对旧版Monolithic应用程序的请求无法被拦截。


推动技术:

带有API网关的后端应用程序框架。


  • 断路器

    在微服务体系结构中,微服务进行同步通信,微服务通常调用其他服务来满足业务需求。由于瞬态故障(网络连接速度慢,超时或时间不可用),对另一个服务的调用可能会失败。在这种情况下,重试呼叫可以解决此问题。但是,如果存在严重问题(微服务完全失败),则微服务将长时间不可用。在这种情况下,重试是没有意义的,并且浪费了宝贵的资源(线程被阻塞,浪费了CPU周期)。同样,一项服务的故障可能会导致整个应用程序级联故障。在这种情况下,立即失败是一种更好的方法。

    对于此类用例,可以使用断路器模式。微服务应通过代理来请求另一个微服务,该代理的工作方式类似于断路器。代理应该计算最近发生的故障数,并使用它来决定是允许操作继续进行还是直接返回异常。


    > Circuit Breaker by Md Kamaruzzaman

    断路器可以具有以下三种状态:

    · 已关闭:断路器将请求发送到微服务,并计算给定时间段内的故障数。如果在一定时间内的故障数量超过阈值,则它将跳闸并进入"打开状态"。

    · 打开:来自微服务的请求立即失败,并返回异常。超时后,断路器进入半开状态。

    · 半开放式:仅允许来自微服务的有限数量的请求通过并调用该操作。如果这些请求成功,则断路器将进入闭合状态。如果任何请求失败,则断路器进入"打开"状态。


    优点:

    · 提高微服务架构的容错性和弹性。

    · 停止将故障级联到其他微服务。


    缺点:

    · 需要复杂的异常处理。

    · 记录和监视。

    · 应该支持手动重置。


    何时使用断路器:

    · 在紧密耦合的微服务体系结构中,微服务进行同步通信。

    · 一个微服务是否依赖于多个其他微服务。


    何时不使用断路器:

    · 松散耦合的,事件驱动的微服务架构。

    · 微服务是否不依赖于其他微服务。


    推动技术:

    API网关,服务网格,各种断路器库(Hystrix,Reselience4J,Polly。


    外部化配置

    每个业务应用程序都有许多用于各种基础结构的配置参数(例如,数据库,网络,连接的服务地址,凭据,证书路径)。同样,在企业环境中,应用程序通常部署在各种运行时中(本地,开发,生产)。实现此目标的一种方法是通过内部配置,这是一种致命的不良做法。由于很容易破坏生产凭据,因此可能导致严重的安全风险。另外,配置参数的任何更改都需要重建应用程序。在微服务架构中,这一点尤为重要,因为我们可能拥有数百种服务。

    更好的方法是外部化所有配置。结果,将构建过程与运行时环境分开。此外,由于生产配置文件仅在运行时或通过环境变量使用,因此将安全风险降到最低。

    优点:

    · 生产配置不是代码库的一部分,因此可以最大程度地减少安全漏洞。

    · 无需重新构建即可更改配置参数。


    缺点:

    我们需要选择一个支持外部化配置的框架。


    何时使用外部化配置:

    任何重要的生产应用程序都必须使用外部配置。


    何时不使用外部化配置:

    在概念发展的证明。


    推动技术:几乎所有企业级的现代框架都支持外部化配置。


    消费者驱动的合同测试

    在微服务架构中,通常由独立的团队开发许多微服务。这些微服务一起工作来满足业务需求(例如,客户请求),并且彼此同步或异步地通信。消费者微服务的集成测试具有挑战性。通常,在这种情况下使用TestDouble可以进行更快,更便宜的测试。但是TestDouble通常并不代表真正的提供程序微服务。另外,如果提供者微服务更改了其API或消息,则TestDouble无法确认这一点。另一个选择是进行端到端测试。虽然在生产之前必须进行端到端测试,但它脆弱,缓慢,昂贵,并且不能替代集成测试(测试金字塔)。

    消费者驱动的合同测试可以在这方面为我们提供帮助。此处,消费者微服务所有者团队编写了一个测试套件,其中包含针对特定提供者微服务的请求和预期响应(用于同步通信)或预期消息(用于异步通信)。这些测试套件称为显式合同。对于提供商微服务,其使用者的所有合同测试套件都添加到了自动测试中。在执行针对特定提供程序微服务的自动测试时,它将运行自己的测试,合同并验证合同。通过这种方式,合同测试可以帮助以自动化的方式维护微服务通信的完整性。

    优点:

    · 如果提供者意外更改了API或消息,则会在很短的时间内自动找到它。

    · 更少的惊喜和更高的健壮性,尤其是包含大量微服务的企业应用程序。

    · 改善团队自主权。


    缺点:

    · 由于合同测试可能使用完全不同的测试工具,因此需要进行额外的工作才能· 在合同商微服务中开发和集成合同测试。

    · 如果合同测试与实际服务消耗不匹配,则可能导致生产失败。


    何时使用消费者驱动的合同测试:

    在大型企业业务应用程序中,通常,不同的团队开发不同的服务。


    何时不使用消费者主导的合同测试:

    · 一个团队开发所有微服务的相对简单,较小的应用程序。

    · 提供者微服务是否相对稳定且未处于积极开发中。


    推动技术:

    契约,邮递员,Spring Cloud合同


    结论

    在现代的大型企业软件开发中,微服务体系结构可以帮助扩展规模并带来许多长期利益。但是微服务架构并不是可以在每个用例中使用的"银弹"。

    如果在错误的应用程序类型中使用它,则微服务架构会带来更多的麻烦。

    想要采用微服务体系结构的开发团队应遵循一组最佳实践,并使用一组可重复使用的,经过严格实践的设计模式。

    微服务架构中最重要的设计模式是每个微服务的数据库。实施此设计模式具有挑战性,并且需要其他几个紧密相关的设计模式(事件源,CQRS和Saga)。

    在具有多个客户端(Web,移动,台式机,智能设备)的典型业务应用程序中,客户端与微服务之间的通信可能会比较混乱,可能需要具有附加安全性的中央控制。在这种情况下,前端的设计模式和API网关非常有用。

    同样,断路器模式可以极大地帮助处理此类应用程序中的错误情况。将旧的Monolithic应用程序迁移到微服务中具有很大的挑战性,而Strangler模式可以帮助迁移。

    消费者驱动的合同测试是微服务集成测试的工具模式。同时,外部化配置是任何现代应用程序开发中的强制性模式。

    该列表并不全面,并且取决于您的用例,您可能需要其他设计模式。但是此列表将为您提供有关微服务体系结构设计模式的出色介绍。

    推荐阅读:
    GO语言版《算法进阶指南》火了,完整版PDF下载!
    使用 Redis 实现一个轻量级的搜索引擎,牛逼啊!

    【重磅分享】从零到一搭建推荐系统指南白皮书.pdf(附48页下载链接)

    亿级(无限级)并发,没那么难

    世界的真实格局分析,地球人类社会底层运行原理

    不是你需要中台,而是一名合格的架构师(附各大厂中台建设PPT)

    字节跳动mysql学习笔记火了,完整版开放下载!


    微服务架构及其最重要的10个设计模式

    相关阅读:还在用 Guava Cache?它才是 Java 本地缓存之王!

    来源:架构头条

    作者 | TDS

    译者 | 孙微

    策划 | Tina

    微服务架构的十个设计模式分别是独享数据库、事件驱动、CQRS、Saga、BFF、API 网关、Strangler、断路器、外部化配置、消费端驱动的契约测试。

    从软件开发早期(1960 年代)开始,应对大型软件系统中的复杂性一直是一项令人生畏的任务。多年来为了应对软件系统的复杂性,软件工程师和架构师们做了许多尝试:David Parnas 的模块化和封装 (1972), Edsger W. Dijkstra (1974)的关注点分离以及 SOA(1988)。

    他们都是使用分而治之这项成熟的传统技术来应对大型系统的复杂性。自 2010 年开始,这些技术被证实无法继续应对 Web 级应用或者现代大型企业级应用的复杂性。因此架构师和工程师们发展出了一种全新的现代方式来解决这个问题,就是微服务架构。它虽然延续了分而治之的思想,但却是以全新的方式来实现的。

    软件设计模式是解决软件设计中常见问题的通用、可复用的解决方案。设计模式让我们可以分享通用词汇并使用经实战检验的方案,以免重复造轮子。我先简单介绍下微服务架构。

    通过阅读这篇文章,你会学到:

    • 微服务架构

    • 微服务架构的优势

    • 微服务架构的劣势

    • 何时使用微服务架构

    最重要的微服务架构设计模式,包括其优缺点、用例、上下文、技术栈示例及可用资源。

    请注意,本清单中的大部分设计模式常出现在多种语境中,并且可以在非微服务架构中使用。而我将在微服务这个特定语境中介绍它们。

    1微服务架构

    我在之前的博客《微服务架构概述及为什么要应用在下个项目》和《单体软件架构真的终结了吗?》中对微服务架构有非常详尽的介绍。如果你感兴趣,可以阅读这两篇博客来深入了解。

    https://towardsdatascience.com/microservice-architecture-a-brief-overview-and-why-you-should-use-it-in-your-next-project-a17b6e19adfd

    https://towardsdatascience.com/looking-beyond-the-hype-is-modular-monolithic-software-architecture-really-dead-e386191610f8

    那到底什么是微服务架构?有很多种定义方法。我的定义是这这样的:

    微服务架构指的是将大型复杂系统按功能或者业务需求垂直切分成更小的子系统,这些子系统以独立部署的子进程存在,它们之间通过轻量级的、跨语言的同步(比如 REST,gRPC)或者异步(消息)网络调用进行通信。

    下面是基于微服务架构的商业 Web 应用的组件视图:


    来自 Md Kamaruzzaman 的微服务架构

     微服务架构的重要特征

    • 整个应用程序被拆分成相互独立但包含多个内部模块的子进程

    • 与模块化的单体应用(Modular Monoliths)或 SOA 相反,微服务应用程序根据业务范围或领域垂直拆分。

    • 微服务边界是外部的,微服务之间通过网络调用(RPC 或消息)相互通信。

    • 微服务是独立的进程,它们可以独立部署。

    • 它们以轻量级的方式进行通信,不需要任何智能通信通道。

     微服务架构的优点

    • 更好的开发规模

    • 更快的开发速度

    • 支持迭代开发或现代化增量开发

    • 充分利用现代软件开发生态系统的优势(云、容器、 DevOps、Serverless)

    • 支持水平缩放和细粒度缩放

    • 小体量,较低了开发人员的认知复杂性

     微服务架构的缺点

    • 更高数量级的活动组件(服务、数据库、进程、容器、框架)

    • 复杂性从代码转移到基础设施

    • RPC 调用和网络通信的大量增加

    • 整个系统的安全性管理更具有挑战性

    • 整个系统的设计变得更加困难

    • 引入了分布式系统的复杂性

     何时使用微服务架构

    • 大规模 Web 应用开发

    • 跨团队企业级应用协作开发

    • 长期收益优先于短期收益

    • 团队拥有能够设计微服务架构的软件架构师或高级工程师

    2微服务架构的设计模式

     独享数据库(Database per Microservice)

    当一家公司将大型单体系统替换成一组微服务,首先要面临的最重要决策是关于数据库。单体架构会使用大型中央数据库。即使转移到微服务架构许多架构师仍倾向于保持数据库不变。虽然有一些短期收益,但它却是反模式的,特别是在大规模系统中,微服务将在数据库层严重耦合,整个迁移到微服务的目标都将面临失败(例如,团队授权、独立开发等问题)。

    更好的方法是为每个微服务提供自己的数据存储,这样服务之间在数据库层就不存在强耦合。这里我使用数据库这一术语来表示逻辑上的数据隔离,也就是说微服务可以共享物理数据库,但应该使用分开的数据结构、集合或者表,这还将有助于确保微服务是按照领域驱动设计的方法正确拆分的。


    Md Kamaruzzaman 的微服务独享数据库

    优点

    • 数据由服务完全所有

    • 服务的开发团队之间耦合度降低

    缺点

    • 服务间的数据共享变得更有挑战性

    • 在应用范围的保证 ACID 事务变得困难许多

    • 细心设计如何拆分单体数据库是一项极具挑战的任务

    何时使用独享数据库

    • 在大型企业应用程序中

    • 当团队需要完全把控微服务以实现开发规模扩展和速度提升

    何时不宜使用独享数据库

    • 在小规模应用中

    • 如果是单个团队开发所有微服务

    可用技术示例

    所有 SQL、 NoSQL 数据库都提供数据的逻辑分离(例如,单独的表、集合、结构、数据库)。

     事件源(Event Sourcing)

    在微服务架构中,特别使用独享数据库时,微服务之间需要进行数据交换。对于弹性高可伸缩的和可容错的系统,它们应该通过交换事件进行异步通信。在这种情况,您可能希望进行类似更新数据库并发送消息这样的原子操作,如果在大数据量的分布式场景使用关系数据库,您将无法使用两阶段锁协议(2PL),因为它无法伸缩。而 NoSQL 数据库因为大多不支持两阶段锁协议甚至无法实现分布式事务。

    在这些场景,可以基于事件的架构使用事件源模式。在传统数据库中,直接存储的是业务实体的当前“状态”,而在事件源中任何“状态”更新事件或其他重要事件都会被存储起来,而不是直接存储实体本身。这意味着业务实体的所有更改将被保存为一系列不可变的事件。因为数据是作为一系列事件存储的,而非直接更新存储,所以各项服务可以通过重放事件存储中的事件来计算出所需的数据状态。


    Md Kamaruzzaman 的事件源

    优点

    • 为高可伸缩系统提供原子性操作

    • 自动记录实体变更历史,包括时序回溯功能

    • 松耦合和事件驱动的微服务

    缺点

    • 从事件存储中读取实体成为新的挑战,通常需要额外的数据存储(CQRS 模式)。

    • 系统整体复杂性增加了,通常需要领域驱动设计。

    • 系统需要处理事件重复(幂等)或丢失

    • 变更事件结构成为新的挑战。

    何时使用事件源

    • 使用关系数据库的、高可伸缩的事务型系统

    • 使用 NoSQL 数据库的事务型系统

    • 弹性高可伸缩微服务架构

    • 典型的消息驱动或事件驱动系统(电子商务、预订和预约系统)

    何时不宜使用事件源

    • 使用 SQL 数据库的低可伸缩性事务型系统

    • 在服务可以同步交换数据(例如,通过 API)的简单微服务架构中。

    可用技术示例

    事件存储:EventStoreDB、Apache Kafka、Confluent Cloud、AWS Kinesis、Azure Event Hub、GCP Pub/Sub、Azure Cosmos DB、MongoDB、Cassandra、Amazon DynamoDB

    框架:Lagom、Akka、Spring、akkatecture、Axon、Eventuate

     命令和查询职责分离(CQRS)

    如果我们使用事件源,那么从事件存储中读取数据就变得困难了。要从数据存储中获取实体,我们需要处理所有的实体事件。有时我们对读写操作还会有不同的一致性和吞吐量要求。

    这种情况,我们可以使用 CQRS 模式。在该模式中,系统的数据修改部分(命令)与数据读取部分(查询)是分离的。而 CQRS 模式有两种容易令人混淆的模式,分别是简单的和高级的。

    在其简单形式中,不同实体或 ORM 模型被用于读写操作,如下所示:

    Md Kamaruzzaman 的 CQRS (简单)

    它有助于强化单一职责原则和分离关注点,从而实现更简洁的设计。

    在其高级形式中,会有不同的数据存储用于读写操作。高级的 CQRS 通常结合事件源模式。根据不同情况,会使用不同类型的写数据存储和读数据存储。写数据存储是“记录的系统”,也就是整个系统的核心源头。


    Md Kamaruzzaman 的 CQRS(高级)

    对于读频繁的应用程序或微服务架构,OLTP 数据库(任何提供 ACID 事务保证的关系或非关系数据库)或分布式消息系统都可以被用作写存储。对于写频繁的应用程序(写操作高可伸缩性和大吞吐量),需要使用写可水平伸缩的数据库(如全球托管的公共云数据库)。标准化的数据则保存在写数据存储中。

    对搜索(例如 Apache Solr、Elasticsearch)或读操作(KV 数据库、文档数据库)进行优化的非关系数据库常被用作读存储。许多情况会在需要 SQL 查询的地方使用读可伸缩的关系数据库。非标准化和特殊优化过的数据则保存在读存储中。

    数据是从写存储异步复制到读存储中的,所以读存储和写存储之间会有延迟,但最终是一致的。

    在公众号互联网架构师回复“2T”,获取惊喜礼包。

    优点

    • 在事件驱动的微服务中数据读取速度更快

    • 数据的高可用性

    • 读写系统可独立扩展

    缺点

    • 读数据存储是弱一致性的(最终一致性)

    • 整个系统的复杂性增加了,混乱的 CQRS 会显着危害整个项目。

    何时使用 CQRS

    • 在高可扩展的微服务架构中使用事件源

    • 在复杂领域模型中,读操作需要同时查询多个数据存储。

    • 在读写操作负载差异明显的系统中

    何时不宜使用 CQRS

    • 在没有必要存储大量事件的微服务架构中,用事件存储快照来计算实体状态是一个更好的选择。

    • 在读写操作负载相近的系统中。

    可用技术示例

    写存储:EventStoreDB, Apache Kafka, Confluent Cloud, AWS Kinesis, Azure Event Hub, GCP Pub/Sub, Azure Cosmos DB, MongoDB, Cassandra. Amazon DynamoDB

    读存储:Elastic Search, Solr, Cloud Spanner, Amazon Aurora, Azure Cosmos DB, Neo4j

    框架:Lagom, Akka, Spring, akkatecture, Axon, Eventuate

     Saga

    如果微服务使用独享数据库,那么通过分布式事务管理一致性是一个巨大的挑战。你无法使用传统的两阶段提交协议,因为它要么不可伸缩(关系数据库),要么不被支持(多数非关系数据库)。

    但您还是可以在微服务架构中使用 Saga 模式实现分布式事务。Saga 是 1987 年开发的一种古老模式,是关系数据库中关于大事务的一个替代概念。但这种模式的一种现代变种对分布式事务也非常有效。Saga 模式是一个本地事务序列,其每个事务在一个单独的微服务内更新数据存储并发布一个事件或消息。Saga 中的首个事务是由外部请求(事件或动作)初始化的,一旦本地事务完成(数据已保存在数据存储且消息或事件已发布),那么发布的消息或事件则会触发 Saga 中的下一个本地事务。


    Md Kamaruzzaman 的 Saga

    如果本地事务失败,Saga 将执行一系列补偿事务来回滚前面本地事务的更改。

    Saga 事务协调管理主要有两种形式:

    1. 事件编排 Choreography:分散协调,每个微服务生产并监听其他微服务的事件或消息然后决定是否执行某个动作。

    2. 命令编排 Orchestration:集中协调,由一个协调器告诉参与的微服务哪个本地事务需要执行。

    优点

    • 为高可伸缩或松耦合的、事件驱动的微服务架构提供一致性事务。

    • 为使用了不支持 2PC 的非关系数据库的微服务架构提供一致性事务。

    缺点

    • 需要处理瞬时故障,并且提供等幂性。

    • 难以调试,而且复杂性随着微服务数量增加而增加。

    何时使用 Saga

    • 在使用了事件源的高可伸缩、松耦合的微服务中。

    • 在使用了分布式非关系数据库的系统中。

    何时不宜使用 Saga

    • 使用关系数据库的低可伸缩性事务型系统。

    • 在服务间存在循环依赖的系统中。

    可用技术示例

    Axon, Eventuate, Narayana

     面向前端的后端 (BFF)

    在现代商业应用开发,特别是微服务架构中,前后端应用是分离和独立的服务,它们通过 API 或 GraphQL 连接。如果应用程序还有移动 App 客户端,那么 Web 端和移动客户端使用相同的后端微服务就会出现问题。因为移动客户端和 Web 客户端有不同的屏幕尺寸、显示屏、性能、能耗和网络带宽,它们的 API 需求不同。

    面向前端的后端模式适用于需要为特殊 UI 定制单独后端的场景。它还提供了其他优势,比如作为下游微服务的封装,从而减少 UI 和下游微服务之间的频繁通信。此外,在高安全要求的场景中,BFF 为部署在 DMZ 网络中的下游微服务提供了更高的安全性。


    Md Kamaruzzaman 的面向前端的后端

    优点

    • 分离 BFF 之间的关注点,使得我们可以为具体的 UI 优化他们。

    • 提供更高的安全性

    • 减少 UI 和下游微服务之间频繁的通信

    缺点

    • BFF 之间代码重复

    • 大量的 BFF 用于其他用户界面(例如,智能电视,Web,移动端,PC 桌面版)

    • 需要仔细的设计和实现,BFF 不应该包含任何业务逻辑,而应只包含特定客户端逻辑和行为。

    何时使用 BFF

    • 如果应用程序有多个含不同 API 需求的 UI

    • 出于安全需要,UI 和下游微服务之间需要额外的层。

    • 如果在 UI 开发中使用微前端。

    何时不宜使用 BFF

    • 如果应用程序虽有多个 UI,但使用的 API 相同。

    • 如果核心微服务不是部署在 DMZ 网络中。

    可用技术示例

    任何后端框架(Node.js,Spring,Django,Laravel,Flask,Play,…)都能支持。

     API 网关

    在微服务架构中,UI 通常连接多个微服务。如果微服务是细粒度的(FaaS) ,那么客户端可能需要连接非常多的微服务,这将变得繁杂和具有挑战性。此外,这些服务包括它们的 API 还将不断进化。大型企业还希望能有其他横切关注点(SSL 终止、身份验证、授权、节流、日志记录等)。

    在公众号互联网架构师后台回复“2T”,获取Java面试题和答案惊喜礼包。

    一个解决这些问题的可行方法是使用 API 网关。API 网关位于客户端 APP 和后端微服务之间充当 facade,它可以是反向代理,将客户端请求路由到适当的后端微服务。它还支持将客户端请求扇出到多个微服务,然后将响应聚合后返回给客户端。它还支持必要的横切关注点。


    Md Kamaruzzaman 的 API 网关

    优点

    • 在前端和后端服务之间提供松耦合

    • 减少客户端和微服务之间的调用次数

    • 通过 SSL 终端、身份验证和授权实现高安全性

    • 集中管理的横切关注点,例如,日志记录和监视、节流、负载平衡。

    缺点

    • 可能导致微服务架构中的单点故障

    • 额外的网络调用带来的延迟增加

    • 如果不进行扩展,它们很容易成为整个企业应用的瓶颈。

    • 额外的维护和开发费用

    何时使用 API 网关

    • 在复杂的微服务架构中,它几乎是必须的。

    • 在大型企业中,API 网关是中心化安全性和横切关注点的必要工具。

    何时不宜使用 API 网关

    • 在安全和集中管理不是最优先要素的私人项目或小公司中。

    • 如果微服务的数量相当少。

    可用技术示例

    Amazon API 网关, Azure API 管理, Apigee, Kong, WSO2 API 管理器

     Strangler

    如果想在运行中的项目中使用微服务架构,我们需要将遗留的或现有的单体应用迁移到微服务。将现有的大型在线单体应用程序迁移到微服务是相当有挑战性的,因为这可能破坏应用程序的可用性。

    一个解决方案是使用 Strangler 模式。Strangler 模式意味着通过使用新的微服务逐步替换特定功能,将单体应用程序增量地迁移到微服务架构。此外,新功能只在微服务中添加,而不再添加到遗留的单体应用中。然后配置一个 Facade (API 网关)来路由遗留单体应用和微服务间的请求。当某个功能从单体应用迁移到微服务,Facade 就会拦截客户端请求并路由到新的微服务。一旦迁移了所有的功能,遗留单体应用程序就会被“扼杀(Strangler)”,即退役。


    Md Kamaruzzaman 的 Strangler

    优点

    • 安全的迁移单体应用程序到微服务

    • 可以并行地迁移已有功能和开发新功能

    • 迁移过程可以更好把控节奏

    缺点

    • 在现有的单体应用服务和新的微服务之间共享数据存储变得具有挑战性

    • 添加 Facade (API 网关)将增加系统延迟

    • 端到端测试变得困难

    何时使用 Strangler

    • 将大型后端单体应用程序的增量迁移到微服务

    何时不宜使用 Strangler

    • 如果后端单体应用很小,那么全量替换会更好。

    • 如果无法拦截客户端对遗留的单体应用程序的请求。

    可用技术示例

    API 网关后端应用框架。

     断路器

    在微服务架构中,微服务通过同步调用其他服务来满足业务需求。服务调用会由于瞬时故障(网络连接缓慢、超时或暂时不可用) 导致失败,这种情况重试可以解决问题。然而,如果出现了严重问题(微服务完全失败),那么微服务将长时间不可用,这时重试没有意义且浪费宝贵的资源(线程被阻塞,CPU 周期被浪费)。此外,一个服务的故障还会引发整个应用系统的级联故障。这时快速失败是一种更好的方法。

    在这种情况,可以使用断路器模式挽救。一个微服务通过代理请求另一个微服务,其工作原理类似于电气断路器,代理通过统计最近发生的故障数量,并使用它来决定是继续请求还是简单的直接返回异常。


    Md Kamaruzzaman 的断路器

    断路器可以有以下三种状态:

    1. 关闭:断路器将请求路由到微服务,并统计给定时段内的故障数量,如果超过阈值,它就会触发并进入打开状态。

    2. 打开:来自微服务的请求会快速失败并返回异常。在超时后,断路器进入半开启状态。

    3. 半开:只有有限数量的微服务请求被允许通过并进行调用。如果这些请求成功,断路器将进入闭合状态。如果任何请求失败,断路器则会进入开启状态。

    优点

    • 提高微服务架构的容错性和弹性

    • 阻止引发其他微服务的级联故障

    缺点

    • 需要复杂的异常处理

    • 日志和监控

    • 应该支持人工复位

    何时使用断路器

    • 在微服务间使用同步通信的紧耦合的微服务架构中

    • 如果微服务依赖多个其他微服务

    何时不宜使用断路器

    • 松耦合、事件驱动的微服务架构

    • 如果微服务不依赖于其他微服务

    可用技术示例

    API 网关,服务网格,各种断路器库(Hystrix, Reselience4J, Polly)。

     外部化配置

    每个业务应用都有许多用于各种基础设施的配置参数(例如,数据库、网络、连接的服务地址、凭据、证书路径)。此外在企业应用程序通常部署在各种运行环境(Local、 Dev、 Prod)中,实现这些的一个方法是通过内部配置。这是一个致命糟糕实践,它会导致严重的安全风险,因为生产凭证很容易遭到破坏。此外,配置参数的任何更改都需要重新构建应用程序,这在在微服务架构中会更加严峻,因为我们可能拥有数百个服务。

    更好的方法是将所有配置外部化,使得构建过程与运行环境分离,生产的配置文件只在运行时或通过环境变量使用,从而最小化了安全风险。

    优点

    • 生产配置不属于代码库,因而最小化了安全漏洞。

    • 修改配置参数不需要重新构建应用程序。

    缺点

    • 我们需要选择一个支持外部化配置的框架。

    何时使用外部化配置

    • 任何重要的生产应用程序都必须使用外部化配置。

    何时不宜使用外部化配置

    • 在验证概念的开发中。

    可用技术示例

    几乎所有企业级的现代框架都支持外部化配置。

     消费端驱动的契约测试

    在微服务架构中,通常有许多有不同团队开发的微服务。这些微型服务协同工作来满足业务需求(例如,客户请求),并相互进行同步或异步通信。消费端微服务的集成测试具有挑战性,通常用 TestDouble 以获得更快、更低成本的测试运行。但是 TestDouble 通常并不能代表真正的微服务提供者,而且如果微服务提供者更改了它的 API 或 消息,那么 TestDouble 将无法确认这些。另一种选择是进行端到端测试,尽管它在生产之前是强制性的,但却是脆弱的、缓慢的、昂贵的且不能替代集成测试(Test Pyramid)。

    在这方面消费端驱动的契约测试可以帮助我们。在这里,负责消费端微服务的团队针对特定的服务端微服务,编写一套包含了其请求和预期响应(同步)或消息(异步)的测试套件,这些测试套件称为显式的约定。对于微服务服务端,将其消费端所有约定的测试套件都添加到其自动化测试中。当特定服务端微服务的自动化测试执行时,它将一起运行自己的测试和约定的测试并进行验证。通过这种方式,契约测试可以自动的帮助维护微服务通信的完整性。

    优点

    • 如果提供程序意外更改 API 或消息,可以被快速的自动发现。

    • 更少意外、更健壮,特别是包含大量微服务的企业应用程序。

    • 改善团队自主性。

    缺点

    • 需要额外的工作来开发和集成微服务服务端的契约测试,因为他们可能使用完全不同的测试工具。

    • 如果契约测试与真实服务情况不匹配,将可能导致生产故障。

    何时使用需求驱动的契约测试

    • 在大型企业业务应用程序中,通常由不同的团队开发不同服务。

    何时不宜使用消费端驱动的契约测试

    • 所有微服务由同一团队负责开发的小型简单的应用程序。

    • 如果服务端微服务是相对稳定的,并且不处在活跃的开发状态。

    可用技术示例

    Pact, Postman, Spring Cloud Contract

    3总结

    在现代大规模企业软件开发中,微服务架构能够帮助开发扩展规模并带来很多长期收益。但是微服务架构并不是随处可用的银弹,如果应用在错误的应用程序类型,微服务架构将弊大于利。希望采用微服务架构的开发团队应该遵循最佳实践,并使用一系列可重用的、久经锤炼的设计模式。

    微服务架构中至关重要的设计模式是独享数据库。实现这种设计模式具有挑战性,需要其他几种密切相关的设计模式(事件驱动、 CQRS、 Saga)来支持。在具有多个客户端(Web、 Mobile、 Desktop、 Smart Devices)的典型业务应用程序中,客户端和微服务之间的通信量可能是很大的,并且需要统一的安全控制,在这种情况面向前端的后端和 API 网关的设计非常有用。此外,断路器模式可以大大地帮助应对这类应用程序的错误处理场景。迁移遗留的单体应用到微服务是极具挑战性的,而 Strangler 模式可以帮助做到这点。消费端驱动的契约测试是微服务集成测试的基础模式。另外外部化配置是任何现代化应用程序开发中的一种必备模式。

    这个系列并不全面,在实际情况中您可能需要其他的设计模式,但这个系列能为您提供一个关于微服务架构设计模式的极好介绍。

    原文链接:

    https://towardsdatascience.com/microservice-architecture-and-its-10-most-important-design-patterns-824952d7fa41

    - EOF -

    PS:如果觉得我的分享不错,欢迎大家随手点赞、在看。

    大家一起在评论区聊聊呗~

    以上是关于微服务架构10个最重要的设计模式的主要内容,如果未能解决你的问题,请参考以下文章

    微服务架构实践之服务容错

    微服务架构及其最重要的10个设计模式

    微服务架构及其最重要的10个设计模式

    微服务架构及其最重要的10个设计模式

    挑战10个最难的Java面试题(附答案)

    「第二部:容器和微服务架构」 基于容器应用架构设计原则