当前位置: 首页 > 后端技术 > Java

10种最重要的微服务设计模式

时间:2023-04-01 22:51:59 Java

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