容器化微服务

提示

此内容摘自电子书《使用 .NET MAUI 的企业应用程序模式》,可在 .NET 文档上获取,也可作为免费可下载的 PDF 脱机阅读。

Enterprise Application Patterns Using .NET MAUI eBook cover thumbnail.

开发客户端-服务器应用程序的重点在于构建在每一层使用特定技术的分层应用程序。 此类应用程序通常称为整体式应用程序,打包到硬件上并进行预缩放以达到峰值负载。 这种开发方法的主要缺点是每一层内的组件之间紧密耦合、单个组件不易缩放以及测试成本。 仅仅是一个小的更新就可能会对层的其余部分产生无法预料的影响,因此对应用程序组件的更改需要重新测试和重新部署其整个层。

在云时代背景下尤其值得关注的是单个组件无法轻易缩放这一点。 整体式应用程序包含特定于域的功能,通常按功能层划分,例如前端、业务逻辑和数据存储。 下图演示了如何通过将整个应用程序克隆到多台计算机上来缩放整体式应用程序。

Monolithic application scaling approach.

微服务

微服务提供了一种不同的应用程序开发和部署方法,该方法能满足新式云应用程序的灵活性、规模和可靠性要求。 微服务应用程序拆分为独立组件,共同提供应用程序的整体功能。 微服务一词强调应用程序应该由足够小的服务组成以反映特定的关注点,因此每个微服务都实现单个功能。 此外,每个微服务都有定义完善的协定,其他微服务与之通信和共享数据。 微服务的典型示例包括购物车、库存处理、购买子系统和付款处理。

微服务可以独立缩放,而大型整体式应用程序则一起缩放。 这意味着可以缩放需要更多处理能力或网络带宽来支持需求的特定功能区域,而不是不必要地横向扩展其他应用程序区域。 下图演示了此方法,其中微服务是独立部署和缩放的,从而跨计算机创建服务实例。

Microservices application scaling approach.

微服务横向扩展几乎是瞬时的,使应用程序能够适应不断变化的负载。 例如,应用程序面向 Web 的功能中的单个微服务可能是唯一需要横向扩展以处理额外传入流量的微服务。

应用程序可伸缩性的经典模型是拥有一个负载均衡的无状态层,其中包含一个共享的外部数据存储来存储持久性数据。 有状态微服务管理自己的持久性数据,通常将其本地存储在它们所在的服务器上,以避免网络访问的开销和跨服务操作的复杂性。 这可以实现尽可能快的数据处理,并可以消除对缓存系统的需求。 此外,可缩放的有状态微服务通常在其实例之间对数据进行分区,以便管理单个服务器所能支持的数据大小和传输吞吐量。

微服务还支持独立更新。 微服务之间的这种松散耦合提供了快速可靠的应用程序演变。 它们的独立分布式性质有助于滚动更新,在任何给定的时间,只有单个微服务的一部分实例会更新。 因此,如果检测到问题,可以在所有实例使用有问题的代码或配置更新之前回滚出错的更新。 同样,微服务通常使用架构版本控制,以便客户端在应用更新时看到一致的版本,而不管与哪个微服务实例进行通信。

因此,微服务应用程序相较于整体式应用程序有很多好处:

  • 每个微服务相对较小,易于管理和发展。
  • 每个微服务都可以独立于其他服务进行开发和部署。
  • 每个微服务可以独立横向扩展。 例如,目录服务或购物篮服务可能比订购服务更需要横向扩展。 因此,由此产生的基础结构在横向扩展时将更有效地消耗资源。
  • 每个微服务都能隔离任何问题。 例如,如果某个服务中存在问题,则只会影响该服务。 其他服务可以继续处理请求。
  • 每个微服务都可以使用最新的技术。 因为微服务是自治的,并且可以并行运行,所以可以使用最新的技术和框架,而不是被迫使用可能由整体式应用程序使用的旧框架。

但是,基于微服务的解决方案也有潜在的缺点:

  • 选择如何将应用程序划分为微服务可能具有挑战性,因为每个微服务都必须是完全自主的、端到端的,包括对其数据源的责任。
  • 开发人员必须实现服务间通信,这将增加应用程序的复杂性和延迟。
  • 多个微服务之间的原子事务通常不可能。 因此,业务要求必须包含微服务之间的最终一致性。
  • 在生产环境中,部署和管理一个包含许多独立服务的系统存在操作复杂性。
  • 客户端到微服务的直接通信可能会使重构微服务的协定变得困难。 例如,一段时间后,系统分区到服务的方式可能需要更改。 单个服务可能拆分为两个或更多服务,两个服务可能会合并。 当客户端直接与微服务通信时,这种重构工作可能会破坏与客户端应用的兼容性。

容器化

容器化是一种软件开发方法,其中应用程序及其版本化依赖项集,以及抽象为部署清单文件的环境配置,一起打包为容器映像,作为一个单元进行测试,然后部署到主机操作系统。

容器是一个隔离、受资源控制和可移植的操作环境,应用程序可以在其中运行而不会触及其他容器或主机的资源。 因此,容器的外观和运行方式类似于新安装的物理计算机或虚拟机。

容器和虚拟机之间存在许多相似之处,如下所示。

Comparison of virtual machines and containers.

容器在操作系统上运行、具有文件系统,并且可以通过网络访问,就像它是物理或虚拟机一样。 但是容器使用的技术和概念与虚拟机有很大不同。 虚拟机包括应用程序、必需的依赖项以及完整的来宾操作系统。 容器包括应用程序及其依赖项,但与其他容器共享操作系统,在主机操作系统上作为隔离进程运行(Hyper-V 容器例外,该容器在各容器特定虚拟机内部运行。)。 因此,容器共享资源,通常需要的资源比虚拟机少。

面向容器进行开发和部署方法的优点是,它消除了由环境设置不一致和随之而来的问题所引起的大多数问题。 此外,容器通过根据需要实例化新容器实现应用程序快速纵向扩展功能。

创建和使用容器时的主要概念包括:

概念 说明
容器主机 配置为托管容器的物理或虚拟机。 容器主机将运行一个或多个容器。
容器映像 映像由堆叠在彼此之上的分层文件系统共同组成,是容器的基础。 映像没有状态,并且在部署到不同的环境时不会更改。
容器 容器是映像的运行时实例。
容器 OS 映像 容器从映像部署。 容器操作系统映像是可能组成容器的许多映像层中的第一层。 容器操作系统是不可变的,无法修改。
容器存储库 每次创建容器映像时,该映像及其依赖项都会存储在本地存储库中。 这些映像可以在容器主机上重复使用多次。 容器映像还可以存储在公共或专用注册表(如 Docker Hub)中,以便可以在许多不同的容器主机上使用它们。

企业在实现基于微服务的应用程序时越来越多地采用容器,Docker 已成为大多数软件平台和云供应商采用的标准容器实现。

eShopOnContainers 参考应用程序使用 Docker 来托管四个容器化后端微服务,如下图所示。

eShopOnContainers reference application back-end microservices.

参考应用程序中的后端服务体系结构以协作微服务和容器的形式分解为多个自治子系统。 每个微服务提供一个功能区域:标识服务、目录服务、订购服务和购物篮服务。

每个微服务都有自己的数据库,从而与其他微服务完全分离。 如有必要,使用应用程序级事件实现来自不同微服务的数据库之间的一致性。 有关详细信息,请参阅微服务之间的通信

有关参考应用程序的详细信息,请参阅 .NET 微服务:容器化 .NET 应用程序的体系结构

客户端和微服务之间的通信

eShopOnContainers 多平台应用使用客户端到微服务直接通信与容器化后端微服务进行通信,如下所示。

Direct client-to-microservice communication.

借助客户端到微服务直接通信,多平台应用通过其公共终结点直接向每个微服务发出请求,每个微服务使用不同的 TCP 端口。 在生产中,终结点通常会映射到微服务的负载均衡器,该负载均衡器在可用实例之间分配请求。

提示

请考虑使用 API 网关通信。

在构建基于微服务的大型复杂应用程序时,客户端到微服务直接通信可能存在缺陷,但对于小型应用程序来说已经足够了。 在设计一个基于微服务的大型应用程序(包含数十个微服务)时,请考虑使用 API 网关通信。 有关详细信息,请参阅.NET 微服务:适用于容器化 .NET 应用程序的体系结构

微服务之间的通信

基于微服务的应用程序是一个分布式系统,可能在多台计算机上运行。 每个服务实例通常是一个进程。 因此,服务必须使用进程内通信协议(如 HTTP、TCP、高级消息队列协议 (AMQP))或二进制协议进行交互,具体取决于每个服务的性质。

微服务到微服务通信的两种常见方法是基于 HTTP 的 REST 通信(查询数据时)和轻量级异步消息传送(跨多个微服务更新通信时)。

在跨多个微服务传播更改时,基于异步消息传送的事件驱动通信至关重要。 使用此方法,微服务会在发生重大情况时发布事件,例如,当它更新业务实体时。 其他微服务订阅这些事件。 然后,微服务收到事件时,可以更新其自己的业务实体,这可能进而导致发布更多事件。 通常通过事件总线实现此发布-订阅功能。

事件总线可实现微服务之间的发布-订阅式通信,无需组件相互显式识别,如下所示。

Publish-subscribe with an event bus.

从应用程序的角度来看,事件总线只是一个通过接口公开的发布-订阅通道。 但是,实现事件总线的方式可能会有所不同。 例如,事件总线实现可以使用 RabbitMQ、Azure 服务总线或其他服务总线,例如 NServiceBus 和 MassTransit。 下图显示了如何在 eShopOnContainers 参考应用程序中使用事件总线。

Asynchronous event-driven communication in the reference application.

使用 RabbitMQ 实现的 eShopOnContainers 事件总线提供了一对多的异步发布-订阅功能。 这意味着在发布一个事件后,可以有多个订阅者侦听同一个事件。 下图说明了这种关系。

One-to-many communication

这种一对多的通信方法使用事件来实现跨多个服务的业务事务,从而确保服务之间的最终一致性。 最终一致事务由一系列分布式步骤组成。 因此,当用户配置文件微服务收到 UpdateUser 命令时,它会更新其数据库中的用户详细信息并将 UserUpdated 事件发布到事件总线。 购物篮微服务和订购微服务都已订阅接收此事件,并相应地在各自的数据库中更新了买方信息。

注意

使用 RabbitMQ 实现的 eShopOnContainers 事件总线旨在仅用作概念证明。 对于生产系统,应考虑替代事件总线实现。

有关事件总线实现的详细信息,请参阅 .NET 微服务:适用于容器化 .NET 应用程序的体系结构

总结

微服务提供了一种应用程序开发和部署方法,该方法能满足新式云应用程序的灵活性、规模和可靠性要求。 微服务的一个主要优点是可以独立横向扩展,这意味着可以缩放需要更多处理能力或网络带宽的特定功能区域来支持需求,而不需要对需求没有增加的应用程序区域进行不必要的缩放。

容器是一个隔离、受资源控制和可移植的操作环境,应用程序可以在其中运行而不会触及其他容器或主机的资源。 企业在实现基于微服务的应用程序时越来越多地采用容器,Docker 已成为大多数软件平台和云供应商采用的标准容器实现。