应用程序和微服务——您需要了解的内容:自主性和您将面临的挑战。
我将要讨论的很多内容将参考微软这份令人惊叹的长达 289 页的巨著,可以从这里下载:
我会分享博客文章、视频以及我找到的任何有趣的白皮书的链接。同时,我也会研究其他人如何创建或将之前的单体应用转换为多个松散耦合的微服务。
正如任何如此宏大且引人入胜的作品一样,本文将涵盖我整个创作过程以及我的学习心得。如果您对本文内容有任何其他建议或想法,欢迎在下方留言,我会根据需要进行修改。
那么让我们开始吧。
什么是微服务?
首先我们需要简单了解一下什么是服务,什么是系统?
从软件角度来说,服务是:
- 由一个组织拥有、建造和运营
- 负责在系统范围内保存、处理和/或分发特定类型的数据
- 可以独立构建、部署和运行,满足定义的运营目标
- 与消费者和其他服务进行沟通,使用惯例和/或合同保证提供信息
- 处理故障情况,使故障不会导致信息损坏
- 保护自身免受不必要的访问,并防止信息丢失
一组服务危害系统
- 系统是一组服务和系统,旨在为明确定义的范围提供组合解决方案。
- 一个系统可能会出现并作为对其他方的服务。
- 系统可以共享服务
- 消费者可能与多个系统交互
服务并非一组特定的语言或框架,而是这些服务的组合,用于提供解决方案。它不仅仅是 Linux 容器上 Docker 实例中用 .Net、JavaScript 或任何其他脚本语言编写的 API。它与实现方式无关。
那么,服务和微服务之间有什么区别呢?微服务必须能够独立部署,而 SOA 服务 通常以单体式部署的方式实现。SOA 是一种架构模式,其中应用程序组件可以向其他组件提供 服务 。然而,在 SOA 中,这些组件可以属于同一个应用程序。
对于微服务,最重要的是要记住它们是“自主的”
这意味着:
- 服务拥有它直接依赖和管理的所有状态
- 服务拥有其通信合同
- 服务可以更改、重新部署和/或完全替换
- 服务有一组众所周知的通信路径
- 服务不得与其他服务共享状态
- 不要依赖或假设任何通用数据存储
- 不依赖任何共享内存状态
- 服务之间无需进行旁路通信
- 所有沟通都是明确的
归根结底,我们要记住,Autonomy 的核心在于跨组织协作。它的理念是,任何一项服务都不能与其他服务耦合!
分布式数据管理的挑战与解决方案
定义边界
设计微服务架构时,您面临的第一个挑战是如何将当前设置分离成独立的模块。您需要定义每个微服务的边界,以识别同一应用程序中解耦的数据点和上下文。目标并非实现尽可能精细的分离,而是实现领域所需的最有意义的分离。如果您定义了边界,然后发现存在大量依赖项,则可能需要重新定义。
重点不在于规模,而在于业务能力。
你应该基于有界上下文模式来定义你的边界。为了识别这些边界, 可以使用领域驱动设计 (DDD)或上下文映射模式。
跨多个微服务的一致性
由于每个微服务都与其他微服务分离,因此保持多个微服务之间的数据一致性是一项挑战。
上文中我们可以看到,当一个微服务中的价格发生变化时,我们必须将该变化传播到其他微服务。通常,这需要通过连接或 API 调用来反映,但由于每个微服务都有自己的私有数据库,因此我们需要使用基于异步通信(例如集成事件)的最终一致性 。
分布式系统通信和数据检索
由于可能有许多解耦的微服务单独运行,因此选择正确的解决方案以最佳方式从这些微服务中查询数据是一项挑战。
在线阅读并使用页面顶部附加的 PDF 时,会发现在创建微服务时, API 网关被大量提及,作为一种将微服务组合在一起的方式。虽然这是一种简单的方法,但它从根本上违背了微服务的核心原则。
首先,我将简要介绍一下ESB(企业服务总线)的历史以及它是什么,然后再讨论Api 网关 以及它们的相似之处。
什么是/曾经是 ESB(企业服务总线)
ESB 模型承诺通过集中化实现简化
实体服务总线承诺通过一个中心点来处理所有事情,从而使系统更加简化,在较小的应用程序中,这种模式有优势,然而在规模上,ESB 的许多优势实际上成为了其最大的弱点。
ESB 最终会成为瓶颈,因为它与系统的每个部分紧密耦合,难题在于规模、移动客户端和大量服务,没有人拥有集中区域,每次更新、升级或完全更改其中一个服务时都需要进行更改,这可能会导致停机,并影响整个系统!
什么是 Api 网关
API 网关也陷入了同样的陷阱。它集中处理多个微服务。显然,它不像 ESB 模型那样庞大,但在网关内更改微服务需要同时更改服务和网关。
如果需要使用 API 网关,则应根据业务边界进行隔离,而不是充当整个应用程序的聚合器。
API 网关模式的缺点
- 最大的缺点是,当你实现 API 网关时,你会将该层与内部微服务耦合在一起。这种耦合可能会给你的应用程序带来严重的问题。(云架构师 Clemens Vaster 在 GOTO 2016 的“消息传递和微服务”会议中将这种潜在的问题称为“新的 ESB”,如下所示。)
- 使用微服务 API 网关会产生额外的可能故障点。
- 如果 API 网关不能正确扩展,则可能会成为瓶颈
- 如果 API 网关包含自定义逻辑和数据聚合,则需要额外的开发成本和未来的维护。开发人员必须更新 API 网关才能公开每个微服务的终结点。此外,内部微服务中的实现更改可能会导致 API 网关级别的代码更改。但是,如果 API 网关仅应用安全性、日志记录和版本控制(例如使用 Azure API 管理时),则可能不需要这笔额外的开发成本。
前面提到的Clemens Vaser主持的这场精彩会议探讨了微服务,并指出API网关并非良策。他指出,我们很容易陷入ESB带来的陷阱,并强烈反对使用ESB。我还在附录中附上了他的演讲幻灯片。
https://www.youtube.com/watch?v=rXi5CLjIQ9k
在单进程运行的单体应用中,模块之间通过方法或函数调用相互调用。这些模块之间可以通过诸如 new Class () 之类的代码实现强耦合,也可以通过依赖注入实现松耦合。
无论哪种方式,对象都在同一个进程中运行。从单体应用转变为基于微服务的应用,最大的挑战在于改变通信机制。
将进程内方法调用直接转换为远程进程服务调用将导致通信效率低下,在分布式环境中无法很好地执行。
正确设计分布式系统的挑战已得到充分证实;程序员做出的假设可能会导致问题。这些假设由 L Peter Deutsch撰写,内容如下:
- 网络可靠。
- 延迟为零。
- 带宽是无限的。
- 网络是安全的。
- 拓扑结构没有改变。
- 有一名管理员。
- 运输成本为零。
- 网络是同质的。
请注意,以上列出的示例均不属实,您不应误以为这些示例不值得考虑,它们会自行解决。所有这些问题都需要代码覆盖率来处理,确保代码能够处理网络中断、数据包丢失、安全协议更改等情况。
您可以在此处阅读更多关于这些谬论的内容:分布式系统的 8 个谬论
解决方案不止一个,而是有多个。
AMQP、HTTP 或 TCP
基于微服务的应用程序是一个运行在多个进程或服务上的分布式系统,通常跨多个服务器或主机。每个服务通常是一个进程。因此,服务必须使用进程间通信协议(例如 HTTP、AMQP)或二进制协议(例如 TCP)进行交互,具体取决于每个服务的性质。
智能端点和哑管道
是一个让人联想到微服务的术语,是一种利用复杂系统拓扑上的异步通信机制进行尝试和测试的设计原则。
一种尽可能解耦的设计,通过使用RESTFUL和灵活的事件驱动通信而不是集中式编排器来编排。
最常见的两种方法是使用带有 API 的 HTTP/S 消息。这是一种同步协议,客户端发送请求并等待响应。这通常使用命令模式 ,每个请求由一个服务处理。
另一种是轻量级异步消息传递协议,例如AMQP。客户端代码或消息无需等待响应,只需将消息发送到RabbitMQ等代理,然后由单独的进程进行处理。发布/订阅方法就是这样一种利用多个接收器的机制,因为异步特性意味着每个请求可以由一个或多个服务实例处理。另一种是事件驱动通信,通常使用服务总线实现,可用的选项包括MassTransit、使用队列的NServiceBus 或使用主题和订阅的 Azure 服务总线。
这两种方法的比较可以在这里找到: 队列与主题和订阅。
服务之间的通信
您应该尽量减少微服务之间的通信。
这些微服务之间的通信越少越好。当然,有时你需要集成微服务。这里的关键规则是,通信应该是异步的,并通过异步传播数据来执行,而不是依赖于其他内部微服务作为初始 HTTP 请求/响应的一部分。
你永远不应该依赖微服务之间的同步通信,目标是每个微服务都保持自主性,并且即使应用程序中的其他服务离线或损坏,也能供客户端使用。这意味着你的架构必须足够灵活,能够应对某些服务故障。
同步操作将影响性能。微服务之间添加的同步依赖项(例如查询请求)越多,客户端的整体响应时间就会越差。
微服务版本控制、发现和服务注册
微服务模型的好处是以增加操作复杂性为代价的,这是由于平台异构性、服务发现的需求以及消息传递和 API 调用量等原因造成的。
服务 API 需要随着时间推移而变化。当这种情况发生时,尤其是当它是一个被多个应用程序使用的公共 API 时,您不能强制采用新的契约。必须制定版本控制策略,以确保新旧版本都能同时供用户使用。
中介模式可以将您的实现拆分为独立的处理程序,而超媒体非常适合对您的服务进行版本控制。
无论微服务在何处运行,都需要解析其 URL,因此需要服务注册表。正如 DNS 解析地址一样,您的服务也需要一个唯一的名称才能被发现。服务注册表模式是保持端点和服务实例更新的关键。