《微服务架构设计模式》阅读笔记(二)

第3章 微服务架构中的进程通信

3.1 微服务架构中的进程间通信概述

3.1.1 交互方式

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

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

交互方式的第二个维度关注的是同步和异步:

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

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

  • 请求/响应:一个客户端向服务端发起请求,等待响应;客户端期望服务端很快就会发送响应。这样的方式会导致服务的紧耦合。
  • 异步请求/响应:客户端发送请求到服务端,服务端异步响应请求。
  • 单向通知:客户端的请求发送到服务端,但是并不期望服务端做出任何响应。

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

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

3.1.2 在微服务架构中定义API

服务的API是服务于客户端之间的契约。
相比单体架构,我们面临的挑战在于:并没有一个简单的编程语言结构可以用来构造和定义服务的API。根据定义,服务和他的客户端并不会一起编译。如果使用不兼容的API部署新版本服务,虽然在编译阶段不会出现错误,但是会出现运行时故障。

3.1.3 API的演化

在基于微服务架构的应用中改变服务的API就没那么容易了。服务的客户端可能是另外的服务于,通常是其他团队所开发的。客户端也极有可能是由组织之外的人所开发和控制的。你不能够强行要求客户端跟服务端的API版本保持一致。另外,由于现在应用程序有着极高的可用性要求,你一般会采用滚动升级的方式来更新服务,因此一个服务的旧版本和新版本肯定会共存。

语义化版本控制
语义化版本控制规范为API版本控制提供了有用的指导.它是一组规则,用于指定如何使用版本号,并且以正确的方式递增版本号。
语义化版本控制要求版本由三部分组成:MAJOR.MINORR.PATCH。必须按如下方式递增版本号:

  • MAJOR:当你对API进行不兼容的更改时。
  • MINOR:当你对API进行向后兼容的增强时。
  • PATCH:当你进行向后兼容的错误修复时。

进行次要并且向后兼容的改变
理想情况下,你应该努力只进行想红藕兼容的更改。向后兼容的更改是对API的附加更改或功能增强:

  • 添加可选属性
  • 向响应添加属性
  • 添加新操作

如果你只进行这些类型的更改,那么老版本的客户端能够直接使用更新的服务,但前提是客户端和服务端都遵守健壮性原则:服务应该为缺少的请求属性提供默认追。同样,客户端应忽略任何额外的响应属性。

进行主要并且不向后兼容的改变
有时,你必须对API进行主要并且不向后兼容的更改。由于你无法强制客户端立即升级,因此服务必须在一段时间内同时支持新旧版本的API。如果你使用的是基于HTTP的进程间通信机制,例如REST,则一种方法是在URL中嵌入主要版本号哦。另一种选择是使用HTTP的内容协商机制,并在MIME类型中包含版本号。

3.1.4 消息格式

消息格式的选择会对进程间通信的效率、API的可用性和可演化性产生影响。
消息的格式可以分为两大类:文本和二进制。

基于文本的消息格式
第一类是JSON和XML这样的基于文本的格式。这类消息格式的好处在于,它们的可读性很高,同时也是自描述的。这样的格式允许消息的接收方只挑选他们感兴趣的的只,而忽略其他值。因此,对消息结构的修改可以做到很好的向后兼容。
使用基于文本格式消息的弊端主要是消息往往过度冗长。消息的每一次传递都必须反复包含除了值以外的属性名称,这样会造成额外的开销。另一个弊端是解析文本引入的额外开销,尤其是在消息较大的时候。

二进制消息格式
有几种不同的二进制格式可供选择。常用的包括Protocol Buffer和Avro。这两种二进制格式的区别在于,前者使用tagged fields,而后者的消费者在解析消息之前需要知道它的格式。因此,实行API的版本升级演进,前者会更加容易。

3.2 基于同步远程过程调用模式的通信

3.2.1 使用REST

如今开发者非常喜欢使用RESTful风格来开发API。REST是一种(总是)使用HTTP协议的进程间通信机制。
REST中的一个关键概念是资源,它通常表示单个业务对象,例如产品或客户,或业务对象的集合。REST使用HTTP动词来操作资源,使用URL引用这些资源。

REST成熟都模型
Leonard Richardson为REST定义了一个成熟度模型,具体包含以下四个层次:

  • Level 0:客户端只是向服务端点发起HTTP POST请求,进行服务调用。每个请求组都指明了需要执行的操作、这个操作针对的目标(例如,业务对象)和必要的参数。
  • Level 1:引入了资源的概念。要执行对资源的操作,客户端需要发出指定要执行的操作和包含任何参数的POST请求。
  • Level 2:使用HTTP动词来执行操作,譬如GET表示火球、POST表示创建、PUT表示更新。请求查询参数和主体指定操作的参数。这让服务能够借助Web基础设施服务,例如通过CDN来缓存GET请求.
  • Level 3:机遇与HATEOAS(Hypertext As The Engine Of Application State)原则设计,基本思想是在由GET请求返回的资源信息中包含链接,这些链接能够执行该资源允许的操作。

在一个请求总获取多个资源的挑战
REST资源通常以业务对象为导向,例如Consumer和Order。因此,设计REST API时的一个常见问题是如何使客户端能够在单个请求中检索多个相关对象。纯REST API要求客户端至少发出两个请求,更复杂的情况需要更多往返并且遭受过多的延迟。
此问题的一个解决方案是API允许客户端在获取资源时检索先关资源。例如,客户端可以使用Get/orders/order-id-1234?expand=consumer检索Order及其Consumer。请求参数中的查询参数用来指定要与Order一起返回的相关资源。这种方法在许多场景中都很有效,但对于更复杂的场景来说,它通常是不够的。实现它也很耗时。这导致了替代技术的日益普及,例如GraphQL。

把操作映射为HTTP动词的挑战
另一个常见的REST API设计问题是如何将要在业务对象上执行的操作映射到HTTP动词。REST API应该使用PUT进行更新,但可能有度欧中方法来更新订单,包括取消订单、修改订单等。此外,更新可能不是幂等的,但这却是使用PUT的要求。一种解决方案是定义用于更新资源的特定方面的子资源。例如,Order Service具有用于取消订单的POST/orders/{orderId}/cacel端点,以及用于修改订单的POST/orders/{orderId}/revise端点。另一种解决方案是将动词指定为URL的查询参数。可惜的是,这两种解决方案都不是特别符合RESTful的要求。

REST的好处和弊端
REST有如下好处:

  • 它非常简单,并且大家都很熟悉
  • 可以使用浏览器扩展或者curl之类的命令行来测试HTTP API
  • 直接支持请求/响应方式的通信
  • HTTP对防火墙友好
  • 不需要中间代理,简化了系统架构

它也存在一些弊端:

  • 它只支持请求/响应方式的通信
  • 可能导致可用性降低
  • 客户端必须知道服务实例的位置(URL)
  • 在单个请求中获取多个资源具有挑战性
  • 有时很难将多个更新操作映射到HTTP动词

3.2.2 使用gRPC

略~

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

在分布式系统中,当服务试图向另一个服务发送同步请求时,永远都面临着局部故障的风险。
要通过合理地设计服务来防止在整个应用程序中故障的传导和扩散,这是至关重要的。解决这个问题分为两部分:

  • 必须让远程过程调用代理有正确处理无响应服务的能力
  • 需要决定如何从失败的远程服务中恢复

开发可靠的远程过程调用代理
每当一个服务同步调用另一个服务时,它应该使用Netflix描述的方法来保护自己。这种方法包括以下机制的组合。

  • 网络超时:在等待针对请求的响应时,一定不要做成无限阻塞,而是要设定一个超时。使用超时可以保证不会一直在无响应的请求上浪费资源。
  • 限制客户端向服务端发出请求的数量:把客户端能够向特定服务发起的请求设置一定上限,如果请求达到了上限,很有可能发起更多的请求也无济于事,这时就应该让请求立即失败。
  • 断路器模式:监控客户端发出请求的成功和失败数量,如果失败的比例超过一定的阈值,就启动断路器,让后续的调用立刻失效。在经过一定的时间后,客户端应该继续尝试,如果调用成功,则接触断路器。

从服务失效故障中恢复
你还必须根据具体情况决定如何从无响应的远程服务中恢复你的服务。一种选择是服务只向其客户返回错误。在其他情况下,返回备用值(fallback value,例如默认值或缓存响应)可能会有意义。

3.2.4 使用服务发现

在这里插入图片描述
服务实例具有动态分配的网络位置。此外,由于自动扩展、故障和升级,服务实例集会动态更改。因此,你的客户端代码必须使用服务发现。

什么是服务发现
服务发现在概念上非常简单:其关键组件是服务注册表,它是包含服务于实例网络位置信息的一个数据库。
服务实例启动和停止时,服务发现脊椎会更新服务注册表。当客户端调用服务时,服务发现机制会查询服务注册表以获取可用服务实例的列表,并将请求路由到其中一个服务实例。
实现服务发现有以下两种主要方式:

  • 服务及其客户直接与服务注册表交互
  • 通过部署基础设施来处理服务发现

应用层服务发现模式
服务实例使用服务注册表注册其网络位置。客户端首先通过查询服务注册表获取服务实例列表来调用服务于,然后向其中一个实例发送请求。
在这里插入图片描述
应用层服务发现的一个好处是它可以处理多平台部署的问题(服务发现机制与具体的部署平台无关)。
应用层服务发现的一个弊端是:你需要为你使用的每种编程语言(可能还有框架)提供服务发现库。另一个弊端是开发者负责设置和管理服务注册表,这会分散一定的精力。

平台层服务发现模式
客户端向DNS名称和VIP(虚拟IP)发出请求,部署平台自动将请求路由到其中一个可用的服务实例。服务注册、服务发现和请求路由完全由部署平台处理。
在这里插入图片描述
由平台提供服务发现机制的主要好处是服务发现的所有方面都完全由部署平台处理。服务和客户端都不包含任何服务发现代码。
弊端是它仅限于支持使用该平台部署的服务。

3.3 基于异步消息模式的通信

使用消息机制时,服务之间的通信采用异步交换消息的方式完成。基于消息机制的应用程序使用消息代理,它充当服务之间的中介。另一种选择是使用无代理架构,通过直接向服务发送消息来执行服务请求。服务客户端向服务发送消息来发出请求,如果希望服务实例回复,服务将通过向客户端发送单独的消息的方式来实现。

3.3.1 什么是消息传递

关于消息
略~

关于消息通道
如图3-7所示,消息通过消息通道进行交换。
在这里插入图片描述
有以下两种类型的消息通道:

  • 点对点:向正在从通道读取的一个消费者传递消息。服务使用点对点通道来实现一对一的交互方式。
  • 发布-订阅:将一条消息发给所有订阅的接收方。服务使用发布-订阅通道来实现一对多的交互方式。

3.3.2 使用消息机制实现交互方式

实现请求/响应和异步请求/响应
两种交互方式之间的区别在于,前者,客户端期望服务端立即响应,而对于后者,则没有这样的期望。消息机制本质上是异步的,因此只提供异步请求/响应,但客户端可能会阻塞,直到收到回复。
在这里插入图片描述
客户端必须告知服务发送回复消息的位置,并且必须将回复消息与请求匹配。

实现单向通知
客户端将消息发送到服务所拥有的点对点通道。服务订阅该通道并处理该消息,但服务不会发回回复。

实现发布订阅
客户端将消息发布到由多个接收方读取的发布/订阅通道即可。

实现发布/异步响应
客户端发布一条消息,在消息头部中指定回复通道,这个通道也是一个发布-订阅通道。消费者将包含相关性ID的回复消息写入回复通道。客户端通过使用相关性ID来手机响应,以此将回复消息与请求进行匹配。

3.3.3 为基于消息机制的服务API创建API规范

略~

3.3.4 使用消息代理

在这里插入图片描述
无代理消息
在无代理架构中,服务可以直接交换消息。
无代理的架构有以下一些好处:

  • 允许更轻的网络流量和更对的延迟
  • 消除了消息代理可能成为性能瓶颈或单点故障的可能性

尽管这些好处看起来很吸引人,但无代理的消息具有以下明显的弊端:

  • 服务需要了解彼此的位置
  • 会导致可用性降低,因为在交换消息时,发送方和接受方都必须同时在线
  • 在实现例如确保消息能够成功投递这些复杂功能时的挑战性更大

基于代理的消息
消息代理是所有消息的中介节点。发送方将消息写入消息代理,消息代理将消息发送到接收方。使用消息代理的一个重要好处是发送方不需要知道接收方的网络位置。另一个好处是消息代理缓冲消息,直到接收方能够处理它们。
选择消息代理时,你需要考虑以下各种因素:

  • 支持的编程语言
  • 支持的消息标准
  • 消息排序
  • 投递保证
  • 持久性
  • 耐久性
  • 可扩展性
  • 延迟
  • 竞争性(并发)接收方

基于代理的消息的好处和弊端
使用消息有以下很多好处:

  • 松耦合
  • 消息缓存
  • 灵活的通信
  • 明确的进程间通信

消息机制也有如下一些弊端:

  • 潜在的性能瓶颈
  • 潜在的单点故障
  • 额外的操作复杂性

3.3.5 处理并发和消息顺序

挑战之一是如何在保留消息顺序的同时,横向扩展多个接收方的实例。为了同时处理消息,拥有多个实例是一个常见的要求。但同时处理消息的挑战是确保每个消息只被处理一次,并且是按照它们发送的顺序来处理的。
现在消息代理使用的常见解决方案是使用分片(分区)通道。图3-11展示了这是如何工作的。
在这里插入图片描述

3.3.6 处理重复消息

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

  • 编写幂等消息处理程序
  • 跟踪消息并丢弃重复项

编写幂等消息处理器
如果应用程序处理消息的逻辑是满足幂等的,那么重复的消息就是无害的。
不幸的是,应用程序逻辑通常不是幂等的。或者你可能正在使用消息代理,该消息代理在重新传递消息时不会保留排序。重复或无序消息可能会导致错误。在这种情况下,你必须编写跟踪消息并丢弃重复消息的消息处理程序。

跟踪消息并丢弃重复消息
一个简单的解决方案是消息接收方使用message id跟踪它已处理的消息并丢弃任何重复项。
在这里插入图片描述

3.3.7 事务性消息

服务通常需要在更新数据库的事务中发布消息。数据库更新和消息发送都必须在事务中进行。否则,服务可能会更新数据库,然后在发送消息之前崩溃。如果服务不以原子方式执行这两个操作,则类似的故障可能使系统处于不一致的状态。
传统的解决办法是在数据库和消息代理之间使用分布式事务。然而,分布式事务对现今的应用程序而言并不是一个很好的选择。而且,很多新的消息代理,并不支持分布式事务。

使用数据库作为消息队列
我们假设你的应用程序正在使用关系型数据库。可靠地发布消息的直接方法是应用事务性发件箱模式。此模式使用数据库表作为临时消息队列。如图3-13所示。
在这里插入图片描述
你可以对某些NoSQL数据库使用类似的方法。作为record存储在数据库中的每个业务实体都有一个属性,该属性是需要发布的消息列表。当服务更新数据库中的实体时,他会向该列表附加一条消息。这是原子的,因为它是通过单个数据库操作来完成的。但是,挑战在于有效的找到那些拥有事件并发布事件的业务实体。

将消息从数据移动到消息代理并对外发送有两种不同的方法。

  • 通过轮询模式发布事件
  • 使用事务日志拖尾模式发布事件
    在这里插入图片描述

3.3.8 消息相关的类库和框架

略~

3.4 使用异步消息提高可用性

3.4.1 同步消息会降低可用性

REST是一种非常流行的进程间通信机制。你可能很想将它用于服务间通信。但是,REST的问题在于它是一个同步协议:HTTP客户端必须等待服务端返回响应。只要服务使用同步协议通信,就可能降低应用程序的可用性。
如果你想最大化一个系统的可用性,就应该设法最小化系统的同步调用量。

3.4.2 消除同步交互

在必须处理同步请求的情况下,仍旧有一些方式可以最大限度的降低同步通信的数量。当然,最彻底的方式还是把所有的服务都改成异步API,但是在现实情况下这并不太可能。

使用异步交互模式
在这里插入图片描述
客户端和服务端时候用消息通道发送消息来实现异步通信。这个交互过程中不存在阻塞等待响应的情况。
这样的架构非常有弹性,因为消息代理会一直缓存消息,直到有服务端接收并处理消息。然而,问题是服务很多情况下都采用类似REST这样的同步通信协议的外部API,并且要求对请求立即做出响应。
在这种情况下,我们可以采用复制数据的方式来提高可用性。

复制数据
在这里插入图片描述
在有些情况,复制数据是一种有用的方式。然而,它的一个弊端在于,有时候被复制的数据量巨大,会导致效率低下。另外一个弊端在于,复制数据并没有从根本上解决服务如何更新其他服务所拥有的数据的这个问题。

先返回响应,再完成处理
在这里插入图片描述
这种方法的优点在于,即使Consumer Service中断,Order Service仍然会创建订单并响应其客户。最终,Consumer Service将重新启动并处理任何排队的消息,并且验证订单。
在完全处理请求之前响应服务的弊端是它使客户端更复杂,为了使客户端知道订单是否已成功创建,客户端必须定期轮询,或者Order Service必须向客户端发送通知消息。

第4章 使用Saga管理事务

传统的分布式事务管理方法对于现在应用程序来说不是一个好的选择(服务的数据是私有的,外部只能通过服务暴露的API访问这些数据,因此在数据库层面实现的分布式事务不再可行。)。跨服务的操作必须使用所谓的Saga来维护数据的一致性,而不是ACID事务。Saga的一个挑战在于只满足ACD特性,而缺乏传统ACID事务的隔离性。
协调Saga有两种不同的方式:

  • 协同式:Saga参与方在没有集中控制器的情况下交换事件式消息
  • 编排式:集中控制器告诉Saga参与方要执行的操作。

4.1 微服务架构下的事务管理

4.1.1 微服务架构对分布式事务的需求

作为FTGO的开发人员,你负责实现createOrder()这个系统操作。这个操作必须验证消费者是否满足下订单的相关条件、验证订单内容、完成消费者的信用卡授权,以及在数据库中创建Order。在单体应用中,这样的操作是相对直观和容易实现的,可以使用一个ACID类事务来保证数据的一致性。
与之相反,在微服务架构下实现同样的操作则颇有难度。如图4-1所示,所需的验证数据散步在不同的服务中。
在这里插入图片描述

4.1.2 分布式事务的挑战

在多个服务、数据库和消息代理之间维持数据一致性的传统方式是采用分布式事务。分布式事务管理的事实标准是XA。XA采用两阶段提交来保证事务中的所有参与方同时完成提交,或者在失败时同时回滚。应用程序的整个技术栈需要满足XA标准。市面上绝大多数SQL数据库和一部分消息代理满足XA标准。
分布式事务并不像听起来那么简单,而是有着许多问题。其中一个问题是,许多新技术,包括NoSQL数据库,并不支持XA标准的分布式事务。同样,一些流行的消息代理也不支持分布式事务。因此,如果你坚持在微服务架构中使用分布式事务,那么不得不放弃使用这些流行的数据库或消息代理。
分布式事务的另一个问题在于,它们本质上都是同步进程间通信,这会降低分布式系统的可用性。为了让一个分布式事务完成提交,所有参与方的服务都必须可用。
Eric Brewer在他注明的CAP理论中早已证明,系统只能够在它的一致性、可用性和分区容错性中同时保证两个。今天,架构师倾向于保证系统的可用性,而放弃对数据强一致的要求。

4.1.3 使用Saga模式维护数据一致性

Saga是一种在微服务架构中维护数据一致性的机制,它可以避免分布式事务所带来的问题。一个Saga表示需要更新多个服务中数据的一个系统操作。Saga由一连串的本地事务组成。每一个本地事务负责更新它所在服务的私有数据库,这些操作仍旧依赖于我们所熟悉的ACID事务框架和函数库。
Saga在几个重要方面与ACID事务不同。它们缺少ACID事务的隔离性。此外,由于每个本地事务都提交了其修改,因此必须使用额外的补偿事务回滚Saga。

实例Saga:Create Order Service
如图4-2所示,Order Service使用此Saga实现createOrder()操作。Saga的第一个本地事务由创建订单的外部请求启动。其他5个本地事务均由前一个完成触发。
在这里插入图片描述
Saga使用补偿事务回滚所做出的改变
传统ACID事务的一个重要特性是:如果业务逻辑检测到违反业务规则,可以轻松回滚事务。通过执行ROLLBACK语句,数据库可以撤销目前为止所做的所有更改。遗憾的是,Saga无法自动回滚,因为每个步骤与都会将其更改提交到本地数据库。你必须编写所谓的补偿事务来显示撤销先前的操作。
在这里插入图片描述

4.2 Saga的协调模式

  • 协同式:把Saga的决策和执行顺序逻辑分布在Saga的每一个参与方中,它们通过交换事件的方式来进行沟通。
  • 编排式:把Saga的决策和执行顺序逻辑集中在一个Saga编排器类中。Saga编排器发出命令式消息给各个Saga参与方,指示这些参与方服务完成具体的操作。

4.2.1 协同式Saga

实现协同式的Create Order Saga
图4-4显示了Create Order Saga的基于协同版本的设计。
在这里插入图片描述
图4-5显示了Accounting Service无法授权消费者信用卡时的事件流
在这里插入图片描述
在实现基于协同的Saga时,你必须考虑一些与服务间通信相关的问题。第一个问题是确保Saga参与方将更新其本地数据库的发布事件作为数据库事务的一部分。
你需要考虑的第二个问题是确保Saga参与方必须能够将接收到每个事件映射到自己的数据上。解决方案是让Saga参与方发布包含相关性ID的事件,该相关性ID使其他参与方能够执行数据的操作。

协同式Saga的好处和弊端
好处:

  • 简单:服务在创建、更新或删除业务对象时发布事件
  • 松耦合:参与方定于事件并彼此之间不会因此产生耦合

弊端:

  • 更难理解:Saga的逻辑分布在每个服务的实现中欧冠
  • 服务之间的循环依赖关系
  • 紧耦合的风险:每个Saga参与方都需要订阅所有影响它们的事件,在代码层面,发布方和订阅方需要保持同步

4.2.2 编排式Saga

编排式是实现Saga的另外一种方式。当使用编排式Saga时,开发人员定义一个编排器类,这个类的唯一职责就是告诉Saga的参与方该做什么操作。Saga编排器使用命令/异步响应方式与Saga的参与方服务通信。为了完成Saga中的一个环节,编排器对某个参与方发出一个命令式消息,告诉这个参与方需要做什么操作。当参与方完成操作后,会给编排器发送一个答复消息。编排器处理这个消息,并决定Saga的下一步操作是什么。

实现编排式Create Order Service
图4-6显示了Create Order Service的基于编排式的设计。
在这里插入图片描述
把Saga编排器视为一个状态机
状态机是建模Saga编排器的一个好方法。状态机由一组状态和一组由事件触发的状态之间的转换组成。每个转换都可以有一个动作,对Saga来说动作就是对某个参与方的调用。
图4-7显示了Create Order Saga的状态机模型。
在这里插入图片描述
Saga编排和事务性消息
基于编排的Saga的每个步骤都包括一个更新数据库和发布消息的服务。服务必须使用事务性消息,以便自动更新数据库并发布消息。

编排式Saga的好处和弊端
好处:

  • 更简单的依赖关系:编排器依赖参与方,参与方不会循环依赖
  • 较少的耦合:每个服务实现供编排器调用的API,不需要知道Saga参与方发布的事件
  • 改善关注点隔离,简化业务逻辑:Saga的协调逻辑本地化在Saga编排器中。领域对象更简单,并且不需要了解它们参与的Saga。

弊端:

  • 编排器中存在集中过多业务逻辑的风险
  • 编排器容易成为单点故障点
  • Saga处理缺乏隔离

4.3 解决隔离问题

ACID事务的隔离属性可以确保同时执行多个事务的结果与顺序执行他们的结果相同。数据库为每个ACID事务提供了具有对数据的独占访问权的错觉。
使用Saga的挑战在于它们缺乏ACID事务的隔离属性。这是因为一旦该事务提交了,每个Saga的本地事务所做的更新都会立即被其他的Saga看到。此行为可能导致两个问题。首先,其他Saga可以在执行时更改该Saga所访问的数据。其他Saga可以在Saga完成更新之前读取其数据,因此可能暴露不一致的数据。

4.3.1 缺乏隔离导致的问题

  • 丢失更新:一个Saga没有读取更新,而是直接覆盖了另一个Saga所做的更改
  • 脏读:一个Saga或一个事务读取了尚未完成的Saga所做的更新。
  • 模糊或不可重复读:一个Saga的两个不同步骤读取相同的数据,确获得了不同的结果,因为另一个Saga已经进行了更新。

4.3.2 Saga模式下实现隔离的对策

Saga事务模型是ACD,它缺乏隔离可能导致异常。开发人员有责任以一种防止异常或最小化其对业务影响的的方式来编写Saga。这有一些通用的对策。

Saga的结构
一个Saga包含三种类型的事务:

  • 可补偿性事务:可以使用补偿事务回滚的事务。
  • 关键性事务:Saga执行过程的关键点。如果关键性事务成功,则Saga将一直运行到完成。
  • 可重复性事务:在关键性事务之后的事务,保证成功。
    在这里插入图片描述
    对策:语义锁
    使用语义锁对策时,Saga的可补偿性事务会在其创建或更新的任何记录中设置标志。该标志表示该记录未提交且可能发生更改。该标志可以是防止其他事务访问记录的锁,也可以是指示其他事务应该谨慎地处理该记录的一个警告。这个标志会被一个可重复的事务清除,这表示Saga成功完成;或通过补偿性事务清除,这表示Saga发生了回滚。
    Order.state字段是语义锁的一个很好的例子。*_PENDING状态实现语义锁定。
    管理语义锁指示问题的一半。你还需要根据具体情况决定一个Saga应该如何处理已被锁定的记录。
    一个选择是让系统命令执行失败并告诉客户端稍后重试。这种方法的主要好处是它易于实现。然而,弊端是它使客户端更复杂,因为它必须实现重试逻辑。
    另一个选择是让系统命令出于阻塞状态吗,直到其他Saga释放了语义锁。
    使用语义锁的好处是它们实质上重新创建了ACID事务提供的隔离性。更新相同记录的Saga被序列化,这显著减少了编程工作量。另一个好处是它们消除了客户端重试的负担。缺点是应用程序必须管理锁。它还必须实现死锁检测算法,该算法执行Saga的回滚以打破死锁并重新执行它。

对策:交换式更新
一个简单的对策是将更新操作设计为可交换的。如果可以按任何顺序执行,则操作是可交换的。账户的debit()和credit()操作是可交换的(如果忽略透支支票)。这种策略是很有用的,因为它可以避免更新丢失。
例如,考虑一个场景,在一个可补偿性食物记入一个账户之后,需要回滚一个Saga。补偿性事务可以简单的贷记账户已撤销更新(记录操作流水,如记入是+100,补偿时贷记-100)。这就不可能覆盖其他Saga的更新。

对策:悲观视图
处理缺乏隔离性的另一种方法是悲观视图。它重新排序Saga的步骤,以最大限度地降低由于脏读而导致的业务风险。

对策:重读值
重读值对策可以防止丢失更新。使用此对策的Saga在更新之前重新读取记录,验证它是否未更改,然后更新记录。如果记录已更改,则Saga将中止并可能重新启动。此对策是乐观脱机锁模式的一种形式。

对策:版本文件
版本文件对策只所以如此命名,是因为它记录了对数据执行的操作,一遍可以对他们进行重排。这是将不可交换操作转换为可交换操作的一种方法。考虑Create Order Saga和Cancel Order Sagat同时执行的场景。除非使用语义锁对策,否则Cancel Order Saga可能会在Create Order Saga授权信用卡之前取消消费者信用卡的授权。
Accounting Service处理这些无序请求的一种方法是在操作到达时记录操作,然后以正确的顺序执行操作。在这种情况下,它将首先记录Cancel Authorization请求。然后,当收到后续的Authorize Card请求时,它会注意到它已经收到Cancel Authentication请求并跳过授权信用卡。

对策:业务风险评级
最终的对策是基于业务价值(业务风险)对策。这是一种基于业务风险选择并发机智的策略。使用此对策的应用程序使用每个请求的属性来决定使用Saga和分布式事务。它使用Saga执行低风险请求,使用分布式事务来执行高风险请求。

4.4 Order Service和Create Order Saga的设计

略~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值