微服务架构设计模式之进程间通信

本文讨论了微服务架构中的交互方式,包括一对一和一对多模式,以及同步与异步的运用。重点介绍了RESTfulAPI的设计与优劣,探讨了断路器模式在处理局部故障中的作用,服务发现的不同层次以及消息队列的选择和挑战。此外,还提及了如何通过数据库表实现事务性消息的发布和同步交互的消除策略。

阅读《微服务架构设计模式》读书笔记

交互方式

有多种客户端与服务的交互方式。它们可以分为两个维度。
第一个维度
关注的是一对一和一对多。

  • 一对一:每个客户端请求由一个服务实例来处理。
  • 一对多:每个客户端请求由多个服务实例来处理。

第二个维度关注的是同步和异步。

  • 同步模式:客户端请求需要服务端实时响应,客户端等待响应时可能导致堵塞。
  • 异步模式:客户端请求不会阻塞进程,服务端的响应可以是非实时的。

在这里插入图片描述

一对一的交互方式有以下几种类型。

  • 请求/响应:一个客户端向服务端发起请求,等待响应;客户端期望服务端很快就会发送响应。在一个基于线程的应用中,等待过程可能造成线程阻塞。这样的方式会导致服务的紧耦合。
  • 异步请求/响应:客户端发送请求到服务端,服务端异步响应请求。客户端在等待响应时不会阻塞线程,因为服务端的响应不会马上就返回。
  • 单向通知:客户端的请求发送到服务端,但是并不期望服务端做出任何响应。

一对多的交互方式有以下几种类型。

  • 发布/订阅方式:客户端发布通知消息,被零个或者多个感兴趣的服务订阅。
  • 发布/异步响应方式:客户端发布请求消息,然后等待从感兴趣的服务发回的响应。

发布/异步响应交互方式是一种更高级别的交互方式,它通过把发布/订阅和请求/响应这两种方式的元素组合在一起实现。客户端发布一条消息,在消息的头部中指定回复通道,这个通道同时也是一个发布-订阅通道。消费者将包含相关性ID的回复消息写人回复通道。客户端通过使用相关性ID来收集响应,以此将回复消息与请求进行匹配。

使用REST

如今开发者非常喜欢使用RESTful风格来开发API。REST是一种(总是)使用HTTP协议的的进程间通信机制。
REST中的一个关键概念是资源,它通常表示单个业务对象,例如客户或产品,或业务对象的集合。REST使用HTTP动词来操作资源,使用URL引用这些资源。例如,GET请求返回资源,POST请求创建新资源,PUT请求更新资源。例如,Order Service具有用于创建Order的POST/order端点以及用于检索Order的的GET/orders/{orderId}端点。

REST的好处和弊端

REST有如下好处:
它非常简单,并且大家都很熟悉。
可以使用浏览器扩展(比如Postman插件)或者curl之类的的命令行(假设使用的是
JSON或其他文本格式)来测试HTTPAPI。
直接支持请求/响应方式的通信。
HTTP对防火墙友好。
不需要中间代理,简化了系统架构。

它也存在一些弊端:
它只支持请求/响应方式的通信。
可能导致可用性降低。由于客户端和服务直接通信而没有代理理来缓冲消息,因此它们必须在RESTAPI调用期间都保持在线。
客户端必须知道服务实例的位置(URL)
在单个请求中获取多个资源具有挑战性。
有时很难将多个更新操作映射到HTTP动词。

使用断路器模式处理局部故障

分布式系统中,当服务试图向另一个服务发送同步请求时,永远都面临着局部故障的风险。因为客户端和服务端是独立的进程,服务端很有可能无法在有限的时间内对客户端的请求做出响应。服务端可能因为故障或维护的原因而暂停,或者服务端也可能因为过载而对请求的响应变得极其缓慢。客户端等待响应被阻塞,这可能带来的麻烦就是在其他客户端甚至使用服务的第三方应用之间传导,并导致服务中断。

断路器一个远程过程调用的代理,在连续失败次数超过指定阔值后的一段时间内,这个代理会立即拒绝其他调用。
断路器必备2个能力:

  • 必须让远程过程调用代理有正确处理无响应服务的能力。
    1. 网络超时:在等待针对请求的响应时,一定不要做成无限阻塞,而是要设定一个超时。使用超时可以保证不会一直在无响应的请求上浪费资源。
    2. 限制客户端向服务器发出请求的数量,把客户端能够向特定服务发起的请求设置一个上限,如果请求达到了这样的上限,很有可能发起更多的请求也无济于事,这时就应该让请求立刻失败。
    3. 断路器模式:监控客户端发出请求的成功和失败数量,如果失败的比例超过一定的阈值,就启动断路器,让后续的调用立刻失效。如果大量的请求都以失败而告终,这说明被调服务不可用,这样即使发起更多的调用也是无济于事。在经过一定的时间后,客户端应该继续尝试,如果调用成功,则解除断路器。
  • 需要决定如何从失败的远程服务中恢复。
    一种选择是服务只是向其客户端返回错误。
    在其他情况下,返回备用值(fallback value,例如默认值或缓存响应)可能会有意义。
    在这里插入图片描述

服务发现

应用层服务发现

如Eureka
实现服务发现的一种方法是应用程序的服务及其客户端与服务注册表进行交互。下图显示了它的工作原理。服务实例使用服务注册表注册其网络位置。客户端首先通过查询服务注册表获取服务实例列表来调用服务,然后它向其中一个实例发送请求。

在这里插入图片描述
这种服务发现方法是两种模式的组合。
第一种模式是自注册模式,服务实例向服务注册表注册自己。服务实例调用服务注册表的注册API来注册其网络位置。它还可以提供运行状况检查。URL运行状况检查URL是一个API端点,服务注册表会定期调用该端点来验证服务实例是否正常且可用于处理请求。服务注册表还可能要求服务实例定期调用"心跳"API以防止其注册过期。
第二种模式是客户端发现模式。当客户端想要调用服务时,它会查询服务注册表以获取服务实例的列表。为了提高性能,客户端可能会缓存服务实例。然后,客户端使用负载平衡算法(例如循环或随机)来选择服务实例。然后它向选择的服务实例发出请求。

应用层服务发现的一个好处是它可以处理多平台部署的问题(服务发现机制与具体的部署平台无关)。
应用层服务发现的一个弊端是你需要为你使用的每种编程语言(可能还有框架)提供服务发现库。

平台层服务发现

许多现代部署平台(如Docker和Kubernetes)都具有内置的服务注册表和服务发现机制。部署平台为每个服务提供DNS名称、虚拟IP(VIP)地址和解析为VIP地址的DNS名称。客户端向DNS名称和VIP发出请求,部署平台自动将请求路由到其中一个可用服务实例。因此,服务注册、服务发现和请求路由完全由部署平台处理。
在这里插入图片描述
这种方式是以下两种模式的组合:
第三方注册模式:由第三方负责(称为注册服务器,通常是部署平台的一部分)处理注册,而不是服务本身向服务注册表注册自己。
服务端发现模式:客户端不再需要查询服务注册表,而是向DNS名称发出请求,对该DNS名称的请求被解析到路由器,路由器查询服务注册表并对请求进行负载均衡。

由平台提供服务发现机制的主要好处是服务发现的所有方面都完全由部署平台处理。服务和客户端都不包含任何服务发现代码。因此,无论使用哪种语言或框架,服务发现机制都可供所有服务和客户使用。
平台提供服务发现机制的一个弊端是它仅限于支持使用该平台部署的服务。例如,基于Kubernetes的发现仅适用于在Kubernetes上运行的服务。

消息队列

选择消息代理时,你需要考虑以下各种因素:
支持的编程语言:你选择的消息代理应该支持尽可能多的的编程语言。
支持的消息标准:消息代理是否支持多种消息标准,比如AMQP和STOMP,还是它仅支持专用的消息标准?
消息排序:消息代理是否能够保留消息的排序?
投递保证:消息代理提供什么样的消息投递保证?
持久性:消息是否持久化保存到磁盘并且能够在代理崩溃时恢复?
耐久性:如果接收方重新连接到消息代理,它是否会收到到断开连接时发送的消息?
可扩展性:消息代理的可扩展性如何?
延迟:端到端是否有较大延迟?
竞争性(并发)接收方:消息代理是否支持竞争性接收方?

现在让我们来看看基于代理的消息的好处和弊端。

好处和弊端

使用消息有以下很多好处。

  • 松耦合:客户端发起请求时只要发送给特定的通道即可,客户端完全不需要感知服务实例的情况,客户端不需要使用服务发现机制去获得服务实例例的网络位置
  • 消息缓存:消息代理可以在消息被处理之前一直缓存消息。像HTTP这样的同步请求响应协议,在交换数据时,发送方和接收方必须同时在线。然而,在使用消息机制的情况下,消息会在队列中缓存,直到它们被接收方处理。这就意味着,例如,即使订单处理系统暂时离线或不可用,在线商店仍旧能够接受客户的订单。订单消息将会在队列中缓存(并不会丢失)
  • 灵活的通信:消息机制支持前面提到的所有交互方式。
  • 明确的进程间通信:基于RPC的机制总是企图让远程服务调用跟本地调用看上去没什么区别(在客户端和服务端同时使用远程调用代理)。然而,因为物理定律(如服务器不可预计的硬件失效)和可能的局部故障,远程和本地证周用还是大相径庭的。消息机制让这些差异变得很明确,这样程序员不会陷入一种"太平盛世"的错觉。

然而,消息机制也有如下一些弊端。

  • 潜在的性能瓶颈:消息代理可能存在性能瓶颈。幸运的是,许多现代消息代理都支持高度的横向扩展。
  • 潜在的单点故障:消息代理的高可用性至关重要,否则系统整体的可靠性将受到影响。幸运的是,大多数现代消息代理都是高可用的。
  • 额外的操作复杂性:消息系统是一个必须独立安装、配置利运维的系统组件。

消息队列设计难题

顺序消息

现代消息代理使用的常见解决方案是使用分片(分区)通道。下图展示了这是如何工作的。
该解决方案分为三个部分。

  1. 分片通道由两个或多个分片组成,每个分片的行为类似于一个通道。
  2. 发送方在消息头部指定分片键,通常是任意字符串或字节序列。消息代理使用分片键将消息分配给特定的分片。例如,它可以通过计算分片键的散数列来选择分片。
  3. 消息代理将接收方的多个实例组合在一起,并将它们视为相同的逻辑接收方。例如,Apache Kafka使用术语消费者组。消息代理将每个分片分配给单个接收器。它在接收方启动和关闭时重新分配分片。
    在这里插入图片描述
重复消息

使用消息机制时必须解决的另一个挑战是处理重复消息。理想情况下,消息代理应该只传递一次消息,但保证有且仅有一次的消息传递通常成本很高。相反,大多数消息代理承诺至少成功传递一次消息。

处理重复消息有以下两种不同的方法。

  • 编写幂等消息处理程序。
    如果应用程序处理消息的逻辑是满足幂等9的,那么重复的消息就是无害的。所谓应用程序的幂等性,是指即使这个应用被相同输入参数多次重复调用时,也不会产生额外的效果。例如,取消一个已经被取消的订单,就是一个幂等性操作。同样,创建一个已经存在的订单操作也必是这样。满足幂等的消息处理程序可以被放心地执行多次(而不会引起错误的结果)只要消息代理在重新传递消息时保持相同的消息顺顺序。
  • 跟踪消息并丢弃重复项。
    一个简单的解决方案是消息接收方使用message id跟踪它已处理的消息并丢弃任何重复项。例如,它可以存储它在数据库表中使用的每条消息的message id。下图显示了如何使用专用表执行此操作。
    在这里插入图片描述
    当接收方处理消息时,它将消息的message id作为创建和更新业务实体的事务的一
    部分记录在数据库表中。在此示例中,接收方将包含message id的行插人 PROCESSED
    MESSAGES表。如果消息是重复的,则INSERT将失败,接收方可以选择丢弃该消息。
    另一个选项是消息处理程序在应用程序表,而不是专用表中记录message id。当使用
    具有受限事务模型的NoSQL数据库时,此方法特别有用,因为NoSQL数据库通常不支持将
    针对两个表的更新作为数据库的事务
事务性消息

服务通常需要在更新数据库的事务中发布消息。例如,在本书中,你将看到在创建或更新业务实体时发布领域事件的例子。数据库更新和消息发送都必须在事务中进行。否则,服务可能会更新数据库,然后在发送消息之前崩溃。如果服务不以原子方式执行这两个操作,则类似的故障可能使系统处于不一致状态。

  • 使用数据库表作为消息队列
    我们假设你的应用程序正在使用关系型数据库。可靠地发布消消息的直接方法是应用事务性发件箱模式。此模式使用数据库表作为临时消息队列。下图所示,发送消息的服务有一个OUTBOX数据库表。作为创建、更新和删除业务对象的数据库事务的一部分,服务通过将消息插入到OUTBOX表中来发送消息。这样可以保证原子性,因为这是本地的ACID事务。
    在这里插入图片描述
    OUTBOX表充当临时消息队列。MessageRelay是一个读取OUTBOX表并将消息发布到消息代理的组件。

将消息从数据库移动到消息代理并对外发送有两种不同的方法去。我们来逐一分析。
通过轮询模式发布事件
轮询数据库是一种在小规模下运行良好的简单方法。其弊端是经常轮询数据库可能造成昂贵的开销(导致数据库性能下降)。

使用事务日志拖尾模式发布事件
更加复杂的实现方式,是让MessageRelay拖尾数据库的事务日志文件(也称为提交日志)。每次应用程序提交到数据库的更新都对应着数据库事务日志中的一个条目。事务日志挖掘器可以读取事务日志,把每条跟消息有关的记录发送给消息代理。下图展示了这个方案的具体实现方式。
在这里插入图片描述

Transaction-Log-Miner读取事务日志条目。它将对应于插入消息的每个相关日志条目转换为消息,并将该消息发布到消息代理。此方法可用于发布写与人关系型数据库中的OUTBOX表的消息或附加到NoSQL数据库中的记录的消息。

消除同步交互

让服务暂缓与其他服务交互,直到它给客户端发送了响应。接下来我们将看看它是如何工作的。
先返回响应,再完成处理
另外一种在请求处理环节消除同步通信的办法如下:
1.仅使用本地的数据来完成请求的验证。
2.更新数据库,包括向OUTBOX表插入消息。
3.向客户端返回响应。
当处理请求时,服务并不需要与其他服务直接进行同步交互。取而代之的是,服务异步向其他的服务发送消息。这种方式确保了服务之间的松耦合。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值