分布式-分布式事务理论、模型、方案、Seata框架

一、分布式事务理论模型
分布式事务问题也叫分布式数据一致性问题,简单来说就是如何在分布式场景中保证多个节点数据的一致性 。分布式事务产生的核心原因在于存储资源的分布性,比如多个数据库,或者MySQL和Redis两种不同存储设备的数据一致性等 。在实际应用中,我们应该尽可能地从设计层面去避免分布式事务的问题,因为任何一种解决方案都会增加系统的复杂度 。接下来我们了解一下分布式事务问题的常见解决方案 。
1.1 X/Open分布式事务模型
X/Open DTP(X/OpenModel)是X/Open这个组织义的一套分布式事务的标准这个标准提出了使用两阶段提交(2PC,Two-Phase-)来保证分布式事的完整性 。如图8-2所示,X/Open DTP中包含以下三种角色 。
图8-2所展示的角色和关系与本地事务的原理基本相同,唯一不同的在于,如果此时RM代表数据库,那么TM需要能够管理多个数据库的事务,大致实现步骤如下:
二、分布式一致性协议 2.1 两阶段提交协议:准备阶段、提交阶段
2.1.1致命的问题 阻塞:准备阶段会锁定资源,直到下一步完成 。单点故障:如果协调者(事务管理器)宕机,会一直阻塞 。脑裂:协调者发送提交命令,有些参与者没有收到,不会执行事务 。多个参与者之间不一致 。2.2.2 两阶段提交协议的流程
致命问题
事务协调者的单点故障:如果协调者在第二阶段出现了故障,那么其他的参与者(RM)会一直处于锁定状态 。
脑裂:导致数据不一致问题:在第二阶段中,事务协调者向所有参与者(RM)发送请求后,发生局部网络异常导致只有一部分参与者(RM)接收到了请求,这部分参与者(RM)收到请求后会执行操作,但是未收到请求的节点由于事务无法提交,导致数据出现不一致问题
2.2 三阶段提交协议:询问阶段、准备阶段、提交阶段
三阶段提交协议是两阶段提交协议的改进版本.它通过超时机制解决了阻塞的问题,把两个阶段增加为三个
2.2.1 三阶段提交协议和两阶段提交协议相比有一些不同点
增加了一个阶段,用于询问所有参与者是否可以执行事务操作并且响应,它的好处是,可以尽早发现无法执行操作而中止后续的行为 。
在准备阶段之后,事务协调者和参与者都引入了超时机制,一旦超时,事务协调者和参与者会继续提交事务,并且认为处于成功状态,因为在这种情况下事务默认为成功的可能性比较大 。
实际上,一旦超时,在三阶段提交协议下仍然可能出现数据不一致的情况,当然概率是比较小的 。另外,最大的好处就是基于超时机制来避免资源的永久锁定 。
*需要注意的是,不管是两阶段提交协议还是三阶段提交协议,都是数据一致性解决方案的实现,我们可以在实际应用中灵活调整 。比如集群中的数据一致性,就用到了优化版的两阶段提交协议,优化的地方在于,它不需要所有参与者在第一阶段返回成功才能提交事务,而是利用少数服从多数的投票机制来完成数据的提交或者回滚 。
可以看出,从时序上来说,如果遇到极端情况,则 TCC 会有很多问题,例如,如果在取消时一些参与者收到指令,而另 些参与者没有收到指令,则整个系统仍然是不 致的。对于这种复杂的情况,系统首先会通过补偿的方式尝试自动修复,如果系统无法修复,则必须由人参与解决TCC 的逻辑上看,可以说 TCC 是简化版的 阶段提交协议,解决了两阶段提交协议的阻塞问题,但是没有解决极端情况下会出现不一致和脑裂的问题 然而,TCC 通过自动化补手段,将需要人工处理的不 致情况降到最少,也是 种非常有用的解决方案 。某著名 互联网公司在内部的 些中间件上实现了 TCC 模式 。我们给出 个使用 TCC 的实际案例,在秒杀的场景中,用户发起下订单请求,应用层先询库存,确认商品库存还有余量,则锁定库存,此时订单状态为待支付,然后指引用户去支付由于某种原因用户支付失败或者支付超时,则系统会自动将锁定的库存解锁以供其他用户秒杀 。
三、 分布式一致性理论
前面提到的两阶段提交和三阶段提交是XA协议解决分布式数据一致性问题的基本原理,但是这两种方案为了保证数据的强一致性,降低了可用性 。实际上这里涉及分布式事务的两个理论模型 。
3.1数据库一致性理论基础
ACID(酸)
关系型数据库天生用于解决具有复杂事务场景的问题,完全满足ACID的特性 。
具有ACID特性的数据库支持强一致性,强一致性代表数据库本身不会出现不一致,每个事务都是原子的,或者成功或者失败,事务间是隔离的,互相完全不受影响,而且最终状态是持久落盘的 。
NoSQL完全不适合交易场景,主要用来做数据分析、ETL、报表、数据挖掘、推荐、日志处理、调用链跟踪等非核心交易场景
?现在我们来看看下订单和扣库存一致性问题,如果是在数据量较小的情况下,可以利用关系型数据库的强一致性解决,也就是把订单表和库存表放在同一个关系型数据库中,利用关系型数据库进行下订单和扣库存两个紧密相关的操作,达到订单和库存实时一致的结果 。如果是大规模高并发的情况,由于业务规则的限制,无法将相关数据分到同一个数据库分片,这时就需要实现最终一致性 。
3.2 CAP定理 (帽子原理)
由于对系统或者数据进行了拆分,我们的系统不再是单机系统,而是分布式系统 。
所以分布式系统理论上不可能选择CA架构,只能选择CP或者AP架构 。对于CP来说,放弃可用性,追求一致性和分区容错性,我们的其实就是追求的强一致 。对于AP来说,放弃一致性(这里说的一致性是强一致性),追求分区容错性和可用性,这是很多分布式系统设计时的选择,后面的BASE也是根据AP来扩展 。
redis 就是 AP 模型,zk 就是 CP
一般来说基于CP 一般来说具有性能瓶颈问题;
基于CAP理论我们知道对于数据一致性问题有AP和CP两种方案,但是在电商领域等互联网场景下,基于CP的强一致性方案在数据库性能和系统处理能力上会存在一定的瓶颈 。所以在互联网场景中更多采用柔性事务,所谓的柔性事务是遵循BASE理论来实现的事务模型,它有两个特性:基本可用、柔性状态 。在本节中主要基于柔性事务模型来分析互联网产品中分布式事务的常见解决方案 。
3.3 BASE(碱)
BASE思想解决了CAP提出的分布式系统的一致性和可用性不可兼得的问题 。BASE思想与ACID原理截然不同,它满足CAP原理,通过牺牲强一致性获得可用性,一般应用于服务化系统的应用层或者大数据处理系统中,通过达到最终一致性来尽量满足业务的绝大多数需求 。
BASE平衡理论,简单来说就是在不同的场景下,可以分别利用ACID和BASE来解决分布式服务化系统的一致性问题 。
BASE对酸碱平衡的总结
| 解决一致性问题的三条实践经验:使用向上扩展(强悍的硬件)并运行专业的关系型数据库,能够保证强一致性;关系型数据库水平伸缩和分片,将相关数据分到数据库的同一个片上;最终一致性 。
四、保证最终一致性的模式
4.0 两端协议、TCC协议缺点
在大规模、 高并发服务化系统中,一 个功能被拆分成多个具有单 功能的子功能,一个流程会有多个系统的多个单 功能的服务组合实现,如果使用两阶段提交协议和 阶段提交协议,能解决系统间的 致性问题 除了这两个协议的自身问题,其实现也比较复杂、成本比较高,最要的是性能不好,相比来看,TCC协议更简单且更容易实现,但是 TCC协议由于个事务都需要执行 Try,再执行略显雕肿,因此,现实系统的底线是仅仅 要达到最终一致性,而不需要实现专业 业的、复杂的一致 致性协议 。
实现最终一致性有些非常有效、简单式的模式,下面就介绍这些模式及其应用场景实现最终一致性非常有效、简单的模式 。
4.1查询模式
每个服务操作都需要有唯一的流水号标识,用方可以通过查询接口得知服务操作执行的状态,然后根据不同的状态来做不同的处理操作 。?为了能够实现查询,每个服务操作都需要有唯 的流水号标识,也可使用此次服务操作对应的资源 ID 来标识,例如:请求流水号、订单号等首先,单笔查询操作是必须提供的,也鼓励使用单笔订单查询,这是因为每次调用 需要占用的负载是可控的 。批量查询则根据 要来提供,如果使用了批 查询,则 要有合理的分页机制,并且必须限制分页的大小,以及对批量查询的吞吐量有容 评估、熔断、隔离和限流等措施;
4.2 补偿模式
有了上面的查询模式,在任何情况下,我们都能得知具体的操作所处的状态,如果整个操作都处于不正常的状态,则我们需要修正操作中有问题的子操作,这可能需要重新执行未完成的子操作,后者取消己经完成的子操作,通过修复使整个分布式系统达到 为了让系统终达到 致状态而做的努力都叫作补偿 。
若业务操作发起方还没有收到业务操作执行方的 明确返回或者调用超时,则用查询模式查询业务执行方的执行状态,在获取业务执行方的状态后,如果业务员执行方已经执行成功,则业务发起方回调,告诉业务发起方执行成功;如果业务发起方的执行状态为失败或者未知,也叫作快速失败策略,然后执行业务操作的逆向操作,保证不被执行或者回滚已经执行的操作,让业务发起方、业务操作方的状态最终一致;
4.2.1补偿操作根据发起形式分为以下几种 。4.3 异步确保模式
异步确保模式是补偿模式的 个典型案例,经常应用到使用方对响应时间要求不太高的场景中,通常把这类操作从主流程中摘除,通过异步的方式进行处理,处理后把结果通过通知系统通知给使用方。这个方案的最大好处是能够对高并发流量进行消峰,例如:电商系统中的物流、配迭,以及支付系统中的计费、入账等 。
在实践中将要执行的异步操作封装后持久入库,然后通过定时捞取未完成的任务进行补偿操作来实现异步确保模式,只要定时系统足够健壮,则任何任务最终都会被成功执行
若对某个操作迟迟没有收到响应,则通过查询模式、补偿模式和异步确保模式来继续未完成的操作 。
4.4 定期校对模式
系统在没有达到 致之前,系统间的状态是不 致的,甚至是混乱的,要通过补偿操作来达到最终 致性的目的,但是如何来发现需要补偿的操作呢?
在操作主流程中的系统间执行校对操作,可以在事后异步地批量校对操作的状态,如果发现不致的操作,则进行补偿,补偿操作与补偿模式中的补偿操作是一致的 。另外,实现定期校对的 个关键就是分布式系统中需要有 个自始 终唯 ID局唯一ID 有以下两种方法(分布式ID) 。
【分布式-分布式事务理论、模型、方案、Seata框架】4.5 可靠消息模式
在分布式系统中,对于主流程中优先级 比较低的操作,大多采用异步的方式执行,也就是前面提到的异步确保模型,为了让异步操作的调用方和被调用方充分理解,也由于专业的消息队列本身具有可伸缩、可分片、可持久等功能,我们通常通过消息队列实现异步化 。对于消息队列,我们需要建立特殊的设施来保证可靠的消息发送及 处理机的幕等性 。
4.5.1 消息的可靠发送
消息的可靠发送可以认为是尽最大努力发送消息通知,有以下两种实现方法 。
4.5.2 消息处理器的幂等性处理
如果我们要保证可靠地发送消息,简单来说就是要保证消息一定发送出去,那么需要有重试机制 。有了重试机制后,消息就一定会重复,那么我们需要对重复的问题进行处理 。处理重复问题的最佳方式是保证操作的幂等性;
保证操作的幂等性的常用方法如下 。
1)使用数据库表的唯一键进行滤重,拒绝重复的请求 。
2)使用分布式表对请求进行滤重 。
3)使用状态流转的方向性来滤重,通常使用数据库的行级锁来实现 。
4)根据业务的特点,操作本身就是幕等的 例如 删除 个资源、增加一个资源、获得个资源等 。
4.6 缓存一致性模式
在大规模、高并发系统中的一个常见的核心需求就是亿级的读需求,显然,关系型数据库并不是解决高并发读需求的最佳方案,互联网 的经典做法就是使用缓存来抗住读流量 。下面是使用缓存来保证一致性的最佳实践 。
1) 如果性能要求不是非常高,则尽量使用分布式缓存,而不要使用本地缓存 。
2) 写缓存时数据一定要完整,如果缓存数据的一部分有效 另一部分无效,则宁可在需要时回源数据库,也不要把部分数据放入缓存中 。
3)使用缓存牺牲了一 致性,为了提高性能,数据库与缓存只需要保持弱一致性,而不需要保持强 致性,否则违背了使用缓存的初衷 。
4) 读的顺序是先读缓存,后读数据库,写的顺序要先写数据库,后写缓存 。
五、超时处理模式?
微服务的交互模式:同步调用模式、接口异步调用模式、消息队列异步处理模式
5.1 接口调用模式 5.1.1 同步调用模式
在同步调用模式中,服务 1调用服务2,服务1 的线程阻塞等待服务2 返回处理结果,如果服务 2一直不返回处理结果,则服务2一 直等待到超时为止;
同步调用模式适用于大规模、高并发的短小操作,而不适用于后端负载较高的场景,例如:几乎所有 JDBC 的实现完全使用 BIO 同步阻塞模式
5.1.2 接口异步调用模式
在接口异步调用模式中,服务1 请求服务2接 受理某项任务,服务2 受理后即刻返回给服1其受理结果,如果受理成功,则服务1继续做其他任务,而服务异2步地处理这项任务,直到服务2处理完这项任务后,才反向地通知服务1 任务已经完成,服务1再做后续处理;
接口异步调用模式适用于非核心链路上负载较高的处理环节,这个环节经常耗时较长,并且对时效性要求不高 。例如:在 B2C 电商系统中,一件商品售卖成功后,需要给相应的商户入账收入,这个过程对时效性要求不高,可以使用接口异步调用模式 。
5.1.3 消息队到异步处理模式
**消息队列异步处理模式利用消息队列作为通信机制,在这种交互模式中,通常服1 只需将某种事件传递给服务2,而不需要等待服务2返回结果 。**在这样的场景下,服务1 与服务2可以充分理解,并且在大规模、高并发的微服务系统中,消息队列对流量具有消峰的功能 。
?消息队列异步处理模式与接口异步调用模式类,多应用于非核心 链路上负载较高的处理环节中,井且服务的上游不关心下游的处理结果,下游也不需要向上游返回处理结果 例如:
在电商系统中,用户下订单支付且交易成功后,后续的物流处理适合使用消息队列异步处理模式,因为物流发货属于物流和配送系统的职责,不应该影响交易,所以交易系统不需要对其有感知;
以上 种交互模式普遍应用于服务化和微服务架构中,它们之间没有绝对的好坏,只需要在特定场景下做出更适合的选择 。
5.1.4 同步与异步的抉择
一些互联网公司试图通过规范来约束这三种方式的使用和选择,下面是笔者在工作中收集的两个不同的团队倡导的关于同步和异步选择的原则 。
这两个原则从字面意义上看是完全不同的,甚至是矛盾的 。实际上 这里的原则都没有错,只不过原则抽象得太干净利落,以至于没有给出适合这些原则的环境信息;
5.2 交互模式下超时问题的解决方案 5.3 超时补偿的原则
对于 2.4.3 节的多个场景来说,
我们都需要对服务间同步超时造成的后果进行处理,而处理方法有快速失败和内部补偿两种,补偿模式也有调用方补偿和接收方补偿两种,
具体使用哪种方式呢?
通过这个案例,我们很容易理解服务间调用超时补偿的原则服务 1调用服务2,如果服务2 响应服务1并且告诉服务 消息己接收,那么服务1任务就结束了;如果服务2 处理失败,那么服务2 应该负责重试或者补偿 。在这种情况下,服务 2通常接收消息后先持久再告诉服务1 接收成功,随后服务2 才开始处理持久的消息,避免服务进程被杀掉而导致消息丢失,;
?服务1 调用服务2,如果服务2 没有给出明确的接收响应,例如网络超时,那么服务1应该持续进行重试,直到服务2 明确表示己经接收消息 在这种情况下容易出现重复
的消息,因此在服务 2中通常要保证滤重或者幕等性 。
六、 分布式事务问题的常见解决方案
在前面的章节中已经详细分析了分布式事务的问题及理论模型,并且基于CAP理论我们知道对于数据一致性问题有AP和CP两种方案,但是在电商领域等与联网场景下,基于CP的强一致性方案在数据库性能和系统处理能力上会存在一定的瓶颈 。所以在互联网场景中更多采用柔性事务,所谓的柔性事务是遵循BAS理论来实现的事务模型,它有两个特性:基本可用、柔性状态 。在本节中主要基于柔性事务模型来分析互联网产品中分布式事务的常见解决方案 。
6.1TCC 补偿型方案
TCC(Try--)是一种比较成熟的分布式数据一致性解决方案它实际上是把一个完整的业务拆分为如下三个步骤 。
其实TCC是一种两阶段提交的思想,第一阶段通过Ty进行准备工作,第二阶段/表示Try阶段操作的确认和回滚 。在分布式事务场景中,每个服务实现TCC之后,就作为其中的一个资源,参与到整个分布式事务中 。然后主业务服务在第一阶段中分别调用所有TCC服务的Try方法 。最后根据第一个阶段的执行情况来决定对第二阶段的或者
对于TCC的工作机制,我们举一个比较简单的例子 。在一个理财App中,用户通过账户余额购买一个理财产品,这里涉及两个事务操作:
这两个事务操作在微服务架构下分别对应的是两个不同的微服务,以及独立的数据库操作,在TCC的工作机制中,首先针对账户服务和理财产品服务分别提供Try、和三个方法 。
在一个主业务方法中,分别调用这两个服务对外提供的处理方法(资金扣减、理财产品可申购额度扣减)这两个服务做实际业务处理时,会先调用Trv方法来做资源预留,如果这两个方法处理都正常,TCC事务协调器就会调用方法对预留资源进行实际应用 。否则TCC事物协调器一旦感知到任何一个服务的Try方法处理失败,就会调用各个服务的方法进行回滚,从而保证数据的一致性 。
在一些特殊情况下,比如理财产品服务宕机或者出现异常,导致该服务并没有收到TCC事务协调器的Cance或者请求,怎么办呢?没关系,TCC事务框架会记录一些分布式事务的操作日志,保存分布式事务运行的各个阶段和状态 。TCC事务协调器会根据操作日志来进行重试,以达到数据的最终一致性 。
需要注意的是,TCC服务支持接口调用失败发起重试,所以TCC暴露的接口都需要满足幂等性;
6.2 基于可靠性消息的最终一致性方案
基于可靠性消息的最终一致性是互联网公司比较常用的分布式数据一致性解决方案,它主要利用消息中间件(或)的可靠性机制来实现数据一致性的投递 。
以电商平台的支付场景为例,用户完成订单的支付后不需要同步等待支付结果,可以继续做其他事情 。但是对于系统来说,大部分是在发起支付之后,等到第三方支付平台提供异步支付结果通知,再根据结果来设置该订单的支付状态 。并目如果是支付成功的状态,大部分电商平台基于营销策略还会给账户增加一定的积分奖励 。所以,当系统接收到第三方返回的支付结果时,需要更新支付服务的支付状态,以及更新账户服务的积分余额,这里就涉及两个服务的数据-致性问题 。
从这个场景中可以发现这里的数据一致性并不要求实时性,所以我们可以采用基于可靠性消息的最终一致性方案来保证支付服务和账户服务的数据一致性 。如图8-7所示,支付服务收到支付结果通知后,先更新支付订单的状态,再发送一条消息到分布式消息队列中,账户服务会监听到指定队列的消息并进行相应的处理,完成数据的同步 。
在图8-7的解决方案中,我们不难发现一些问题,就是支付服务的本地事务与发送消息这个操作的原子性问题,具体描述如下;
以上问题也有很多成熟的解决方案,以为例,它提供了事务消息模型,如图8-8所示,具体的执行逻辑如下:
在事务消息模型中,事务是由生产者来完成的,消费者不需要考虑,因为消息队列可靠性投递机制的存在,如果消费者没有签收该消息,那么消息队列服务器会重复投递,从而实现生产者的本地数据和消费者的本地数据在消息队列的机制下达到最终一致 。
不难发现,在的事务消息模型中最核心的机制应该是事务回查,实际上查询模式在很多类似的场景中都可以应用 。在分布式系统中,由于网络通信的存在,服务之间的远程通信除成功和失败两种结果外,还存在一种未知状态,比如网络超时 。服务提供者可以提供一个查询接口向外部输出操作的执行状态,服务调用方可以通过调用该接口得知之前操作的结果并进行相应的处理 。
6.3 最大努力通知型
最大努力通知型和基于可靠性消息的最终一致性方案的实现是类似的,它是一种比较简单的柔性事务解决方案也比较适用于对数据一致性要求不高的场景,最典型的使用场景是支付宝支付结果通知,实现流程如图8-9 所示;
七、分布式事务框架Seata
Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务 。它提供了AT、TCC、Saga和XA事务模式,为开发者提供了一站式的分布式事务解决方案 。其中TCC和XA我们前面分析过,AT和Saga这两种事务模式是什么呢?下面先来简单介绍一下这两种事务模式;
7.1 AT模式
AT模式是Seata最主推的分布式事务解决方案,它是基于XA演进而来的一种分布式事务模式,所以它同样分为三大模块,分别是TM、RM和TC,其中TM和RM作为Seata的客户端与业务系统集成,TC作为Seata的服务器独立部署 。TM表示事务管理器( ),它负责向TC注册一个全局事务,并生成一个全局唯一的XID 。在AT模式下,每个数据库资源被当作一个RM( ),在业务层面通过JDBC标准的接口访问RM时,Seata会对所有请求进行拦截 。每个本地事务进行提交时,RM都会向TC(or,事务协调器)注册一个分支事务 。Seata的AT事务模式如图8-10所示 。
具体执行流程如下:
AT模式和XA一样,也是一个两阶提交事务模型,不过和XA相比,做了很多优化,笔者会在后续的章节中重点分析AT模式的实现原理
7.2 Saga模式
Saga模式又称为长事务解决方案,主要描述的是在没有两阶段提交的情况下如何解决分布式事务问题 。其核心思想是:把一个业务流程中的长事务拆分为多个本地短事务,业务流程中的每个参与者都提交真实的提交给该本地短事务,当其中一个参与者事务执行失败,则通过补偿机制补偿前面已经成功的参与者 。
如图8-11所示,Saga由一系列sub-阻成,每个T都有对应的补偿动作C补偿动作用于撤销T造成的数据变更结果 。它和TCC相比,少了Try这个预留动作,每一个操作都真实地影响到数据库 。
7.2.1Saga的优劣势
和XA或者TCC相比,它的优势包括:一阶段直接提交本地事务;没有锁等待,性能较高,在事件驱动的模式下短事务可以异步执行;补偿机制的实现比较简单
缺点是Saga并不提供原子性和隔离性支持,隔离性的影响是比较大的,比如用户购买一个商品后系统赠送一张优惠券,如果用户已经把优惠券使用了,那么事务如果出现异常要回滚时就会出现问题 。
7.2.2Saga的实现方式
在一个电商平台的下单场景中,一般会涉及订单的创建、商品库存的扣减、钱包支付、积分赠送等操作,整体的时序图如图8-12所示 。
电商平台下单的流程是一个典型的长事务场景,根据Saga模式的定义,先将长事务拆分成多个本地短事务每个服务的本地事务按照执行顺序逐一提交,一旦其中一个服务的事务出现异常,则采用补偿的方式逐一撤回这一过程的实现会涉及Saga的协调模式,它有两种常用的协调模式 。
7.2.2.1事件/编排式
在基于事件的编排模式中,第一个服务执行完一个本地事务之后,发送一个事件 。这个事件会被一个或者多个服务监听,监听到事件的服务再执行本地事务并发布新的事件,此后一直延续这种事件触发模式,直到该业务流程中最后一个服务的本地事务执行结束,才意味着整个分布式长事务也执行结束,如图8-13所示 。
这个流程看起来很复杂,但是却是比较常见的解决方案,下面简单描述一下具体的步骤
订单服务创建新的订单,把订单状态设置为待支付,并发布一个事件库存服务监听到事件后,执行本地的库存冻结方法,如果执行成功,则发布一个事件 。支付服务监听事件后,执行账户扣款方法,并发布
EVENT事件·最后,积分服务监听事件,增加账户积分,并更新订单状态为成功 。上述任一步骤执行失败,都会发送一个失败的事件,每个服务需要监听失败的情况根据实际需求进行逐一回滚 。7.2.2.2命令/协同式
命令/协同式需要定义一个Saga协调器,负责告诉每一个参与者该做什么,Saga协调器以命令/回复的方式与每项服务进行通信,如图8-14所示
命令/协同式的实现步骤如下
订单服务首先创建一个订单,然后创建一个订单Saga协调器,启动订单事务 。
Saga协调器向库存服务发送冻结库存命令,库存服务通过Order Saga Reply Queue回复执行结果 。接着,Saga协调器继续向支付服务发起账户扣款命令,支付服务通过Order Saga Reply Queue回复执行结果最后,Saga协调器向积分服务发起增加积分命令,积分服务回复执行结果
需要注意的是,订单Saga协调器必须提前知道“创建订单事务”的所有流程(Seata是通过基于JSON的状态机引擎来实现的),并且在整个流程中任何一个环节执行失败,它都需要向每个参与者发送命令撤销之前的事务操作 。7.3 Seata的安装
Seata是一个需要独立部署的中间件,除直接部署外,它还支持多种部署方式,比如、 。本节主要讲解直接安装的方式
在Seata官网下载1.0.0版本的安装包,100是笔者写作时最新的发布版本进入$(目录,根据系统类型执行相应的启动脚本,在Linux/Max下的执行命令如下 。

分布式-分布式事务理论、模型、方案、Seata框架

文章插图
7.3. 的存储方式
设置事务日志和数据库连接
参数说明如下
7.3.2 Seata服务端配置中心说明
在${)\conf目录下有两个配置文件,分别是.con和file.conf.
.conf配置说明
.conf中包含两项配置:、,完整的配置内容如下
表示配置Seata服务注册的地址,支持目前市面上所有主流的注册中心组件 。它的配置非常简单,通过type指定注册中心的类型,然后根据指定的类型配置对应的服务地址信息,比如当type=nacos时,则匹配到Nacos的配置项如下 。
type默认为file,它表示不依赖于配置中心,在fle类型下,可以不依赖第三方注册中心快速集成Seata 。不过,file类型不具备注册中心的动态发现和动态配置功能 。
配置用于配置Seata服务端的配置文件地址,也就是可以通过配置指定Seata服务端的配置信息的加载位置,它支持从远程配置中心读取和本地文件读取两种方式 。如果配置为远程配置中心,可以使用type指定,配置形式和相同 。
在默认情况下type=file,它会加载file.conf文件中的配置信息
file.conf配置说明
file.conf存储的是Seata服务端的配置信息,完整配置如下 。它包含、、,分别表示协议配置、服务端配置、监控 。
Seata服务端启动时会加载file.conf中的配置参数,这些参数读者不需要记,只需要知道这些参数可以优化即在Seata官网上对参数有非常详细的说明 。
7.3.3 从配置中心加载Seata的配置
从前面的分析过程中我们知道,Seata服务在启动时可以将自己注册到注册中心上,并且file.conf文件中的配置同样可以保存在配置中心,接下来我们尝试把配置信息存储到Nacos上
将配置上传到Nacos
在的官方代码托管平台下载Seata的源码,在源码包中有一个文件夹(目前只在源码包中存在),目录结构如下:
进入-目录,包含.txt和不同配置中心的目录(该目录下包含shel脚本和py脚本) 。其中.txt存放的是Seata客户端和服务端的所有配置信息 。
在-\nacos目录下,执行如下脚本:
sh nacos-config.sh-h 192168.216.128-p 8848 -g SEATE_GROUP
该脚本的作用是把.txt中的配置信息上传到Nacos配置中心 。由于.txt中提供的是默认配置,在实际使用时可以先修改该文件中的内容,再执行上传操作(当然,也可以上传完成之后在Nacos控制台上根据实际需求修改对应的配置项)
脚本如果执行正确,将会在Nacos配置中心看到如图8-15所示的配置列表
Seata服务端修改配置加载位置
进入$)\conf目录,修改.conf文件中的段,完整配置如下:
7.4 关于事务分组的说明
在Seata 端的file.conf置中有一个属性,它表示事务分组映射,是Seata的资源逻辑,类似于服务实例,它的主要作用是根据分组来获取Seata 的服务实例 。
7.4.1服务分组的工作机制
首先,在应用程序中需要配置事务分组,也就是使用nner构造方法中的参数,这个参数有如下几种赋值方式
然后,Seata客户端会根据应用程序的去指定位置(file.conf或者远程配置中心)查找g{)对应的配置值,该值代表TC集群(Seata )的名称
最后,程序会根据集群名称去配置中心或者file.conf仲获得对应的服务列表,也就是.
对应的TC集群真实的服务列表 。实现原理如图8-17所示,具体步骤描述如下
思考事务分组设计
通过上述分析可以发现,在客户端获取服务器地址并没有直接采用服务名称,而是增加了一层事务分组映射到集群的配置 。这样做的好处在于,事务分组可以作为资源的逻辑隔离单位,当某个集群出现故障时,可以把故障缩减到服务级别,实现快速故障转移,只需要切换对应的分组即可 。
7.5 模式的实现原理
在前面的章节中提到过,AT模式是基于XA事务模型演进而来的,所以它的整体机制也是一个改进版的两阶段提交协议 。
AT模式事务的整体执行流程在8.3.1节中讲过,读者可以将书翻回去再复习一遍 。
下面我们详细分析在整个执行流程中,每一个阶段的具体实现原理 。同时,为了更好地理解AT模式的工作机制,我们以库存表来描述整个工作过程,表结构及数据如图8-18所示
7.5.1AT模式第一阶段的实现原理
7.5.2 AT模式第二阶段的原理分析
TC接收到所有事务分支的事务状态汇报之后,决定对全局事务进行提交或者回滚 。
事务提交
如果决定是全局提交,说明此时所有分支事务已经完成了提交,只需要清理日志即可 。这也是和XA最大的不同点,其实在第一阶段各个分支事务的本地事务已经提交了,所以这里并不需要TC来触发所有分支事务的提交,如图8-21所示 。
图8-21中
事务提交的执行流程是:
在第一步中,TC并不需要同步知道分支事务的处理结果,所以分支事务才会采用异步的方式来执行 。因为对于提交操作来说,分支事务只需要清除日志即可,而即便日志清除失败,也不会对整个分布式事务产生任何影响 。
事务回滚
在整个全局事务链中,任何一个事务分支执行失败,全局事务都会进入事务回滚流程 。各位读者应该不难猜出,所谓的回滚无非就是根据UNDO LOG中记录的数据镜像进行补偿 。如果全局事务回滚成功,数据的一致性就得到了保证 。全局事务回滚流程如图8-22所示 。
7.5.3 关于事务的隔离性保证
我们知道,在ACID事务特性中,有一个隔离性,所谓的隔离性是指多个用户并发访问数据库时,数据库为每个用户开启的事务不能被其他事务的操作所干扰,多个并发事务之间要相互隔离 。
在AT模式中,当多个全局事务操作同一张表时,它的事务隔离性保证是基于全局锁来实现的,本节分别针对写隔离与读隔离进行分析 。
7.5.3.1 写隔离
写隔离是为了在多个全局事务针对同一张表的同一个字段进行更新操作时,避免全局事务在没有被提交之前被其他全局事务修改 。写隔离的主要实现是,在第一阶段本地事务提交之前,确保拿到全局锁 。如果拿不到全局锁,则不能提交本地事务 。并且获取全局锁的尝试会有一个范围限制,如果超出范围将会放弃全局锁的获取,并且回滚事务,释放本地锁 。
以一个具体的案例来分析,假设有两个全局事务tx1和tx2,分别对表的count字段进行更新操作,count的初始值为100
7.5.3.2 读隔离
我们知道数据库有如下4种隔离级别 。
在某些特定场景中要求事务隔离级别必须为Read,目前Seata是通过tor执行器对 FOR 语句进行代理的,FOR 语句在执行时会申请全局锁 。如图8-25所示,如果全局锁已经被其他分支事务持有,则释放本地锁(回滚 语句的本地执行)并重试 。在这个过程中,查询请求会被“”,直到全局锁被拿到,也就是读取的相关数据已提交时才返回 。
8 小结
这一章的篇幅较长,主要是在分布式事务领域有太多的理论模型,同时涉及分布式事务的场景也相对复杂一本章主要针对Seata中的AT事务模式进行了详细的讲解,它是Seata主推的一个分布式事务解决方案 。在使用AT模式时有一个前提,RM必须是支持本地事务的关系型数据库 。
Seata的AT模式基于本地事务的特性,通过拦截并解析SQL的方式,记录自定义的回滚日志 。虽然是根据XA事务模型演进而来的,但是它打破了XA协议阻塞性的制约,在一致性、性能、易用性3个方面取得了平衡:在达到确定一致性(非最终一致)的前提下,即保障较高的性能,又能完全不侵入业务 。
Seata支持TCC、AT、Saga,在大部分场景中,Seata的AT模式都适用 。同时可以根据实际需求选择TCC或者Saga等解决方案 。