在这篇文章中,注重讲解如何使用设计模式、原则和最佳实践来设计微服务架构。
原文地址:Monolithic to Microservices Architecture with Patterns & Best Practices
前言背景
如何通过设计高可用性、高可扩展性、低延迟以及能够应对微服务分布式架构网络故障的系统来处理数百万的请求。

图示:事件驱动架构
从软件架构的基础开始,通过设计一个能够处理低数量请求的单体式电子商务架构。架构将逐步演变:
- Monolithic 单体架构
- Layered Architecture 分层架构
- Service-Oriented Architecture (面向服务的架构 SOA)
- Microservices (微服务)
- Event-Driven Microservices Architectures(事件驱动微服务架构)

图示:设计架构之旅
单体架构
几十年来,许多方法和模式在软件开发中不断发展,它们都有优势和挑战,要理解云原生微服务,我们需要了解什么是单体应用程序以及它们如何引导我们从单体架构转向微服务架构

图示:单体架构
可以说大多数遗留应用程序都是采用单体架构实现的。如果所有项目功能都存在于一个代码库中,那么这个应用程序就被称为单体应用程序。
单体模式将用户界面、业务代码和数据库调用等所有内容都包含在同一个代码库中。所有应用程序工件都包含在一个单一的巨大部署中。即使是单体应用程序也可以设计成不同的层,如表现层、业务层和数据层,然后以单个 jar/war 文件的形式部署该代码库。
单体应用优势:
- 由于它是一个单一的代码库,因此容易拉取并启动项目
- 由于这个项目结构包含在一个项目中,并且容易调试不同模块之间的交互。
- 由于部件较少,维护和排错的复杂性较低
单体应用不足:
- 随着时间推移,代码规模变得过大;管理具有挑战性
- 在同一代码库中并行工作很困难
- 在传统的庞大单体应用程序上实现新的大功能很困难
- 任何更改都需要部署整个应用程序的新版本
何时使用单体架构
体架构有很多缺点,但如果你正在构建一个小型应用程序,单体架构是你项目中可以应用的最佳架构之一。因为,在很多方面,单体应用程序都很直接。单体架构为以下方面提供了简单性:构建、测试、部署、排错、垂直扩展等。
与微服务相比,单体架构的开发要简单得多,部署起来也更简单,因为只需要一个 jar/war 文件就可以部署整个应用程序
设计单体架构
逐步设计我们的电子商务应用程序的单体架构,根据需求逐一迭代架构设计。
功能需求:
- List products(列出产品)
- Filter products as per brand and categories(按品牌和类别筛选产品)
- Put products into the shopping cart(将产品加入购物车)
- Apply coupons for discounts and see the total cost for all the items in the shopping cart(使用优惠券享受折扣,查看购物车中所有商品的总费用)
- Checkout the shopping cart and create an order(结算购物车并创建订单)
- List my old orders and order items history(查看我的历史订单和订单商品记录)
非功能性需求
- Scalability(可扩展性)
- Acceptable performance due to increase of concurrent users(由于并发用户数的增加,性能可接受)
使用单体架构设计了我们的电子商务应用程序,如下:

图示:设计单体架构
并且添加了大型电子商务框;这些是电子商务应用的组件:
- Shopping UI 购物界面
- Catalog Service 目录服务
- Shopping Cart Service 购物车服务
- Discount Service 折扣服务
- Order Service 订单服务
这个传统 Web 应用的所有模块在容器中都是单一的艺术品,这个单体应用有一个庞大的代码库,包含了所有模块。
如果你在这个应用程序中引入新的模块,你必须修改现有代码,然后部署一个带有不同代码的工件到应用服务器,例如 Tomcat 服务器。
单体架构的可扩展性
通过添加两个更多的应用服务器,并在客户端和电子商务应用程序之间的单体应用程序前面放置一个负载均衡器,实现了单体架构的水平扩展。
为了在单体架构中实现可扩展性,我们需要增加电子商务应用服务器的实例数量,并在应用前面配置一个新的负载均衡器。
负载均衡器将使用一致性分布算法来适应并发请求,并将这些请求发送到我们的电子商务应用服务器。这种能力将为服务器提供均衡的负载。

NGINX 是负载均衡的绝佳选择。
微服务架构
微服务是能够协同工作并且可以独立部署的小型业务服务。
微服务架构风格是一种将单个应用程序作为一组小型服务来开发的方法,每个服务都在自己的进程中运行,并通过轻量级机制通信,通常是 HTTP 或 gRPC API。
因此,我们可以将微服务架构描述为一种云原生架构方法,其中应用程序由许多松散耦合且可独立部署的小型组件组成。让我们描述一些微服务的特征
- 它们拥有自己的技术栈,包括数据库和数据管理模型
- 通过 REST API、事件流和消息代理进行相互通信。
- 它们按业务能力进行组织,服务之间的界限通常被称为限界上下文
微服务特征
微服务是小型、独立且松散耦合的。一个单独的小型开发团队可以编写和维护一个服务。每个服务是一个独立的代码库,一个小型开发团队可以管理。
这些服务可以独立部署。一个团队可以在不重建和重新部署整个应用程序的情况下更新现有服务。
每个服务负责持久化其数据或外部状态。这一能力与传统模型不同,在传统模型中,有一个单独的数据层负责数据持久化。
微服务架构的优势
- 敏捷性:微服务的一个基本特征是服务更小且可独立部署。
- 小型、专注的团队:一个微服务应该足够小,以便一个功能团队可以构建、测试和部署
- 可扩展性: 微服务可以独立扩展,因此你可以仅扩展需要较少资源的子服务,而无需扩展整个应用程序
微服务架构的挑战
- 复杂性:微服务应用程序包含许多需要协同工作并创造价值的服务。由于服务数量众多,其变动部分比单体应用程序更多
- 网络问题和延迟:由于微服务规模小且通过服务间通信进行交互,我们需要管理网络问题
- 数据完整性:每个微服务都有自己独立的数据持久化。因此,数据一致性可能是一个挑战
设计微服务架构
根据需求逐一迭代架构设计

我们在设计微服务架构时遵循了"每个服务一个数据库"的模式,并为每个微服务配置了数据库。微服务是从单体应用模块中分解出来的独立服务。
现在这些数据库可以是多语言的持久化存储。这意味着产品微服务可以使用 NoSQL 文档数据库,购物车微服务可以使用 NoSQL 键值对数据库,而订单微服务可以根据每个微服务的存储需求使用关系型数据库。
第一次架构演进
让我们看看微服务架构图,思考这个架构中缺失了什么?这个架构的痛点在哪里?我们如何将这个架构演进成更优的架构,使其更具可扩展性、可用性,并能够处理更多并发请求

图示:前端和后端紧密耦合
可以看到前端 UI 元素和微服务之间的通信是直接的,管理所有这些连接点似乎很复杂。现在我们应该专注于微服务之间的通信
微服务通信
在转向基于微服务的应用程序时,改变通信机制是最大的挑战之一。
本质上,微服务是分布式的;微服务通过网络层面的服务间通信进行相互通信。每个微服务都有自己的实例和进程。因此,服务必须使用 HTTP、gRPC 或使用 AMQP 协议的消息代理等跨服务通信协议进行交互。
由于微服务是复杂结构,由独立开发和部署的服务组成,我们应该考虑通信类型,并在设计阶段进行管。
微服务通信设计模式--API 网关模式
如果你想要设计和构建基于微服务的复杂大型应用程序,并且有多个客户端应用程序,建议使用 API 网关模式。
该模式提供了一个反向代理,用于将请求重定向或路由到您的内部微服务端点。API 网关为客户端应用程序提供一个单一端点,并在内部将请求映射到内部微服务。我们应该在客户端和内部微服务之间使用 API 网关。
API 网关可以处理通用的技术问题,如授权,因此无需在每个微服务中编写相同的功能,授权可以通过 API 网关以集中方式处理,并转发到内部微服务。
此外,API 网关管理路由到内部微服务,并且可以将多个微服务请求聚合在一个对客户端的响应中。
总之,API 网关将放置在客户端应用程序和内部微服务之间,作为反向代理并路由客户端请求到后端服务。它还提供通用技术问题,如身份验证、SSL 终止和缓存。
设计 API 网关--微服务通信设计模式
我们将通过添加 API 网关模式来迭代电子商务架构。

图示:使用 API 网关
可以看到这张图展示了客户端请求在单个入口点汇集,并将请求路由到内部微服务。
这项功能将处理客户端请求,并将内部微服务进行路由,将多个内部微服务聚合为一个客户端请求,并执行诸如认证和授权、速率限制和流量控制等横切关注点。
第二次架构演进
我们将继续演进我们的架构,但请看看当前的设计,并考虑我们如何能改进设计,
这里连接到单个 API 网关有多个客户端应用程序。我们应该小心这种情况,因为如果我们在这里放置单个 API 网关,可能会包括与单点故障相关的风险。
如果这些客户端应用程序在 API 网关中增加或添加更多逻辑以处理业务复杂性,这将是一种反模式。因此,我们应该使用面向前端的后端(BFF)模式来解决这个问题。
面向前端的后端(BFF)模式--微服务通信设计模式
面向前端的后端(Backends for Frontends pattern BFF)模式可以根据特定的前端应用程序分离 API 网关。因此,我们拥有多个被前端应用程序消费的后端服务,而在它们之间,我们包含 API 网关来处理、路由和聚合操作。
但这会导致单点故障。为了解决这个问题,BFF 提供了创建多个 API 网关的可能性,根据客户端应用程序的边界进行分组,并将它们分配到不同的 API 网关中。

图示:BFF 模式
单一的复杂 API 网关可能存在风险,并可能成为我们架构中的瓶颈。大型系统通常会通过按功能类型分组客户端来暴露多个 API 网关,例如移动端、Web 端和桌面端。当避免为多个接口定制单一后端时,BFF 模式非常有用。
因此,我们应该根据用户界面创建多个 API 网关。这些 API 网关能够最好地匹配前端环境的需求,而不用担心影响其他前端应用程序。后端为前端模式为实现多个网关提供了指导。
设计后端为前端模式 BFF--微服务通信设计模式
我们将通过根据面向前端的后端(Backends for Frontends pattern BFF)模式添加更多 API 网关模式来迭代我们的电子商务架构。

我们为应用程序添加了多个 API 网关。这些 API 网关最符合前端环境的需求,而不用担心影响其他前端应用程序。面向前端的后端模式为实现多个网关提供了指导。
后端内部微服务之间的服务到服务通信--微服务通信设计模式
我们在微服务架构中创建了 API 网关。所有这些同步请求都来自客户端,并通过 API 网关发送到内部微服务。
但如果客户端请求需要多个内部微服务怎么办?我们如何管理内部微服务之间的通信?

在设计微服务应用时,我们应该谨慎考虑后端内部微服务如何相互通信。最佳实践是尽可能减少服务间通信。
然而,有时由于客户需求或请求的操作需要调用多个内部服务,我们无法减少这些内部通信。
那么我们该如何实现这个请求?这些内部调用使得每个微服务之间存在耦合;在我们的案例中,购物车、产品和定价微服务相互依赖。
如果一个微服务没有响应,它就无法向客户端返回数据,因此它不具备容错能力。如果微服务的依赖性和耦合性增加,就会产生很多问题,我们无法发挥微服务架构的全部潜力。
如果客户端检查购物车,这将启动一系列操作。因此,如果我们尝试使用请求/响应同步消息模式来执行这个下单用例,它看起来就像这张图。
如我们所见,一个客户端 HTTP 请求需要六个同步 HTTP 请求。因此,很明显它增加了延迟,并对我们系统的性能、可扩展性和可用性产生负面影响。
如果我们已经部署了这个用例,如果步骤 5 或 6 失败,或者某些中间服务宕机怎么办?即使它们没有宕机,也可能很繁忙,有些服务无法及时获得响应,在这种情况下,高延迟是不可接受的。
我们可以采用两种方法来解决这个问题:
- 将微服务通信改为使用消息代理系统进行异步通信
- 使用服务聚合模式将一些查询操作聚合在一个 API 网关中。
服务聚合模式--微服务通信设计模式
为了最小化服务之间的通信,我们可以应用服务聚合模式。服务聚合设计模式接收来自客户端或 API 网关的请求,将请求分发到多个内部后端微服务,然后将结果合并,并在一个响应结构中回复给最初的请求者。

图示:服务聚合模式
通过服务聚合模式实现,我们可以减少客户端和微服务之间的闲聊和通信开销。
设计-服务聚合模式-服务注册模式-微服务通信设计模式
通过添加服务聚合模式 -服务注册模式-微服务通信设计模式来迭代我们的电子商务架构

图示:聚合和服务注册模式
微服务异步消息通信
如果您的通信仅在少数几个微服务之间进行,同步通信是合适的。但当多个微服务需要相互调用且可以等待长时间或复杂操作完成时,我们应该使用异步通信。

否则,微服务的依赖和耦合将产生瓶颈和严重的架构问题。
如果你有多个需要相互交互的微服务,并且希望在不依赖任何其他服务或实现松散耦合的情况下进行交互,那么我们应该在我们的微服务架构中使用异步消息通信。
由于异步消息通信基于事件工作,事件可以成为微服务之间的通信形式。我们将这种通信称为事件驱动通信。
发布-订阅设计模式
发布-订阅是一种消息模式,其中消息的发送者称为发布者,而特定的接收者称为订阅者

因此,发布者不会直接将消息发送给订阅者。相反,每个服务将消息分类并发布到消息代理系统中,而不知道有哪些订阅者。
设计-发布/订阅消息代理--微服务异步通信设计模式
我们将通过添加发布/订阅消息代理来迭代我们的电子商务架构,以提供微服务异步通信设计

如果我们调整技术栈,就可以开始考虑用于发布-订阅消息代理功能的选项。
微服务数据管理
微服务是独立的,并且只执行特定的功能需求。这意味着它们需要频繁地相互集成。主要来说,这些集成是通过查询彼此服务的数据来进行聚合或执行逻辑。
CQRS 设计模式
CQRS 是微服务间查询时的一种关键模式 。我们可以使用 CQRS 设计模式来避免复杂的查询并消除低效的连接。CQRS 代表命令和查询责任分离。这种模式将数据库的读和写操作分开
为了隔离命令和查询,最佳实践是将读数据库和写数据库分开。这样,如果我们的应用是读密集型的,也就是说读操作多于写操作,我们可以定义一个针对查询优化的自定义数据模式

物化视图模式是实现读取数据库的绝佳示例。因为通过这种方式,我们可以避免在查询操作中使用复杂的连接和映射,而是使用预定义的细粒度数据
通过这种隔离,我们甚至可以使用不同的数据库进行读写操作,例如使用 NoSQL 文档数据库进行读取,并使用关系型数据库进行 CRUD 操作
事件溯源模式/事件溯源模型
CQRS 设计模式中的读取数据库提供数据的有向视图,使用非规范化表。当然,这些有向视图读取数据库从写入数据库中消费事件,并将它们转换为非规范化视图

应用事件溯源模式是将数据保存操作转换为数据库。不是在数据库中保留最新的数据状态,事件溯源模式提供在数据库中按数据事件顺序保存所有事件。
设计架构--CQRS、事件溯源、最终一致性、物化视图
我们将通过应用 CQRS、事件溯源、最终一致性和物化视图来设计我们的电子商务架构

所以,当用户创建或更新订单时,我会使用关系型数据库。当用户查询单个订单或订单历史时,我会使用 NoSQL 读取数据库,并通过使用带有发布/订阅模式的消息代理系统来同步两个数据库以保持一致性。
事件驱动微服务架构
事件驱动的微服务架构意味着通过事件消息与微服务进行通信。我们看到,在微服务异步通信部分,可以使用发布/订阅模式以及 Kafka 作为消息代理系统。
事件驱动架构可以实现异步行为和松散耦合结构。例如,当需要数据时,服务可以通过事件来消费数据而不是发送请求。这种能力将提供性能提升

此外,事件驱动的微服务架构还有很多创新,如使用实时消息平台、流处理、事件中心、实时处理、批量处理、数据智能等。
根据这种新的事件驱动微服务架构,我们可以通过 Event-Hubs 连接所有内容。我们可以将 Event-Hubs 视为一个可以提供实时处理的大型事件存储数据库
设计架构------事件驱动微服务架构
我们将使用事件驱动微服务架构来设计我们的电子商务应用程序

现在我们可以决定这个架构的技术栈。当然,我们应该选择 Apache Kafka 作为事件中心,以及 Apache Spark 用于实时和近实时流应用程序,这些应用程序转换或响应数据流。
现在我们可以提出同样的问题;我们的设计能容纳多少并发请求

通过这种最新的基于事件的微服务架构,使用容器和编排器进行部署,能够以低延迟容纳目标并发请求。因为这种架构是完全松散耦合的,并且设计用于高可扩展性和高可用性。
我们已经根据设计原则和模式设计了我们的电子商务微服务架构。现在,您可以利用这些知识和学习成果,设计自己的架构,并学会如何在设计中使用这些模式工具箱。
原文地址:Monolithic to Microservices Architecture with Patterns & Best Practices
本文到此结束,感谢阅读。