自软件开发初期(1960年代)以来,解决大型软件系统的复杂性一直是一项艰巨的任务。多年来,软件工程师和架构师为解决软件系统的复杂性做了很多尝试:DavidParnas的模块化和信息隐藏(1972年)、EdsgerW.Dijkstra的关注点分离(1974年)、面向服务的体系结构(1998年)。他们都使用一种久经考验的技术来解决大型系统的复杂性:分而治之。自2010年代以来,这些技术不足以解决网络规模应用程序或现代大型企业应用程序的复杂性。因此,架构师和工程师开发了一种新方法来解决现代软件系统的复杂性:微服务架构。它还使用相同的旧“分而治之”技术,尽管采用的是一种新颖的方式。软件设计模式是针对软件设计中常见问题的通用、可重用的解决方案。设计模式帮助我们共享通用词汇并使用经过实战检验的解决方案,而不是重新发明轮子。在上一篇文章:EffectiveMicroservices:10BestPractices中,我描述了一组开发有效微服务的最佳实践。在这里,我描述了一组设计模式来帮助您实施这些最佳实践。如果您是微服务架构的新手,那么不用担心,我将向您介绍微服务架构。通过阅读本文,您将了解:微服务架构微服务架构的优点微服务架构的缺点何时使用微服务架构最重要的微服务架构设计模式及其优缺点、用例、上下文、技术堆栈示例和有用的资源。请注意,此列表中的大多数设计模式都有多个上下文,可用于非微服务架构。但我将在微服务架构的背景下对其进行描述。微服务架构我在之前的博客文章中详细介绍了微服务架构:微服务架构:简要概述以及为什么要在下一个项目中使用它以及模块化单体软件架构真的死了吗?如果您有兴趣,可以阅读它们以更深入地了解它们。什么是微服务架构。微服务架构有多种定义。这是我的定义:微服务架构旨在将一个大型、复杂的系统垂直(按功能或业务需求)划分为更小的子系统,这些子系统是进程(因此可独立部署),它们之间通过独立于语言的轻量级网络通信相互通信(例如REST、gRPC)或异步(通过消息传递)。下面是一个具有微服务架构的业务Web应用程序的组件视图:>MicroserviceArchitecturebyMdKamaruzzaman微服务架构的重要特征:整个应用程序分为多个独立的进程,每个进程可以包含多个内部模块。与模块化单体或SOA相比,微服务应用程序是垂直拆分的(根据业务能力或领域)微服务边界是外部的。因此,微服务通过网络调用(RPC或消息)相互通信。由于微服务是独立的进程,因此可以独立部署。他们以不需要任何智能通信渠道的轻量级方式进行通信。微服务架构的优势:更好的开发规模。更高的开发速度。支持迭代或增量现代化。充分利用现代软件开发生态系统(云、容器、DevOps、无服务器)。支持水平缩放和粒度缩放。由于其较小的尺寸,它降低了开发人员的认知复杂性。微服务架构的缺点:有很多移动部件(服务、数据库、进程、容器、框架)。复杂性从代码转移到基础设施。RPC调用和网络流量激增。管理整个系统的安全性具有挑战性。设计整个系统更加困难。介绍了分布式系统的复杂性。何时使用微服务架构:Web级应用程序开发。企业应用程序开发发生在多个团队处理应用程序时。长期收益优先于短期收益。团队拥有能够设计微服务架构的软件架构师或高级工程师。微服务架构的设计模式一旦一家公司用许多较小的微服务取代了一个大型单体系统,它面临的最重要的决定就是数据库。在整体架构中,使用了一个大型的中央数据库。许多架构师更喜欢让数据库保持原样,即使他们转向微服务架构也是如此。虽然它提供了一些短期的好处,但它是一种反模式,尤其是在大型系统中,因为微服务将在数据库层紧密耦合。迁移到微服务的整个目标将失败(例如团队授权、独立开发)。更好的方法是为每个微服务提供自己的数据存储,这样数据库层的服务之间就没有强耦合。这里我使用数据库这个词来表示数据的逻辑分离,即微服务可以共享同一个物理数据库,但它们应该使用单独的模式/集合/表。它还将确保根据域驱动设计正确隔离微服务。>MdKamaruzzaman的每个微服务的数据库优点:对服务的数据的完全所有权。开发服务的团队之间的松散耦合。缺点:在服务之间共享数据变得具有挑战性。提供应用程序范围的ACID事务保证变得更加困难。将Monolith数据库分解成更小的部分需要仔细设计,这是一项艰巨的任务。每个微服务什么时候使用数据库:在大型企业中。当团队需要完全拥有他们的微服务以实现开发扩展和开发速度时。何时不为每个微服务使用数据库:在小型应用程序中。如果一个团队开发所有的微服务。启用技术的示例:所有SQL和NoSQL数据库都提供数据的逻辑分离(例如,单独的表、集合、模式、数据库)。事件溯源在微服务架构中,特别是如果每??个微服务都使用数据库,微服务需要交换数据。对于有弹性、高度可扩展和容错的系统,它们应该通过交换事件进行异步通信。在这种情况下,您可能希望执行原子操作,例如更新数据库和发送消息。如果您有一个SQL数据库并希望为大量数据分配分布式事务,则不能使用两阶段锁定(2PL),因为它无法扩展。如果你用的是NoSQL数据库,想有分布式事务,你不能用2PL,因为很多NoSQL数据库不支持两阶段锁。在这种情况下,使用具有事件溯源的基于事件的架构。在传统数据库中,直接存储具有当前“状态”的业务实体。在事件源中,存储的是任何状态更改事件或其他重要事件,而不是实体。这意味着对业务实体的修改被保存为一个不可变的事件序列。可以通过在给定时间重新处理该业务实体的所有事件来推导出该业务实体的状态。由于数据存储为事件序列,而不是通过直接更新数据存储,因此各种服务可以重放事件存储中的事件以计算其各自数据存储的适当状态。>MdKamaruzzaman的事件溯源优势:为高度可扩展的系统提供原子性。实体的自动历史记录,包括时间旅行功能。松散耦合和事件驱动的微服务。缺点:从事件存储中读取实体变得具有挑战性,通常需要额外的数据存储(CQRS模式)系统的整体复杂性增加,通常需要领域驱动设计。系统需要处理重复事件(幂等)或丢失事件。迁移事件模式变得具有挑战性。何时使用事件溯源:具有SQL数据库的高度可扩展的事务系统。带有NoSQL数据库的事务系统。高度可扩展和弹性的微服务架构。典型的消息驱动或事件驱动系统(电子商务、预订和预订系统)。何时不使用事件溯源:具有SQL数据库的低可扩展性事务系统。在简单的微服务架构中,微服务可以同步交换数据(例如,通过API)。启用技术的示例:事件存储:EventStoreDB、ApacheKafka、ConfluentCloud、AWSKinesis、AzureEventHubs、GCPPub/Sub、AzureCosmosDB、MongoDB、Cassandra。AmazonDynamoDB,框架:Lagom、Akka、Spring、akkatecture、Axon、Eventuate命令查询责任分离(CQRS)如果我们使用事件源,那么从事件存储中读取数据将变得具有挑战性。要从数据存储中获取实体,我们需要处理所有实体事件。另外,有时我们对读写操作的一致性和吞吐量要求不同。在此用例中,我们可以使用CQRS模式。在CQRS模式中,系统的数据修改部分(命令)与数据读取(查询)部分分离。CQRS模式有两种类型:简单模式和高级模式,这在软件工程师中引起了一些混淆。在简单形式中,使用不同的实体或ORM模型进行读写,如下所示:>CQRS(simple)byMdKamaruzzaman它有助于实现“单一职责原则”和“关注点分离”,使设计更加简洁。在其高级形式中,单独的数据存储用于读取和写入操作。高级CQRS与事件溯源一起使用。根据用例,使用不同类型的写入数据存储和读取数据存储。写入数据存储是“记录系统”,是整个系统的黄金来源。>MdKamaruzzaman的CQRS(高级)对于读取密集型应用程序或微服务架构,使用OLTP数据库(任何提供ACID事务保证的SQL或NoSQL数据库)或分布式消息传递平台作为写入存储。对于写入量大的程序(高写入可扩展性和吞吐量),使用水平可写入可扩展的数据库(公共云全球数据库)。规范化数据保存在写入数据存储中。针对搜索(例如ApacheSolr、Elasticsearch)或读取(键值数据存储、文档数据存储)优化的NoSQL数据库用作读取存储。在许多情况下,可扩展的SQL数据库用于需要SQL查询的地方。标准化和优化的数据将保存在读取存储中。数据从写入存储异步复制到读取存储。结果,读取存储滞后于写入存储并最终变得一致。优点:在事件驱动的微服务中更快地读取数据。数据的高可用性。读写系统可以独立扩展。缺点:读取数据存储弱一致性(最终一致性)增加了系统的整体复杂度。CargotrainingCQRS会严重危害整个项目。何时使用CQRS:在使用事件源的高度可扩展的微服务架构中。在读取数据需要查询多个数据存储的复杂领域模型中。在读写操作具有不同负载的系统中。何时不使用CQRS:在微事件数量微不足道的微服务架构中,使用事件存储快照来计算实体状态是更好的选择。在读取和写入操作具有相似负载的系统中。启用技术的示例:写入存储:EventStoreDB、ApacheKafka、ConfluentCloud、AWSKinesis、AzureEventHub、GCPPub/Sub、AzureCosmosDB、MongoDB、Cassandra。AmazonDynamoDB阅读商店:ElasticSearch、Solr、CloudSpanner、AmazonAurora、AzureCosmosDB、Neo4j框架:Lagom、Akka、Spring、akkatecture、Axon、EventuateSAGA如果您使用微服务架构和每个微服务的数据库,则通过分布式事务管理一致性具有挑战性。您不能使用传统的两阶段提交协议,因为它无法扩展(SQL数据库)或不受支持(许多NoSQL数据库)。您可以将Saga模式用于微服务架构中的分布式事务。Saga是一种旧模式,于1987年开发,作为SQL数据库中长期运行的数据库事务的概念替代方案。然而,这种模式的现代变体也适用于分布式事务。Saga模式是一系列本地事务,其中每个事务都会更新数据存储中的数据并在单个微服务中发布事件或消息。saga中的第一个事务由外部请求(事件或操作)启动。一旦本地事务完成(数据存储在数据存储中,并发布消息或事件),发布的消息/事件将触发Saga中的下一个本地事务。>MdKamaruzzaman的Saga如果本地事务失败,Saga会执行一系列补偿事务以撤消先前本地事务的更改。Saga事务协调有两种主要变体:分散式协调,其中每个微服务生成并侦听其他微服务的事件/消息,并决定是否应该采取行动。整体协调,协调者告诉被协调的微服务需要执行哪些本地事务。优点:通过高度可扩展或松耦合、事件驱动的微服务架构中的事务提供一致性。一致性是通过微服务架构中的事务提供的,该架构使用不支持2PC的NoSQL数据库。缺点:需要处理瞬态故障并应提供幂等性。难以调试,并且随着微服务数量的增加,复杂性也随之增加。何时使用Saga:在使用事件源的高度可扩展、松散耦合的微服务架构中。在使用分布式NoSQL数据库的系统中。何时不使用Saga:具有SQL数据库的低可扩展性事务系统。在服务之间存在循环依赖关系的系统中。使能技术示例:Axon、Eventuate、Narayana前端后端(BFF)在现代业务应用程序开发中,尤其是在微服务架构中,前端和后端应用程序是分离的独立服务。它们通过API或GraphQL连接。如果应用程序也有移动应用程序客户端,那么为Web和移动客户端使用相同的后端微服务将是一个问题。移动客户端通常具有与Web客户端不同的API要求,因为它们具有不同的屏幕尺寸、显示、性能、能量和网络带宽。后端的后端模式可用于每个UI都有为该特定UI量身定制的单独后端的场景。它还提供了其他优势,例如充当下游微服务的门面,减少UI和下游微服务之间的喋喋不休的通信。另外在高安全性的情况下,下游微服务部署在DMZ网络中,使用BFF来提供更高的安全性。>MdKamaruzzamanPros的BackendsforFrontends:SeparationofconcernsbetweenBFFs。我们可以针对特定的UI优化它们。提供更高的安全性。减少UI和下游微服务之间的通信。缺点:BFF之间的代码重复。如果使用许多其他UI(例如智能电视、网络、移动设备、桌面),BFF的数量也会激增。BFF不应包含任何业务逻辑,而应仅包含特定于客户端的逻辑和行为,因此需要仔细设计和实现。何时将后端用于前端:如果应用程序具有多个具有不同API要求的UI。如果您出于安全原因需要在UI和下游微服务之间增加一个层。如果你在UI开发中使用微前端。何时不将后端用作前端:如果应用程序有多个UI,但它们使用相同的API。如果核心微服务没有部署在DMZ中。使能技术示例:任何后端框架(Node.js、Spring、Django、Laravel、Flask、Play等)都支持它。APIGateway在微服务架构中,UI通常连接到多个微服务。如果微服务是细粒度的(FaaS),客户端可能需要连接到许多微服务,这变得很麻烦且具有挑战性。此外,服务(包括其API)可以发展。大型企业还希望有其他横切关注点(SSL终止、身份验证、授权、节流、日志记录等)。解决这些问题的一种可能方法是使用API网关。API网关位于客户端应用程序和后端微服务之间,充当门面。它可以用作反向代理,将客户端请求路由到适当的后端微服务。它还可以支持将客户端请求扇出到多个微服务,然后将聚合响应返回给客户端。它还支持基本的横切关注点。>MdKamaruzzaman的API网关优点:提供前端和后端微服务之间的松散耦合。减少客户端和微服务之间的往返次数。具有SSL终止、身份验证和授权的高安全性。集中管理横切关注点,例如日志记录和监控、节流、负载平衡。缺点:可能导致微服务架构中的单点故障。由于额外的网络调用,延迟会增加。如果不进行扩展,它们很容易成为整个企业的瓶颈。额外的维护和开发成本。何时使用API网关:在复杂的微服务架构中,它几乎是强制性的。在大公司中,必须使用API网关来集中安全和横切关注点。何时不使用API网关:在安全和中央管理不是最优先考虑的私人项目或小型公司中。如果微服务数量少。支持技术示例:AmazonAPIGateway、AzureAPIManagement、Apigee、Kong、WSO2APIManagerStrangler如果您想在棕地项目中使用微服务架构,则需要将遗留或现有的单体应用程序迁移到微服务。将现有的大规模生产单体应用程序迁移到微服务中非常具有挑战性,因为它会破坏应用程序的可用性。一种解决方案是使用Strangler模式。Strangler模式意味着通过用新的微服务逐渐替换特定功能,逐步将单体应用程序迁移到微服务架构。此外,新功能仅在微服务中添加,绕过了传统的单体应用程序。然后配置Facade(API网关)以在遗留Monolith和微服务之间路由请求。一旦功能从Monolith迁移到微服务,Facade就会拦截客户端请求并路由到新的微服务。一旦迁移了所有遗留单体功能,遗留单体应用程序将被“扼杀”,即退役。>MdKamaruzzamanPros的Strangler:将单体应用程序安全迁移到微服务。迁移和新功能开发可以并行进行。迁移过程可以有自己的节奏。缺点:在现有的Monolith和新的微服务之间共享数据存储变得具有挑战性。添加外观(API网关)会增加系统延迟。端到端测试变得困难。何时使用Strangler:以增量方式将大型后端单体应用程序迁移到微服务。何时不使用Strangler:如果整个后端组件较小,则批量替换是更好的选择。如果无法拦截对遗留单体应用程序的客户端请求。驱动技术:具有API网关的后端应用程序框架。CircuitBreaker在微服务架构中,微服务是同步通信的,微服务经常会调用其他服务来满足业务需求。对其他服务的调用可能会由于暂时性故障(网络连接速度慢、超时或不可用时间)而失败。在这种情况下,重试呼叫可以解决问题。但是,如果出现严重问题(微服务完全失效),微服务将长时间不可用。在这种情况下,重试是毫无意义的,并且是对宝贵资源的浪费(线程阻塞,浪费CPU周期)。同样,一项服务的故障可能会导致整个应用程序发生级联故障。在这种情况下,立即失败是更好的方法。对于此类用例,可以使用断路器模式。一个微服务应该通过代理请求另一个微服务,代理就像断路器一样工作。代理应该计算最近失败的次数,并使用它来决定是允许操作继续还是简单地返回一个异常。>CircuitBreakerbyMdKamaruzzaman断路器可以有以下三种状态:Closed:断路器向微服务发送请求并计算给定时间段内的故障次数。如果一定时间内的故障次数超过阈值,则跳闸并进入“分闸状态”。On:来自微服务的请求立即失败并出现异常。超时后,断路器进入半开状态。半开放:只允许有限数量的微服务请求通过并调用操作。如果这些请求成功,断路器将进入关闭状态。如果任何请求失败,断路器将进入“打开”状态。优点:提高微服务架构的容错性和弹性。停止将故障级联到其他微服务。缺点:需要复杂的异常处理。记录和监控。应该支持手动复位。何时使用断路器:在紧密耦合的微服务架构中,微服务同步通信。一个微服务是否依赖于多个其他微服务。何时不使用断路器:松耦合、事件驱动的微服务架构。一个微服务是否不依赖于其他微服务。驱动技术:API网关、服务网格、各种断路器库(Hystrix、Reselience4J、Polly。外部化配置)每个业务应用程序都有许多用于各种基础设施(例如,数据库、网络、连接服务地址、凭证、证书路径)的配置参数。同样,在企业环境中,应用程序通常部署在各种运行时(本地、开发、生产)中。实现这一点的一种方法是通过内部配置,这是一种致命的不良做法。由于生产凭证很容易被泄露,这可能会导致严重的安全风险。此外,配置参数的任何更改都需要重建应用程序。这在微服务架构中尤为重要,因为我们可能有数百个服务的数据。更好的方法是将所有配置外部化。因此,构建过程与运行时环境分离。此外,由于生产配置文件仅在运行时或通过环境变量使用,因此安全风险也降至最低。优点:生产配置不是代码库的一部分,因此安全漏洞最小化。无需重建即可更改配置参数。缺点:我们需要选择一个支持外部化配置的框架。何时使用外部化配置:任何严肃的生产应用程序都必须使用外部化配置。何时不使用外部化配置:概念开发证明。推送技术:几乎所有的企业级现代框架都支持外部化配置。消费者驱动的契约测试在微服务架构中,很多微服务通常是由独立的团队开发的。这些微服务协同工作以满足业务需求(例如,客户请求)并以同步或异步方式相互通信。消费者微服务的集成测试具有挑战性。通常,在这种情况下使用TestDouble可以进行更快、更便宜的测试。但是TestDouble通常并不代表真正的提供者微服务。此外,如果提供者微服务更改其API或消息,TestDouble无法确认这一点。另一种选择是进行端到端测试。虽然端到端测试在生产前是强制性的,但它脆弱、缓慢、昂贵,并且不能替代集成测试(测试金字塔)。消费者驱动的契约测试可以在这方面帮助我们。在这里,消费者微服务所有者团队为特定提供者微服务编写了一个测试套件,其中包含请求和预期响应(用于同步通信)或预期消息(用于异步通信)。这些测试套件称为显式契约。对于提供者微服务,其消费者的所有合同测试套件都被添加到自动化测试中。当针对特定提供者微服务执行自动化测试时,它会运行自己的测试、合同并验证合同。这样,契约测试可以帮助以自动化方式维护微服务通信的完整性。优点:如果提供者不小心更改了API或消息,会在很短的时间内自动发现。更少的意外和更多的健壮性,尤其是对于具有大量微服务的企业应用程序。提高团队自主权。缺点:需要额外的工作来开发和集成承包商微服务中的合同测试,因为合同测试可能使用完全不同的测试工具。如果合同测试与实际服务消费不匹配,可能会导致生产失败。何时使用消费者驱动的契约测试:在大型企业业务应用程序中,通常,不同的团队开发不同的服务。何时不使用消费者主导的契约测试:一个相对简单的小型应用程序,所有微服务均由一个团队开发。提供者微服务是否相对稳定,没有在积极开发中。驱动技术:Contracts、Postman、SpringCloudContracts结论在现代大型企业软件开发中,微服务架构可以帮助扩展并带来许多长期利益。但是微服务架构并不是可以在每个用例中使用的“银弹”。如果您在错误类型的应用程序中使用微服务架构,可能会造成更多麻烦。想要采用微服务架构的开发团队应该遵循一组最佳实践,并使用一组可重用的、经过验证的设计模式。微服务架构中最重要的设计模式是每个微服务一个数据库。实施此设计模式具有挑战性,需要其他几个密切相关的设计模式(事件溯源、CQRS和Saga)。在具有多个客户端(Web、移动、桌面、智能设备)的典型业务应用程序中,客户端和微服务之间的通信可能是混乱的,并且可能需要具有额外安全性的中央控制。在这种情况下,前端的设计模式和API网关就非常有用了。此外,断路器模式可以极大地帮助处理此类应用程序中的错误情况。将遗留的单体应用程序迁移到微服务中可能非常具有挑战性,而Strangler模式可以帮助完成这种迁移。消费者驱动的契约测试是微服务集成测试的一种工具模式。同时,外部化配置是任何现代应用程序开发中的强制性模式。此列表并不全面,根据您的用例,您可能需要其他设计模式。但这份清单将为您提供微服务架构设计模式的精彩介绍。
