自从软件开发的早期(1960年代)以来,解决大型软件系统中的复杂性一直是一项艰巨的任务。多年来,软件工程师和架构师为解决软件系统的复杂性进行了许多尝试:DavidParnas的模块化和信息隐藏(1972),Edsger W. Dijkstra的关注分离(1974),面向服务的体系结构(1998)。
他们所有人都使用了久经考验的成熟技术来解决大型系统的复杂性:分而治之。自2010年代以来,这些技术不足以解决Web规模应用程序或现代大型企业应用程序的复杂性。结果,架构师和工程师开发了一种新方法来解决现代软件系统的复杂性:微服务架构。它也使用了相同的旧"分而治之"技术,尽管采用了新颖的方式。
软件设计模式是解决软件设计中常见问题的通用,可重用的解决方案。设计模式可帮助我们共享通用词汇,并使用经过实战检验的解决方案,而不是重新发明轮子。在文章:有效的微服务:10个最佳实践中,我描述了开发有效的微服务的一组最佳实践。在这里,我将描述一组设计模式,以帮助您实现这些最佳实践。如果您是微服务架构的新手,那么不用担心,我将向您介绍微服务架构。
通过阅读本文,您将学到:
请注意,此清单的大多数设计模式都有几种上下文,可以在非微服务体系结构中使用。但是我将在微服务架构的背景下对其进行描述。
微服务架构
在之前的博客文章中,我已经详细介绍了微服务体系结构:微服务体系结构:简要概述以及为什么要在下一个项目中使用它以及模块化单片软件体系结构真的死了吗?如果您有兴趣,可以阅读它们以更深入地了解它们。
什么是微服务架构。微服务架构有很多定义。这是我的定义:
微服务架构旨在将大型,复杂的系统垂直(按功能或业务要求)划分为较小的子系统,这些子系统属于流程(因此可独立部署),并且这些子系统之间通过与语言无关的轻量级网络通信相互通信(例如REST,gRPC)或异步(通过消息传递)方式。
这是具有微服务架构的业务Web应用程序的组件视图:
> Microservice Architecture by Md Kamaruzzaman
微服务架构的重要特征:
微服务架构的优势:
微服务架构的缺点:
何时使用微服务架构:
微服务架构的设计模式
每个微服务独占数据库
一旦公司用许多较小的微服务替换了大型的单片系统,它面临的最重要的决定就是关于数据库。在整体架构中,使用大型中央数据库。许多架构师都喜欢保留数据库原样,即使他们转向微服务架构也是如此。尽管它提供了一些短期好处,但它是一种反模式,尤其是在大规模系统中,因为微服务将紧密耦合在数据库层中。转向微服务的整个目标将失败(例如,团队授权,独立开发)。
更好的方法是为每个微服务都提供自己的数据存储,以使数据库层中的服务之间不存在强耦合。在这里,我使用数据库一词来表示数据的逻辑分离,即微服务可以共享同一物理数据库,但是它们应该使用单独的架构/集合/表。它还将确保根据域驱动设计正确隔离微服务。
> Event Sourcing by Md Kamaruzzaman
优点:
缺点:
启用技术示例:
命令查询职责隔离(CQRS)
如果我们使用事件源,那么从事件存储中读取数据将变得充满挑战。要从数据存储中获取实体,我们需要处理所有实体事件。另外,有时我们对读写操作有不同的一致性和吞吐量要求。
在这种用例中,我们可以使用CQRS模式。在CQRS模式中,系统的数据修改部分(命令)与数据读取(查询)部分分开。CQRS模式有两种形式:简单和高级,这导致软件工程师之间产生一些混淆。
以简单的形式,不同的实体或ORM模型用于读取和写入,如下所示:
> CQRS (simple) by Md Kamaruzzaman
它有助于实施"单一责任原则"和"关注点分离",从而使设计更简洁。
> CQRS (advanced) by Md Kamaruzzaman
对于重读应用程序或微服务体系结构,将OLTP数据库(任何提供ACID事务保证的SQL或NoSQL数据库)或分布式消息平台用作写存储。对于繁重的写程序(高写可伸缩性和吞吐量),使用了水平可写伸缩的数据库(公共云全局数据库)。规范化的数据保存在写入数据存储中。
为搜索(例如ApacheSolr,Elasticsearch)或读取(键值数据存储,文档数据存储)而优化的NoSQL数据库用作读取存储。在许多情况下,在需要SQL查询的地方使用可伸缩的SQL数据库。归一化和优化的数据将保存在读取存储中。
数据从写入存储异步复制到读取存储。结果,读存储区滞后于写存储区,并且最终保持一致。
优点:
缺点:
何时使用CQRS:
何时不使用CQRS:
启用技术示例:
如果您将微服务体系结构与每个微服务的数据库一起使用,那么通过分布式事务管理一致性就具有挑战性。您不能使用传统的两阶段提交协议,因为它无法扩展(SQL数据库)或不被支持(许多NoSQL数据库)。
您可以将Saga模式用于MicroserviceArchitecture中的分布式事务。Saga是一种旧模式,于1987年开发,作为SQL数据库中长期运行的数据库事务的概念替代方案。但是,这种模式的现代变体对于分布式事务也非常有效。Saga模式是一个本地事务序列,其中每个事务在单个微服务中更新数据存储中的数据并发布事件或消息。传奇中的第一个事务由外部请求(事件或操作)启动。一旦本地事务完成(数据存储在数据存储中,并且发布消息或事件),发布的消息/事件将触发Saga中的下一个本地事务。
> Saga by Md Kamaruzzaman
如果本地事务失败,则Saga执行一系列补偿事务,以撤消先前本地事务的更改。
Saga交易协调主要有两种变体:
优点:
缺点:
何时使用佐贺:
什么时候不使用佐贺:
启用技术示例:
前端的后端(BFF)
在现代业务应用程序开发中,尤其是在微服务体系结构中,前端和后端应用程序是分离的和独立的服务。它们通过API或GraphQL连接。如果应用程序还具有MobileApp客户端,则对Web和Mobile客户端使用相同的后端微服务将成为问题。移动客户端的API要求通常与Web客户端不同,因为它们具有不同的屏幕大小,显示,性能,能源和网络带宽。
后端的后端模式可用于每个UI都有为特定UI定制的单独后端的场景。它还提供了其他优势,例如充当下游微服务的外观,从而减少了UI与下游微服务之间的闲聊通信。同样,在高度安全的情况下,下游微服务部署在DMZ网络中,BFF用于提供更高的安全性。
> Backends for Frontends by Md Kamaruzzaman
优点:
缺点:
何时将后端用于前端:
何时不使用后端作为前端:
启用技术示例:
API网关
在微服务架构中,UI通常与多个微服务连接。如果微服务是细粒度的(FaaS),则客户端可能需要连接许多微服务,这变得很繁琐且具有挑战性。而且,服务(包括其API)可以发展。大型企业还希望拥有其他跨领域的问题(SSL终止,身份验证,授权,限制,日志记录等)。
解决这些问题的一种可能方法是使用API网关。API网关位于客户端APP和后端微服务之间,并充当外观。它可以用作反向代理,将客户端请求路由到适当的后端微服务。它还可以支持将客户端请求的扇出扩展到多个微服务,然后将汇总的响应返回给客户端。它还支持基本的跨领域关注。
> API Gateway by Md Kamaruzzaman
优点:
缺点:
何时使用API网关:
何时不使用API网关:
启用技术示例:
扼杀者
如果要在棕地项目中使用微服务架构,则需要将旧版或现有的Monolithic应用程序迁移到微服务。将现有的大型生产单片式应用程序迁移到微服务中具有很大的挑战性,因为这可能会破坏应用程序的可用性。
一种解决方案是使用Strangler模式。Strangler模式意味着通过逐步用新的微服务替换特定功能,将Monolithic应用程序逐步迁移到微服务架构。此外,新功能仅在微服务中添加,绕过了传统的Monolithic应用程序。然后将Facade(API网关)配置为在旧版Monolith和微服务之间路由请求。一旦功能从Monolith迁移到微服务,Facade就会拦截客户端请求并路由到新的微服务。一旦所有旧版Monolithic功能都已迁移,旧版Monolithic应用程序将被"勒死",即退役。
> Strangler by Md Kamaruzzaman
优点:
缺点:
何时使用Strangler:
何时不使用Strangler:
推动技术:
断路器
在微服务体系结构中,微服务进行同步通信,微服务通常调用其他服务来满足业务需求。由于瞬态故障(网络连接速度慢,超时或时间不可用),对另一个服务的调用可能会失败。在这种情况下,重试呼叫可以解决此问题。但是,如果存在严重问题(微服务完全失败),则微服务将长时间不可用。在这种情况下,重试是没有意义的,并且浪费了宝贵的资源(线程被阻塞,浪费了CPU周期)。同样,一项服务的故障可能会导致整个应用程序级联故障。在这种情况下,立即失败是一种更好的方法。
对于此类用例,可以使用断路器模式。微服务应通过代理来请求另一个微服务,该代理的工作方式类似于断路器。代理应该计算最近发生的故障数,并使用它来决定是允许操作继续进行还是直接返回异常。
> Circuit Breaker by Md Kamaruzzaman
优点:
缺点:
何时使用断路器:
何时不使用断路器:
推动技术:
外部化配置
每个业务应用程序都有许多用于各种基础结构的配置参数(例如,数据库,网络,连接的服务地址,凭据,证书路径)。同样,在企业环境中,应用程序通常部署在各种运行时中(本地,开发,生产)。实现此目标的一种方法是通过内部配置,这是一种致命的不良做法。由于很容易破坏生产凭据,因此可能导致严重的安全风险。另外,配置参数的任何更改都需要重建应用程序。在微服务架构中,这一点尤为重要,因为我们可能拥有数百种服务。
更好的方法是外部化所有配置。结果,将构建过程与运行时环境分开。此外,由于生产配置文件仅在运行时或通过环境变量使用,因此将安全风险降到最低。
优点:
缺点:
何时使用外部化配置:
何时不使用外部化配置:
推动技术:几乎所有企业级的现代框架都支持外部化配置。
消费者驱动的合同测试
在微服务架构中,通常由独立的团队开发许多微服务。这些微服务一起工作来满足业务需求(例如,客户请求),并且彼此同步或异步地通信。消费者微服务的集成测试具有挑战性。通常,在这种情况下使用TestDouble可以进行更快,更便宜的测试。但是TestDouble通常并不代表真正的提供程序微服务。另外,如果提供者微服务更改了其API或消息,则TestDouble无法确认这一点。另一个选择是进行端到端测试。虽然在生产之前必须进行端到端测试,但它脆弱,缓慢,昂贵,并且不能替代集成测试(测试金字塔)。
消费者驱动的合同测试可以在这方面为我们提供帮助。此处,消费者微服务所有者团队编写了一个测试套件,其中包含针对特定提供者微服务的请求和预期响应(用于同步通信)或预期消息(用于异步通信)。这些测试套件称为显式合同。对于提供商微服务,其使用者的所有合同测试套件都添加到了自动测试中。在执行针对特定提供程序微服务的自动测试时,它将运行自己的测试,合同并验证合同。通过这种方式,合同测试可以帮助以自动化的方式维护微服务通信的完整性。
优点:
缺点:
何时使用消费者驱动的合同测试:
何时不使用消费者主导的合同测试:
推动技术:
结论
在现代的大型企业软件开发中,微服务体系结构可以帮助扩展规模并带来许多长期利益。但是微服务架构并不是可以在每个用例中使用的"银弹"。如果在错误的应用程序类型中使用它,则微服务架构会带来更多的麻烦。想要采用微服务体系结构的开发团队应遵循一组最佳实践,并使用一组可重复使用的,经过严格实践的设计模式。
微服务架构中最重要的设计模式是每个微服务的数据库。实施此设计模式具有挑战性,并且需要其他几个紧密相关的设计模式(事件源,CQRS和Saga)。在具有多个客户端(Web,移动,台式机,智能设备)的典型业务应用程序中,客户端与微服务之间的通信可能会比较混乱,可能需要具有附加安全性的中央控制。在这种情况下,前端的设计模式和API网关非常有用。同样,断路器模式可以极大地帮助处理此类应用程序中的错误情况。将旧的Monolithic应用程序迁移到微服务中具有很大的挑战性,而Strangler模式可以帮助迁移。消费者驱动的合同测试是微服务集成测试的工具模式。同时,外部化配置是任何现代应用程序开发中的强制性模式。
该列表并不全面,并且取决于您的用例,您可能需要其他设计模式。但是此列表将为您提供有关微服务体系结构设计模式的出色介绍。