Skip to content

Latest commit

 

History

History
761 lines (495 loc) · 58.7 KB

File metadata and controls

761 lines (495 loc) · 58.7 KB

五、将微服务架构应用于企业应用

本章致力于描述基于称为微服务的小模块的高度可扩展架构。微服务架构允许细粒度的扩展操作,其中每个模块都可以根据需要进行扩展,而不会影响系统的其余部分。此外,通过允许每个系统子部分独立于其他子部分发展和部署,它们允许更好的持续集成/持续部署CI/CD

在本章中,我们将介绍以下主题:

  • 什么是微服务?
  • 微服务什么时候有帮助?
  • NET 如何处理微服务?
  • 管理微服务需要哪些工具?

到本章结束时,您将学习如何在.NET 中实现单个微服务。第 6 章Azure Service Fabric第 7 章Azure Kubernetes Service也解释了如何部署、调试和管理整个基于微服务的应用。

技术要求

在本章中,您将要求:

什么是微服务?

微服务架构允许组成解决方案的每个模块独立于其他模块进行扩展,以最低成本实现最大吞吐量。事实上,扩展整个系统而不是当前的瓶颈不可避免地会导致资源的显著浪费,因此子系统扩展的细粒度控制对系统的总体成本有相当大的影响。

然而,微服务不仅仅是可伸缩的组件——它们是可以独立开发、维护和部署的软件构建块。在可独立开发、维护和部署的模块之间拆分开发和维护提高了整个系统的 CI/CD 周期(CI/CD 概念在第 3 章**中的使用 Azure DevOps组织您的工作一节中有更详细的解释)使用 Azure DevOps记录需求。

CI/CD 的改进源于微服务独立性,因为它支持以下功能:

  • 在不同类型的硬件上扩展和分发微服务。
  • 由于每个微服务都是独立于其他微服务部署的,因此不存在二进制兼容性或数据库结构兼容性约束。因此,不需要调整组成系统的不同微服务的版本。这意味着它们中的每一个都可以根据需要进化,而不受其他人的约束。
  • 将他们的开发任务分配给完全独立的较小团队,从而简化工作组织,减少处理大型团队时不可避免的协调效率低下。
  • 由于每个微服务都是一个独立的部署单元,因此使用更合适的技术在更合适的环境中实现每个微服务。这意味着选择最适合您的需求的工具和最小化开发工作和/或最大化性能的环境。
  • 由于每个微服务都可以使用不同的技术、编程语言、工具和操作系统来实现,因此企业可以通过将环境与开发人员的能力相匹配来使用所有可用的人力资源。例如,如果未充分利用的 Java 开发人员以相同的所需行为在 Java 中实现微服务,他们也可以参与.NET 项目。
  • 遗留子系统可以嵌入到独立的微服务中,从而使它们能够与较新的子系统协作。这样,公司可以缩短新系统版本的上市时间。此外,通过这种方式,遗留系统可以缓慢地向更现代的系统发展,对成本和组织有可接受的影响。

下一个小节解释了微服务的概念是如何构思的。然后,我们将继续这个介绍性部分,探索基本的微服务设计原则,并分析为什么微服务通常被设计为 Docker 容器。

微服务与模块概念的演变

为了更好地理解微服务的优势及其设计技术,我们必须记住软件模块化和软件模块的双重性质:

  • 代码模块化指的是代码组织,它使我们能够轻松地修改代码块,而不会影响应用的其余部分。它通常通过面向对象的设计来实现,在面向对象的设计中,模块可以用类来标识。
  • 部署模块化取决于您的部署单元是什么以及它们具有哪些属性。最简单的部署单元是可执行文件和库。因此,例如,动态链接库DLL)肯定比静态库更模块化,因为它们在部署之前不能与主可执行文件链接。

虽然代码模块化的基本概念已经停滞不前,但部署模块化的概念仍在不断发展,微服务目前正沿着这条发展道路发展。

作为对微服务发展道路上主要里程碑的简要回顾,我们可以说,首先,单片可执行文件被分解为静态库。后来,DLL 取代了静态库。

当.NET(以及其他类似的框架,如 Java)改进了可执行文件和库的模块化时,发生了巨大的变化。事实上,使用.NET,它们可以部署在不同的硬件和操作系统上,因为它们是在库第一次执行时编译的中间语言中部署的。此外,它们还克服了以前 DLL 的一些版本控制问题,因为任何可执行文件都会带来一个 DLL,其版本与安装在操作系统中的同一 DLL 的版本不同。

然而,.NET 不能接受两个引用的 DLL——比方说,A和[T2】B——使用一个公共依赖项的两个不同版本——比方说,C。例如,假设有一个更新的a的版本,其中包含许多我们想要使用的新功能,反过来,我们需要依赖B不支持的C的更新版本。在这种情况下,我们应该放弃更新版本的A,因为CB不兼容。这一困难导致了两个重要的变化:

  • 开发领域从 DLL 和/或单个文件转移到包管理系统,如 NuGet 和 npm,它们通过语义版本控制自动检查版本兼容性。
  • 面向服务架构SOA。部署单元开始实现为 SOAP,然后实现为 RESTWeb 服务。这解决了版本兼容性问题,因为每个 web 服务在不同的进程中运行,并且可以使用每个库的最合适版本,而不会导致与其他 web 服务不兼容的风险。此外,每个 web 服务公开的接口与平台无关,也就是说,web 服务可以使用任何框架与应用连接并在任何操作系统上运行,因为 web 服务协议基于普遍接受的标准。SOA 和协议将在第 14 章应用具有.NETCore 的面向服务架构中进行更详细的讨论。

微服务是 SOA 的一种演变,它添加了更多的特性和约束,以提高服务的可伸缩性和模块化,从而改善整个 CI/CD 周期。有时有人说微服务在 SOA 中做得很好

微服务设计原则

总而言之,微服务架构是一种 SOA,它最大化了独立性和细粒度扩展。既然我们已经阐明了微服务独立性和细粒度扩展的所有优点,以及独立性的本质,我们就可以了解微服务设计原则了。

让我们从独立性约束产生的原则开始。我们将在单独的小节中逐一讨论。

设计选择的独立性

每个微服务的设计不能依赖于在实现其他微服务时所做的设计选择。这一原则使每个微服务 CI/CD 周期完全独立,并使我们在如何实现每个微服务方面有更多的技术选择。这样,我们就可以选择最好的技术来实现每个微服务。

这一原则的另一个后果是不同的微服务不能连接到同一个共享存储(数据库或文件系统),因为共享同一个存储也意味着共享决定存储子系统结构的所有设计选择(数据库表设计、数据库引擎等)。因此,一个微服务要么有自己的数据存储,要么根本没有存储,并与负责处理存储的其他微服务通信。

在这里,拥有专用数据存储并不意味着物理数据库分布在微服务本身的进程边界内,而是意味着微服务可以独占访问由外部数据库引擎处理的数据库或一组数据库表。事实上,出于性能原因,数据库引擎必须在专用硬件上运行,并具有针对其存储功能进行优化的操作系统和硬件功能。

通常,通过区分逻辑微服务和物理微服务,设计选择的独立性以较轻的形式进行解释。更具体地说,逻辑微服务是由几个使用相同数据存储但独立负载平衡的物理微服务实现的。也就是说,逻辑微服务被设计为一个逻辑单元,然后分割成更多的物理微服务,以实现更好的负载平衡。

独立于部署环境

微服务在不同的硬件节点上扩展,不同的微服务可以托管在同一节点上。因此,微服务越不依赖于操作系统提供的服务和其他安装的软件,它就可以部署在更多可用的硬件节点上。还可以执行更多节点优化。

这就是为什么微服务通常被集装箱化并使用 Docker 的原因。容器将在本章的容器和 Docker小节中进行更详细的讨论,但基本上,容器化是一种技术,它允许每个微服务将其依赖性带到其中,以便它可以在任何地方运行。

松耦合

每个微服务必须与所有其他微服务松散耦合。这一原则具有双重性质。一方面,这意味着,根据面向对象的编程原则,每个微服务公开的接口不能太具体,而应尽可能通用。然而,这也意味着微服务之间的通信必须最小化,以降低通信成本,因为微服务不共享相同的地址空间,也不在不同的硬件节点上运行。

没有链接的请求/响应

当一个请求到达一个微服务时,它不能导致对其他微服务的嵌套请求/响应的递归链,因为类似的链会导致不可接受的响应时间。如果所有微服务的私有数据模型在每次更改时都与推送通知同步,则可以避免链式请求/响应。换句话说,一旦微服务处理的数据发生变化,这些变化就会被发送到所有可能需要它们来满足其请求的微服务。这样,每个微服务在其私有数据存储中都拥有为其所有传入请求提供服务所需的所有数据,而无需向其他微服务请求其缺少的数据。

总之,每个微服务必须包含为传入请求提供服务并确保快速响应所需的所有数据。为了使其数据模型保持最新并为传入请求做好准备,微服务必须在数据更改发生时立即进行通信。这些数据更改应该通过异步消息进行通信,因为同步嵌套消息会导致不可接受的性能,因为它们会阻塞调用树中涉及的所有线程,直到返回结果。

值得指出的是,设计选择的独立性原则实质上是领域驱动设计的有界上下文原则,我们将在第 12 章**了解软件解决方案中的不同领域中详细讨论。在本章中,我们将看到,通常,全领域驱动的设计方法对于每个微服务的更新子系统非常有用。

一般来说,所有根据有界上下文原则开发的系统都可以用微服务架构更好地实现,这一点很重要。事实上,一旦一个系统被分解成几个完全独立且松散耦合的部分,很可能由于不同的流量和不同的资源需求,这些不同的部分将需要独立地进行扩展。

在前面的约束条件下,我们还必须添加一些用于构建可重用 SOA 的最佳实践。关于这些最佳实践的更多细节将在第 14 章**中给出,应用具有.NETCore 的面向服务的架构,但现在,大多数 SOA 最佳实践都是通过用于实现 web 服务的工具和框架自动实施的。

细粒度扩展要求微服务小到足以隔离定义良好的功能,但这也需要一个复杂的基础设施,负责自动实例化微服务,在各种硬件计算资源(通常称为节点上分配实例,并进行扩展根据需要提供这些结构。这些结构将在本章中介绍需要哪些工具来管理微服务?部分,并在第 6 章Azure Service Fabric第 7 章Azure Kubernetes中详细讨论服务

此外,通过异步通信进行通信的分布式微服务的细粒度扩展要求每个微服务具有弹性。事实上,定向到特定微服务实例的通信可能会由于硬件故障或目标实例在负载平衡操作期间被杀死或移动到另一个节点的简单原因而失败。

临时故障可以通过指数重试来克服。这是我们在每次失败后重试相同操作的地方,延迟呈指数级增加,直到达到最大尝试次数。例如,首先,我们将在 10 毫秒后重试,如果此重试操作导致失败,则在 20 毫秒后进行新的尝试,然后在 40 毫秒后进行新的尝试,依此类推。

另一方面,长期失败通常会导致重试操作激增,这可能会以类似于拒绝服务攻击的方式耗尽所有系统资源。因此,通常,指数重试与断路策略一起使用:在给定数量的故障后,假定长期故障,并通过返回即时故障而不尝试通信操作,在给定时间内阻止对资源的访问。

同样重要的是,由于故障或请求峰值导致的某些子系统的拥塞不会传播到其他系统部分,以防止整个系统的拥塞。舱壁隔离通过以下方式避免拥塞传播:

  • 仅允许最大数量的类似同时出站请求;比如说,10。这类似于为线程创建设置上限。
  • 超过上一个界限的请求将排队。
  • 如果达到最大队列长度,任何进一步的请求都会引发异常以中止它们。

重试策略可能会使同一消息被多次接收和处理,因为发送方没有收到消息已被接收的确认,或者只是因为操作超时,而接收方实际收到了消息。这个问题唯一可能的解决方案是设计所有消息,使它们是幂等的,也就是说,设计消息的方式是,多次处理同一消息与一次处理同一消息具有相同的效果。

例如,将数据库表字段更新为值是一个幂等操作,因为重复一次或两次具有完全相同的效果。但是,增加十进制字段不是幂等运算。微服务设计者应该努力用尽可能多的幂等元消息来设计整个应用。剩余的非幂等消息必须通过以下方式或使用其他类似技术转换为幂等消息:

  • 附加唯一标识每条消息的时间和某个标识符。
  • 将接收到的所有消息存储在字典中,该字典已由附加到上一点中提到的消息的唯一标识符编制索引。
  • 拒绝旧邮件。
  • 当收到可能是重复的消息时,请验证它是否包含在字典中。如果是,那么它已经被处理过了,所以拒绝它。
  • 由于旧消息会被拒绝,因此可以定期将它们从字典中删除,以避免其成倍增长。

我们将在第 6 章**Azure 服务结构末尾的示例中使用此技术。

值得指出的是,一些消息代理,如 Azure Service Bus,提供了实现前面描述的技术的工具。Azure 服务总线在*.NET 通信设施*小节中讨论。

在下一小节中,我们将讨论基于 Docker 的微服务集装箱化。

集装箱码头工人

我们已经讨论了拥有不依赖于运行环境的微服务的优势:更好的硬件利用率、将传统软件与较新模块混合的能力、混合多个开发堆栈以便为每个模块实现使用最佳堆栈的能力,等等。通过将每个微服务及其所有依赖项部署到私有虚拟机上,可以轻松实现与宿主环境的独立性。

然而,使用操作系统的私有副本启动虚拟机需要大量时间,必须快速启动和停止微服务,以降低负载平衡和故障恢复成本。事实上,启动新的微服务可能是为了替换有故障的微服务,也可能是因为它们从一个硬件节点移动到另一个硬件节点以执行负载平衡。此外,将操作系统的整个副本添加到每个微服务实例将是一种过度的开销。

幸运的是,微服务可以依赖一种更轻的技术形式:容器。容器是一种轻型虚拟机。他们没有虚拟化一台完整的机器——他们只是虚拟化位于操作系统内核之上的操作系统文件系统级别。它们使用主机的操作系统(内核、DLL 和驱动程序),并依赖操作系统的本机功能来隔离进程和资源,以确保为它们运行的映像提供一个隔离的环境。

因此,容器被绑定到特定的操作系统,但它们不会承受在每个容器实例中复制和启动整个操作系统的开销。

在每台主机上,容器由运行时处理,运行时负责从映像创建容器,并为每个容器创建一个隔离的环境。最著名的容器运行时是 Docker,它是一个事实上的容器化标准。

图像是指定每个容器中放置的内容以及在容器外部公开哪些容器资源(如通信端口)的文件。图像不需要明确指定其全部内容,但可以分层。这样,通过在现有映像上添加新软件和配置信息来构建映像。

例如,如果要将.NET 应用部署为 Docker 映像,只需将软件和文件添加到 Docker 映像,然后引用现有的.NET Docker 映像即可。

为了便于图像引用,将图像分组到公共或私有的注册表中。它们类似于 NuGet 或 npm 注册中心。Docker 提供公共注册表(https://hub.docker.com/_/registry 在这里,您可以找到您可能需要在自己的图像中引用的大多数公共图像。但是,每个公司都可以定义私有注册中心。例如,Azure 提供 Microsoft 容器注册表,您可以在其中定义您的专用容器注册表服务:https://azure.microsoft.com/en-us/services/container-registry/ 。在那里,您还可以找到代码中可能需要引用的大多数与.NET 相关的图像。

在实例化每个容器之前,Docker 运行时必须解决所有递归引用。由于 Docker 运行时有一个缓存,它在缓存中存储与每个输入图像相对应的完整组装图像,并且该图像已被处理,因此每次创建新容器时都不会执行此繁琐的作业。

由于每个应用通常由几个模块组成,在不同的容器中运行,Docker 还允许.yml文件,也称为组合文件,指定以下信息:

  • 要部署哪些映像。
  • 每个映像公开的内部资源必须如何映射到主机的物理资源。例如,Docker 映像公开的通信端口必须如何映射到物理机器的端口。

我们将在中分析 Docker 图像和.yml文件。NET 如何处理微服务?本章第节。

Docker 运行时在一台机器上处理图像和容器,但通常情况下,容器化微服务在由多台机器组成的集群上进行部署和负载平衡。集群由名为Orchestrator的软件处理。中将引入编排器,需要哪些工具来管理微服务?本章的部分,并在第 6 章Azure 服务结构第 7 章Azure Kubernetes 服务中详细描述。

现在我们已经了解了什么是微服务,它们可以解决什么问题,以及它们的基本设计原则,我们已经准备好分析何时以及如何在我们的系统架构中使用它们。下一节将分析何时应该使用它们。

微服务什么时候有帮助?

这个问题的答案要求我们理解微服务在现代软件架构中扮演的角色。我们将在以下两个小节中介绍这一点:

  • 分层架构和微服务
  • 什么时候值得考虑微服务架构?

让我们从详细了解分层架构和微服务开始。

分层架构和微服务

企业系统通常组织在逻辑独立的层中。第一层是与用户交互的层,称为表示层,而最后一层负责存储/检索数据,称为数据层。请求起源于表示层并通过所有层,直到它们到达数据层,然后返回,反向遍历所有层,直到它们到达表示层,表示层负责将结果呈现给用户/客户端。层不能

每个层从上一层获取数据,对其进行处理,并将其传递到下一层。然后,它从下一层接收结果并将其发送回上一层。此外,抛出的异常不能跳转层——每一层都必须拦截所有异常,或者以某种方式解决它们,或者将它们转换为用其前一层语言表示的其他异常。层架构确保每个层的功能与所有其他层的功能完全独立。

例如,我们可以在不影响数据层之上的所有层的情况下更改数据库引擎。同样,我们可以完全更改用户界面,即表示层,而不会影响系统的其余部分。

此外,每一层实现不同类型的系统规范。数据层负责系统 To0T0 必须记住的 AUT1,表示层负责系统用户交互协议,中间的所有层实现域规则,它指定数据必须如何处理(例如,必须如何计算雇员工资)。通常,数据层和表示层仅由一个域规则层(称为业务层或应用层)分隔。

每一层不同的语言:数据层所选存储引擎的语言,业务层讲领域专家的语言,表示层讲用户的语言。因此,当数据和异常从一层传递到另一层时,必须将它们翻译成目标层的语言。

第 12 章中的用例–理解用例的领域,理解软件解决方案中的不同领域,将给出一个详细的如何构建分层架构的示例,该部分专门用于领域驱动设计。

也就是说,微服务如何适应分层架构?它们是否足以满足所有层或某些层的功能?单个微服务可以跨多个层吗?

最后一个问题最容易回答:是的!事实上,我们已经说过,微服务应该在其逻辑边界内存储所需的数据。因此,存在跨越业务层和数据层的微服务。另一些人负责封装共享数据,并将其限制在数据层中。因此,我们可能有业务层微服务、数据层微服务和跨这两层的微服务。那么,演示层呢?

表示层

如果表示层是在服务器端实现的,那么它也可以适合于微服务架构。单页应用和移动应用在客户机上运行表示层,因此它们要么直接连接到业务微服务层,要么更常见地连接到公开公共接口并负责将请求路由到正确微服务的API 网关

在微服务架构中,当表示层是网站时,可以使用一组微服务来实现。但是,如果它需要大量的 web 服务器和/或大量的框架,那么将它们封装起来可能并不方便。这一决定还必须考虑当 Web 服务器和 Web 服务器和系统的其余部分之间可能需要硬件防火墙时发生的性能损失。

ASP.NET 是一个轻量级框架,运行在 light Kestrel web 服务器上,因此它可以高效地进行容器化,并用于内部网应用的微服务。然而,公共高流量网站需要专用的硬件/软件组件,以防止它们与其他微服务一起部署。事实上,虽然 Kestrel 是内联网网站的可接受解决方案,但公共网站需要更完整的 web 服务器,如 IIS、Apache 或 NGINX。在这种情况下,安全性和负载平衡要求更加迫切,需要专用的硬件/软件节点和组件。因此,基于微服务的架构通常提供专门的组件,负责与外部世界的接口。例如,在第 7 章**Azure Kubernetes 服务中,我们会看到在Kubernetes集群中,这个角色是由所谓的入口扮演的。

无需特定于微服务的技术,单片网站可以轻松分解为负载平衡的较小子网站,但微服务架构可以将微服务的所有优势带入单个 HTML 页面的构建中。更具体地说,不同的微服务可能负责每个 HTML 页面的不同区域。不幸的是,在撰写本文时,使用可用的.NET 技术很难实现类似的场景。

在这里可以找到一个概念证明,它使用基于 ASP.NET 的微服务实现了一个网站,这些微服务在每个 HTML 页面的构建中相互配合:https://github.com/Particular/Workshop/tree/master/demos/asp-net-core 。这种方法的主要限制是,微服务的合作只是为了生成生成 HTML 页面所需的数据,而不是生成实际的 HTML 页面。相反,这是由单片网关处理的。事实上,在撰写本文时,ASP.NET MVC 之类的框架并没有为 HTML 生成的分发提供任何便利。我们将在第 15 章**中回到这个例子,展示 ASP.NETCore MVC

现在,我们已经澄清了系统的哪些部分可以从采用微服务中受益,我们准备在决定如何采用微服务时说明规则。

什么时候值得考虑微服务架构?

微服务可以改善业务层和数据层的实现,但采用微服务会带来一些成本:

  • 将实例分配给节点并扩展它们需要支付云费用或内部基础设施和许可证费用。
  • 将一个独特的进程拆分为更小的通信进程会增加通信成本和硬件需求,特别是在微服务是容器化的情况下。
  • 为微服务设计和测试软件需要更多的时间,并且在时间和复杂性上增加了工程成本。特别是,使微服务具有弹性并确保它们充分处理所有可能的故障,以及通过集成测试验证这些功能,可以将开发时间增加一个数量级以上。

那么,微服务什么时候值得使用它们呢?是否有必须作为微服务实现的功能?

第二个问题的粗略答案是:是的,当应用在流量和/或软件复杂性方面足够大时。事实上,随着应用复杂性的增加和通信量的增加,建议我们支付与扩展相关的成本,因为这允许开发团队进行更多的扩展优化和更好的处理。我们为此支付的成本很快就会超过采用微服务的成本。

因此,如果细粒度扩展对我们的应用有意义,并且如果我们能够估计细粒度扩展和开发给我们带来的节约,我们就可以轻松地计算出总体应用吞吐量限制,从而方便采用微服务。

微服务成本也可以通过我们产品/服务的市场价值的增加来证明。由于微服务架构允许我们使用针对其使用进行了优化的技术来实现每个微服务,因此添加到我们软件中的质量可以证明所有或部分微服务成本是合理的。

然而,缩放和技术优化并不是唯一需要考虑的参数。有时,我们被迫采用微服务架构,而无法进行详细的成本分析。

如果负责整个系统的 CI/CD 的团队规模过大,那么这个大团队的组织和协调会造成困难和低效。在这种情况下,最好采用一种架构,将整个 CI/CD 周期分解为可由较小团队处理的独立部分。

此外,由于这些开发成本只有在大量请求的情况下才是合理的,因此我们可能需要由不同团队开发的独立模块处理高流量。因此,扩展优化和减少开发团队之间交互的需要使得采用微服务架构非常方便。

由此,我们可以得出结论,如果系统和开发团队增长过多,那么有必要将开发团队分成更小的团队,每个团队都在一个有效的有界上下文子系统上工作。在类似的情况下,微服务架构很可能是唯一可能的选择。

另一种迫使采用微服务架构的情况是,将较新的子部分与基于不同技术的遗留子系统集成,因为容器化微服务是实现之间高效交互的唯一方式旧系统和新的子部件,以逐渐用新的子部件取代旧的子部件。类似地,如果我们的团队由具有不同开发堆栈经验的开发人员组成,那么基于容器化微服务的架构可能成为必须的

在下一节中,我们将分析可用的构建块和工具,以便实现基于.NET 的微服务。

NET 如何处理微服务?

NET 被认为是一个多平台框架,它轻巧、快速,足以实现高效的微服务。特别是,ASP.NET 是实现文本 REST 和二进制 gRPC API 与微服务通信的理想工具,因为它可以与 Kestrel 等轻型 web 服务器高效运行,并且本身是轻型和模块化的。

整个.NET 框架在思想中将微服务作为一个战略部署平台,并具有用于构建高效轻量级 HTTP 和 gRPC 通信的设施和软件包,以确保服务弹性并处理长期运行的任务。以下小节描述了我们可以用来实现基于.NET 的微服务架构的一些不同工具或解决方案。

.NET 通讯设施

微服务需要两种通信渠道。

  • 第一个是直接或通过 API 网关接收外部请求的通信通道。由于可用的 web 服务标准和工具,HTTP 是用于外部通信的常用协议。NET 的主要 HTTP/gRPC 通信工具是 ASP.NET,因为它是一个轻量级 HTTP/gRPC 框架,非常适合在小型微服务中实现 Web API。我们将在第 14 章中详细介绍 ASP.NET 应用,应用以.NET 为核心的面向服务架构,专门用于 HTTP 和 gRPC 服务。NET 还提供了一个高效、模块化的 HTTP 客户端解决方案,该解决方案能够汇集和重用重连接对象。此外,HttpClient类将在第 14 章应用具有.NETCore 的面向服务架构中进行更详细的描述。
  • 第二种是一种不同类型的通信渠道,用于向其他微服务推送更新。事实上,我们已经提到,微服务内部通信不能由正在进行的请求触发,因为阻止调用其他微服务的复杂树会将请求延迟增加到不可接受的水平。因此,在使用更新之前不得立即请求更新,并且应在状态发生更改时推送更新。理想情况下,这种通信应该是异步的,以实现可接受的性能。事实上,同步调用会在发送方等待结果时阻塞发送方,从而增加每个微服务的空闲时间。但是,如果通信足够快(低通信延迟和高带宽),则可以接受仅将请求放入处理队列,然后返回成功通信的确认而不是最终结果的同步通信。发布者/订阅者通信更可取,因为在这种情况下,发送者和接收者不需要相互了解,从而增加了微服务的独立性。事实上,所有对某种类型的通信感兴趣的接收者只需要注册以接收特定的事件,而发送者只需要发布这些事件。所有连接都由一个服务执行,该服务负责排队事件并将其分派给所有订户。发布者/订阅者模式将在第 11 章设计模式和.NET 5 实现以及其他有用模式中进行更详细的描述。

虽然.NET 不直接提供可能有助于异步通信的工具或实现发布者/订户通信的客户端/服务器工具,但 Azure 通过Azure 服务总线提供了类似的服务。Azure 服务总线通过 Azure 服务总线队列处理排队异步通信,通过 Azure 服务总线主题处理发布者/订阅者通信。

一旦您在 Azure 门户上配置了 Azure Service Bus,您就可以连接到它,以便通过Microsoft.Azure.ServiceBusNuGet 包中包含的客户端发送消息/事件和接收消息/事件。

Azure 服务总线有两种类型的通信:基于队列的通信和基于主题的通信。在基于队列的通信中,发送方放入队列中的每条消息都会由从队列中提取消息的第一个接收方从队列中删除。另一方面,基于主题的通信是发布者/订阅者模式的实现。每个主题都有多个订阅,可以从每个主题订阅中提取发送到主题的每条消息的不同副本。

设计流程如下:

  1. 定义 Azure 服务总线专用命名空间。
  2. 获取 Azure 门户创建的根连接字符串和/或定义具有较少权限的新连接字符串。
  3. 定义发件人将以二进制格式发送邮件的队列和/或主题。
  4. 对于每个主题,定义所有必需订阅的名称。
  5. 在基于队列的通信中,发送方向队列发送消息,接收方从同一队列中提取消息。每条消息都会传递给一个接收者。也就是说,一旦接收方获得对队列的访问权,它就会读取并删除一条或多条消息。
  6. 在基于主题的通信中,每个发送者向主题发送消息,而每个接收者从与该主题相关联的私有订阅中提取消息。

Azure Service Bus 还有其他商业替代品,如 NServiceBus、MassTransit、Brighter 和 ActiveMQ。还有一个免费的开源选项:RabbitMQ。RabbitMQ 可以安装在本地、虚拟机或 Docker 容器中。然后,您可以通过RabbitMQ.ClientNuGet 包中包含的客户端与之连接。

RabbitMQ 的功能类似于 Azure Service Bus 提供的功能,但您必须处理所有实现细节、已执行操作的确认等,而 Azure Service Bus 负责所有低级操作,并为您提供更简单的接口。Azure 服务总线和 RabbitMQ 将与基于发布者/订户的通信一起在第 11 章设计模式和.NET 5 实现中描述。

如果微服务发布到 Azure Service Fabric,这将在下一章(第 6 章Azure Service Fabric中进行描述),我们可以使用内置的可靠二进制通信。

通信是有弹性的,因为通信原语自动使用重试策略。这种通信是同步的,但这不是一个很大的限制,因为 Azure Service Fabric 中的微服务具有内置队列;因此,一旦接收方接收到消息,他们就可以将其放入队列并立即返回,而不会阻塞发送方。

然后,队列中的消息由单独的线程处理。这种内置通信的主要限制是它不基于发布者/订阅者模式;发送方和接收方必须相互了解。当这是不可接受的,您应该使用 Azure 服务总线。我们将在第 6 章Azure Service Fabric中学习如何使用 Service Fabric 的内置通信。

弹性任务执行

弹性通信,一般来说,弹性任务执行可以很容易地用 Apple T0 来实现。Polly 可通过 Polly NuGet 软件包获得。

在 Polly 中,您定义策略,然后在这些策略的上下文中执行任务,如下所示:

var myPolicy = Policy
  .Handle<HttpRequestException>()
  .Or<OperationCanceledException>()
  .Retry(3);
....
....
myPolicy.Execute(()=>{
    //your code here
}); 

每个策略的第一部分指定必须处理的异常。然后,指定捕获其中一个异常时要执行的操作。在前面的代码中,如果故障由HttpRequestException异常或OperationCanceledException异常报告,则Execute方法最多重试三次。

以下是指数重试策略的实现:

var erPolicy= Policy
    ...
    //Exceptions to handle here
    .WaitAndRetry(6, 
        retryAttempt => TimeSpan.FromSeconds(Math.Pow(2,
            retryAttempt))); 

WaitAndRetry的第一个参数指定在失败的情况下最多执行六次重试。作为第二个参数传递的 lambda 函数指定在下次尝试之前等待的时间。在这个特定的示例中,此时间随着尝试次数以 2 的幂指数增长(第一次重试为 2 秒,第二次重试为 4 秒,依此类推)。

以下是一个简单的断路器策略:

var cbPolicy=Policy
    .Handle<SomeExceptionType>()
    .CircuitBreaker(6, TimeSpan.FromMinutes(1)); 

六次失败后,由于返回异常,任务在 1 分钟内无法执行。

以下是舱壁隔离政策的实施情况(更多信息请参见微服务设计原则部分):

Policy
  .Bulkhead(10, 15) 

Execute方法最多允许 10 次并行执行。在执行队列中插入进一步的任务。这有 15 个任务的限制。如果超过队列限制,将引发异常。

为了使舱壁隔离策略正常工作,并且通常为了使每个策略正常工作,必须通过相同的策略实例触发任务执行;否则,Polly 无法计算特定任务的活动执行次数。

策略可以与Wrap方法相结合:

var combinedPolicy = Policy
  .Wrap(erPolicy, cbPolicy); 

Polly 提供了更多的选项,例如用于返回特定类型的任务的通用方法、超时策略、任务结果缓存、定义自定义策略的能力,等等。也可以在任何 ASP.NET 和.NET 应用的依赖项注入部分将 Polly 配置为HttPClient定义的一部分。通过这种方式,定义弹性客户机是非常直接的。

有关 Polly 官方文件的链接,请参见进一步阅读部分。

使用通用主机

每个微服务可能需要运行几个独立的线程,每个线程对收到的请求执行不同的操作。这样的线程需要多种资源,例如数据库连接、通信通道、执行复杂操作的专用模块等等。此外,当微服务启动时,所有处理线程都必须充分初始化,当微服务由于负载平衡或错误而停止时,所有处理线程都必须正常停止。

所有这些需求促使.NET 团队构思并实现了托管服务主机。主机为运行多个任务创建适当的环境,称为托管服务,并为提供资源、公共设置和优雅的启动/停止。

web 主机的概念主要是为了实现 ASP.NETCore web 框架,但从.NETCore 2.1 开始,主机概念扩展到了所有.NET 应用。

在撰写本书时,任何 ASP.NET Core 或 Blazor 项目中都会自动为您创建一个Host,因此您只能在其他项目类型中手动添加它。

Host概念相关的所有功能都包含在Microsoft.Extensions.HostingNuGet 包中。

首先,您需要为主机配置一个流畅的界面,从一个HostBuilder实例开始。此配置的最后一步是调用Build方法,该方法使用我们提供的所有配置信息组装实际主机:

var myHost=new HostBuilder()
    //Several chained calls
    //defining Host configuration
    .Build(); 

主机配置包括定义公共资源、定义文件的默认文件夹、从多个源(JSON 文件、环境变量和传递给应用的任何参数)加载配置参数,以及声明所有托管服务。

值得指出的是,ASP.NET Core 和 Blazor 项目使用的方法可以执行Host的预配置,其中包括前面列出的几个任务。

然后,可以启动主机,从而启动所有托管服务:

host.Start(); 

在主机关闭之前,程序将一直被前一条指令阻塞。主机可以通过其中一个托管服务关闭,也可以通过调用awaithost.StopAsync(timeout)从外部关闭。这里,timeout是一个时间跨度,定义了等待托管服务正常停止的最长时间。在此之后,如果所有托管服务尚未终止,则将中止这些服务。

通常,当编排器启动微服务时,传递一个cancellationToken来表示微服务正在关闭。当微服务托管在 Azure 服务结构中时,就会发生这种情况。

因此,在大多数情况下,我们可以使用RunAsyncRun方法,而不是使用host.Start(),可能会将我们从编排器或操作系统收到的cancellationToken传递给它:

await host.RunAsync(cancellationToken) 

一旦cancellationToken进入取消状态,就会触发这种关闭方式。默认情况下,主机有 5 秒的关机超时时间;也就是说,一旦请求关机,它将等待 5 秒后退出。此时间可以在ConfigureServices方法中更改,用于声明托管服务等资源:

var myHost = new HostBuilder()
    .ConfigureServices((hostContext, services) =>
    {
        services.Configure<HostOptions>(option =>
        {
            option.ShutdownTimeout = System.TimeSpan.FromSeconds(10);
        });
        ....
        ....
        //further configuration
    })
    .Build(); 

但是,增加主机超时不会增加编排器超时,因此如果主机等待时间过长,整个微服务将被编排器终止。

如果没有明确地将取消令牌传递给RunRunAsync,则会自动生成取消令牌,并在操作系统通知应用它将终止它时自动发出信号。此取消令牌将传递给所有托管服务,使它们有机会正常停止。

托管服务是IHostedService接口的实现,其唯一方法是StartAsync(cancellationToken)StopAsync(cancellationToken)

两种方法都通过了一个cancellationTokenStartAsync方法中的cancellationToken表示请求关机。StartAsync方法在执行启动主机所需的所有操作时定期检查此cancellationToken,如果发出信号,主机启动过程将中止。另一方面,StopAsync方法中的cancellationToken表示停机超时已过期。

托管服务可以使用与定义主机选项相同的ConfigureServices方法声明,如下所示:

services.AddHostedService<MyHostedService>(); 

但是,有些项目模板,如 ASP.NETCore 项目模板,在不同的类中定义了一个ConfigureServices方法。如果此方法接收到的services参数与HostBuilder.ConfigureServices方法中可用的参数相同,则此方法可以正常工作。

ConfigureServices中的大多数声明都需要添加以下命名空间:

using Microsoft.Extensions.DependencyInjection; 

通常情况下,IHostedService接口不是直接实现的,而是可以从BackgroundService抽象类继承而来,该抽象类公开了更易于实现的ExecuteAsync(CancellationToken)方法,我们可以在该方法中放置服务的整个逻辑。通过将cancellationToken作为参数传递表示关机,这更容易处理。我们将在第 6 章**Azure 服务结构末尾的示例中查看IHostedService的实现。

为了允许托管服务关闭主机,我们需要声明一个IApplicationLifetime接口作为其构造函数参数:

public class MyHostedService: BackgroundService 
{
    private readonly IHostApplicationLifetime applicationLifetime;
    public MyHostedService(IHostApplicationLifetime applicationLifetime)
    {
        this.applicationLifetime=applicationLifetime;
    }
    protected Task ExecuteAsync(CancellationToken token) 
    {
        ...
        applicationLifetime.StopApplication();
        ...
    }
} 

创建托管服务时,会自动传递一个IHostApplicationLifetime的实现,其StopApplication方法会触发主机关闭。这个实现是自动处理的,但我们也可以声明自定义资源,这些资源的实例将自动传递给将它们声明为参数的所有主机服务构造函数。因此,假设我们定义这样一个构造函数:

Public MyClass(MyResource x, IResourceInterface1 y)
{
    ...
} 

有几种方法可以定义前面的构造函数所需的资源:

services.AddTransient<MyResource>();
services.AddTransient<IResourceInterface1, MyResource1>();
services.AddSingleton<MyResource>();
services.AddSingleton<IResourceInterface1, MyResource1>(); 

当我们使用AddTransient时,会创建一个不同的实例,并将其传递给所有需要该类型实例的构造函数。另一方面,使用AddSingleton,将创建一个唯一的实例,并将其传递给所有需要声明类型的构造函数。带有两个泛型类型的重载允许您传递一个接口和一个实现该接口的类型。通过这种方式,构造函数需要接口,并且与该接口的特定实现解耦。

如果资源构造函数包含参数,它们将以递归方式自动实例化为ConfigureServices中声明的类型。这种与资源交互的模式称为依赖注入DI),将在第 11 章设计模式和.NET 5 实现中详细讨论。

HostBuilder还有一种方法可以用来定义默认文件夹,即用于解析所有.NET 方法中提到的所有相对路径的文件夹:

.UseContentRoot("c:\\<deault path>") 

它还有一些方法可用于添加日志记录目标:

.ConfigureLogging((hostContext, configLogging) =>
    {
        configLogging.AddConsole();
        configLogging.AddDebug();
    }) 

前面的示例显示了一个基于控制台的日志记录源,但我们也可以使用足够的提供者登录 Azure 目标。进一步阅读部分包含指向一些 Azure 日志提供商的链接,这些提供商可以使用已部署在 Azure 服务结构中的微服务。一旦配置了日志记录,您就可以启用托管服务并通过在其构造函数中添加ILoggerFactoryILogger<T>参数来记录自定义消息。

最后,HostBuilder提供了从各种来源读取配置参数的方法:

.ConfigureHostConfiguration(configHost =>
    {
        configHost.AddJsonFile("settings.json", optional: true);
        configHost.AddEnvironmentVariables(prefix: "PREFIX_");
        configHost.AddCommandLine(args);
    }) 

从应用内部使用参数的方式将在第 15 章**中详细说明,介绍 ASP.NETCore MVC,专门针对 ASP.NET。

Visual Studio 对 Docker 的支持

VisualStudio 支持创建、调试和部署 Docker 映像。Docker 部署要求我们在开发机器上安装Docker Desktop for Windows,以便运行 Docker 映像。下载链接可在本章开头的技术要求部分找到。在开始任何开发活动之前,我们必须确保它已安装并正在运行(当 Docker 运行时正在运行时,您应该在窗口通知栏中看到 Docker 图标)。

Docker 支持将通过一个简单的 ASP.NET MVC 项目来描述。让我们创建一个。为此,请执行以下步骤:

  1. 将项目命名为MvcDockerTest
  2. 为简单起见,如果尚未禁用身份验证,请禁用身份验证。
  3. 您可以选择在创建项目时添加 Docker 支持,但请不要选中 Docker 支持复选框。您可以测试 Docker 支持在创建任何项目后如何添加到该项目中。

构建并运行 ASP.NET MVC 应用后,右键单击解决方案资源管理器中的项目图标,选择添加,然后选择容器编排器支持****Docker Compose

您将看到一个对话框,要求您选择容器应该使用的操作系统;选择安装Docker Desktop for Windows时选择的同一个。这不仅可以创建 Docker 映像,还可以创建 Docker Compose 项目,这有助于配置 Docker Compose 文件,以便它们同时运行和部署多个 Docker 映像。事实上,如果向解决方案中添加另一个 MVC 项目并启用容器编排器支持,则新的 Docker 映像将添加到同一 Docker Compose 文件中。

启用 Docker Compose 而不仅仅是Docker的优点是,您可以手动configure如何在开发机器上运行映像,以及如何通过编辑添加到解决方案中的 Docker Compose 文件将 Docker 映像端口映射到外部端口。

如果 Docker 运行时已正确安装并正在运行,则应该能够从 Visual Studio 运行 Docker 映像。

分析 Docker 文件

让我们分析一下 VisualStudio 创建的 Docker 文件。这是一系列图像创建步骤。在From指令的帮助下,每一步都会用其他内容丰富现有图像,这是对现有图像的引用。以下是第一步:

FROM mcr.microsoft.com/dotnet/aspnet:x.x AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443 

第一步使用 Microsoft 在 Docker 公共存储库中发布的mcr.microsoft.com/dotnet/aspnet:x.xASP.NET(核心)运行时(其中x.x是在项目中选择的 ASP.NET(核心)版本)。

WORKDIR命令在将要创建的映像中创建跟随该命令的目录。如果该目录尚不存在,则会在映像中创建该目录。两个EXPOSE命令声明映像端口中的哪些端口将暴露在映像外部并映射到实际主机。映射端口在部署阶段作为 Docker 命令的命令行参数或 Docker Compose 文件中确定。在我们的例子中,有两个端口:一个用于 HTTP(80),另一个用于 HTTPS(443)。

这个中间映像由 Docker 缓存,Docker 不需要重新计算它,因为它不依赖于我们编写的代码,而只依赖于所选的 ASP.NET(核心)运行时版本。

第二步生成不用于部署的不同映像。相反,它将用于创建要部署的特定于应用的文件:

FROM mcr.microsoft.com/dotnet/core/sdk:x  AS build
WORKDIR /src
COPY ["MvcDockerTest/MvcDockerTest.csproj", "MvcDockerTest/"]
RUN dotnet restore MvcDockerTest/MvcDockerTest.csproj
COPY . .
WORKDIR /src/MvcDockerTest
RUN dotnet build MvcDockerTest.csproj -c Release -o /app/build
FROM build AS publish
RUN dotnet publish MvcDockerTest.csproj -c Release -o /app/publish 

此步骤从 ASP.NET SDK 映像开始,其中包含我们不需要添加以进行部署的部分;这些是处理项目代码所必需的。在build映像中创建新的src目录,并生成当前映像目录。然后,将项目文件复制到/src/MvcDockerTest

RUN命令对映像执行操作系统命令。在本例中,它调用dotnet运行时,要求它还原先前复制的项目文件引用的 NuGet 包。

然后,COPY..命令将整个项目文件树复制到src映像目录中。最后,将项目目录设置为当前目录,并要求dotnet运行时以发布模式构建项目,并将所有输出文件复制到新的/app/build目录中。最后,在名为publish的新图像中执行dotnet publish任务,将发布的二进制文件输出到/app/publish

最后一步从我们在第一步中创建的映像开始,该映像包含 ASP.NET(Core)运行时,并添加在前一步中发布的所有文件:

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "MvcDockerTest.dll"] 

ENTRYPOINT命令指定执行映像所需的操作系统命令。它接受一个字符串数组。在我们的例子中,它接受dotnet命令及其第一个命令行参数,即我们需要执行的 DLL。

发布项目

如果我们在项目上右键点击并点击发布,我们会看到几个选项:

  • 将图像发布到现有或新的 web 应用(由 Visual Studio 自动创建)
  • 发布到多个 Docker 注册中心之一,包括一个私有 Azure 容器注册中心,如果该注册中心不存在,则可以从 Visual Studio 中创建

Docker Compose 支持允许您运行和发布多容器应用,并添加更多图像,例如随处可用的容器化数据库。

以下 Docker Compose 文件将两个 ASP.NET 应用添加到同一 Docker 映像中:

version: '3.4'
services:
  mvcdockertest:
    image: ${DOCKER_REGISTRY-}mvcdockertest
    build:
      context: .
      dockerfile: MvcDockerTest/Dockerfile
  mvcdockertest1:
    image: ${DOCKER_REGISTRY-}mvcdockertest1
    build:
      context: .
      dockerfile: MvcDockerTest1/Dockerfile 

前面的代码引用了现有的 Docker 文件。任何与环境相关的信息都放在docker-compose.override.yml文件中,当从 Visual Studio 启动应用时,该文件与docker-compose.yml文件合并:

version: '3.4'
services:
  mvcdockertest:
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
      - ASPNETCORE_URLS=https://+:443;http://+:8 
    ports:
      - "3150:80"
      - "44355:443"
    volumes:
      - ${APPDATA}/Asp.NET/Https:/root/.aspnet/https:ro
  mvcdockertest1:
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
      - ASPNETCORE_URLS=https://+:443;http://+:80
      - ASPNETCORE_HTTPS_PORT=44317
    ports:
      - "3172:80"
      - "44317:443"
    volumes:
      - ${APPDATA}/Asp.NET/Https:/root/.aspnet/https:ro 

对于每个映像,该文件定义了一些环境变量(启动应用时将在映像中定义)、端口映射和一些主机文件。

主机中的文件直接映射到图像中。每个声明都包含主机中的路径、路径在映像中的映射方式以及所需的访问权限。在我们的例子中,volumes用于映射 VisualStudio 使用的自签名 HTTPS 证书。

现在,假设我们要添加一个容器化 SQL Server 实例。我们需要在docker-compose.ymldocker-compose.override.yml之间拆分如下说明:

sql.data:
  image: mssql-server-linux:latest
  environment:
  - SA_PASSWORD=Pass@word
  - ACCEPT_EULA=Y
  ports:
  - "5433:1433" 

在这里,前面的代码指定了 SQL Server 容器的属性,以及 SQL Server 的配置和安装参数。更具体地说,上述代码包含以下信息:

  • sql.data是容器的名称。
  • image指定从何处获取图像。在我们的例子中,图像包含在公共 Docker 注册表中。
  • environment指定 SQL Server 需要的环境变量,即管理员密码和接受 SQL Server 许可证。
  • 通常,ports指定端口映射。
  • docker-compose.override.yml用于从 Visual Studio 中运行图像。

如果您需要为生产环境或测试环境指定参数,您可以进一步添加docker-compose-xxx.override.yml文件,如docker-compose-staging.override.ymldocker-compose-production.override.yml,然后在目标环境中手动启动,代码如下:

docker-compose -f docker-compose.yml -f docker-compose-staging.override.yml 

然后,您可以使用以下代码销毁所有容器:

docker-compose -f docker-compose.yml -f docker-compose.test.staging.yml down 

尽管docker-compose在处理节点集群时能力有限,但它主要用于测试和开发环境。对于生产环境,需要更复杂的工具,我们将在本章后面看到(在*中需要哪些工具来管理微服务?*部分)。

Azure 和 Visual Studio 对微服务编排的支持

VisualStudio 为基于服务结构平台的微服务应用提供了特定的项目模板,您可以在其中定义各种微服务,配置它们,并将它们部署到 Azure 服务结构,后者是一个微服务编排器。Azure 服务结构将在第 6 章Azure 服务结构中详细描述。

VisualStudio 还具有特定的项目模板,用于定义要部署在 Azure Kubernetes 中的微服务,并具有用于在与 Azure Kubernetes 中部署的其他微服务通信时调试单个微服务的扩展。

此外,还提供了一些工具,用于在开发机器中测试和调试多个通信微服务,而无需安装任何 Kubernetes 软件,以及在 Azure Kubernetes 上自动部署它们,只需提供最少的配置信息。

Azure Kubernetes 的所有 Visual Studio 工具将在第 7 章Azure Kubernetes 服务中描述。

管理微服务需要哪些工具?

有效地处理 CI/CD 周期中的微服务需要一个专用 Docker 映像注册表和一个能够执行以下操作的最先进的微服务编排器:

  • 在可用硬件节点上分配和负载平衡微服务
  • 监控服务的运行状况,并在发生硬件/软件故障时更换故障服务
  • 日志记录和显示分析
  • 允许设计器动态更改需求,例如分配给集群的硬件节点、服务实例的数量等

下面的小节描述了我们可以用来存储 Docker 映像的 Azure 设施。Azure 中可用的微服务编排器在专门的章节中分别进行了描述,即第 6 章Azure 服务结构第 7 章Azure Kubernetes 服务

在 Azure 中定义您的私有 Docker 注册表

在 Azure 中定义您的私有 Docker 注册表非常简单。只需在 Azure 搜索栏中键入Container registries并选择容器注册。在出现的页面上,单击添加按钮。

将出现以下表格:

![](img/B16756_05_01.png)

图 5.1:创建 Azure 私有 Docker 注册表

您选择的名称用于组成整个注册表 URI:<name>.azurecr.io。通常,您可以指定订阅、资源组和位置。通过SKU下拉列表,您可以从不同级别的产品中进行选择,这些产品在性能、可用内存和其他一些辅助功能方面都有所不同。

无论何时在 Docker 命令或 Visual Studio 发布表单中提及图像名称,都必须在它们前面加上注册表 URI:<name>.azurecr.io/<my imagename>

如果图像是使用 Visual Studio 创建的,则可以按照发布项目后显示的说明进行发布。否则,您必须使用docker命令将它们推入注册表。

使用与 Azure 注册表交互的 Docker 命令的最简单方法是在计算机上安装 Azure CLI。从下载安装程序 https://aka.ms/installazurecliwindows 并执行它。安装 Azure CLI 后,您可以从 Windows 命令提示符或 PowerShell 使用az命令。为了连接 Azure 帐户,您必须执行以下登录命令:

az login 

此命令应启动默认浏览器,并引导您完成手动登录过程。

登录到 Azure 帐户后,您可以通过键入以下命令登录到您的私人注册表:

az acr login --name {registryname} 

现在,假设您在另一个注册表中有一个 Docker 映像。首先,让我们在本地计算机上提取图像:

docker pull other.registry.io/samples/myimage 

如果前面的图像有多个版本,则将提取最新版本,因为未指定任何版本。可以按如下方式指定图像的版本:

docker pull other.registry.io/samples/myimage:version1.0 

使用下面的命令,您应该可以在本地图像列表中看到myimage

docker images 

然后,在图像上标记您要在 Azure 注册表中分配的路径:

docker tag myimage myregistry.azurecr.io/testpath/myimage 

名称和目的地标签可能都有版本(:<version name>

最后,使用以下命令将其推送到注册表:

docker push myregistry.azurecr.io/testpath/myimage 

在这种情况下,您可以指定一个版本;否则,将推送最新版本。

通过执行此操作,您可以使用以下命令从本地计算机删除图像:

docker rmi myregistry.azurecr.io/testpath/myimage 

总结

在本章中,我们描述了什么是微服务,以及它们是如何从模块的概念演变而来的。然后,我们讨论了微服务的优势以及何时值得使用它们,以及它们设计的一般标准。我们还解释了什么是 Docker 容器,并分析了容器与微服务架构之间的紧密联系。

然后,我们通过描述.NET 中可用的所有工具来实现一个更实际的实现,这样我们就可以实现基于微服务的架构。我们还描述了微服务所需的基础架构,以及 Azure 集群如何提供 Azure Kubernetes 服务和 Azure 服务结构。

下一章将详细讨论 Azure 服务结构编排器。

问题

  1. 模块概念的双重性质是什么?
  2. 缩放优化是微服务的唯一优势吗?如果没有,请列出一些进一步的优点。
  3. 波利是什么?
  4. Visual Studio 提供了哪些 Docker 支持?
  5. 什么是 orchestrator?Azure 上有哪些 orchestrator?
  6. 为什么基于发布者/订阅者的通信在微服务中如此重要?
  7. 什么是兔子?
  8. 为什么幂等信息如此重要?

进一步阅读

以下是 Azure 服务总线和 RabbitMQ 这两种事件总线技术的官方文档链接: