seata-分布式事务与seata

分布式事务与 Seata

分布式事务

分布式事务是个现实中很常见的现象,平常的跨行转帐就是一个很典型的分布式事务。java

现实中,每一个银行各自管理各自的帐户,在执行跨行转帐时,须要确保转出帐户扣费正确,转入帐户增长正确的金额。在电子渠道上操做看着很简单,其后台须要执行分布式事务的处理流程有不少步骤,若是帐户不平,还须要进行人工对帐,中间涉及到一系列的制度和机制。这里暂且不涉及电子交易的安全可信的问题,只讨论分布式事务。算法

转帐的数字形式上无非是一系列的电子信号,可是它是用户的财富的事实表明,其转移有法律上的意义,银行不能随意改动(理论上),任何的修改都是须要有事实依据的,是获得用户受权的。正如经典数据库的说法,银行的数据库存储的是一系列事实。sql

在早期,没有银行间共同信任的中间机构时,银行要与其它银行转帐时,必需要在对方开设一个银行专属的帐户,转帐时变成了从这个专属帐户交易到普通帐户,而后银行间再定时进行结算。这种方式会致使 n 个银行之间相互转帐时,须要 n^2 个专属帐户,试想 1 万间银行的状况,因此基本没法在大量银行间进行维护。数据库

而后银行间共同组织起共同信任的中间机构,如中国银联和 SWIFT ,你们都经过中间机构进行转帐(仍是会有须要银行之间直接对接的状况的)。在转帐时,可能会出现各类各样的状况:安全

1. 扣款失败
2. 扣款成功,通知失败
3. 扣款成功,通知成功,入帐失败
4. 扣款成功,通知成功,用户要求撤回
5. ...

这个场景下,涉及到多方参与者,为保证整个交易事实的正确一致,必须知足多方都成功或撤销,就是一个分布式的事务。服务器

为实现分布事务的问题,有一些协议:两阶段提交、三阶段提交、事务补偿、saga 等等。网络

两阶段提交(2pc)

2pc 认为,既然各方参与者都没法肯定对方的状态,那么引入一个事务协调者来统一安排参与者的操做吧。多线程

1. 协调者首先向全部参与者发一个准备消息 prepare ,参与者在本地进行预处理事务,并报告可否执行
2.1 协调者收到全部参与者的确认消息,那么就发一个 global_commit 消息,全部参与者进行提交
2.2 协调者收到有任意的参与者预处理失败,或者参与者超时,就发一个 global_rollback 消息,全部参与者取消

但 2pc 没有想到的是,这个世界充满了不肯定性,任何的节点均可能会出现问题:架构

  1. 同步阻塞,全部参与者都是事务阻塞型,公共资源会处于阻塞状态
  2. 单点故障,协调者发出 prepare 后异常,那么全部参与者都在处理本地事务挂起状态
  3. 不一致,部分参与者节点在 global_commit 前异常,这部分事务没有提交,致使局部数据不一致
  4. 全局事务状态没法肯定,一旦协调者异常,即便出现新的协调者,也是没法肯定以前的事务状态

因而,在 2pc 的基础上,又推出了三阶段提交(3pc)app

三阶段提交(3pc)

2pc 没法肯定协调者状态,在 3pc 时为协议者也引入超时机制;针对同步阻塞和不一致问题,增长一个准备阶段。整个过程为成 can_commit、pre_commit、do_commit(abort) 3个阶段。

1. 协调者发 can_commit,参与者检查,类型于 prepare
2. 协调者发 pre_commit,参与者开始本地事务,写 undo 日志
3.1 全部参与者成功 pre_commit,协调者发 do_commit,参与者提交,清理 undo
3.2 任意参与者失败或超时,协调者发 abort,参与者回滚并释放资源
3.3 协调者超时,参与者默认 commit

若是出现同步阻塞,那么会在 2 出现失败,全局回滚。参与者默认 commit ,使得只要 pre_commit 阶段成功,那么分布式事务就能默认提交。

可是这里还存在数据不一致问题,若是 pre_commite 阶段,部分参与者成功,部分失败,而成功参与者没有收到后续的 abort ,会默认提交,形成不一致。

分布式一致性算法,目前好像基本上都是 Paxos 的变种,它实现的也不是所有都一致,是基于少数服从多数的原则,在使用场景上有所区别,多见于存储系统中。zookeeper 就是基于 paxos 的实现。

CAP 原理

CAP 原理描述的在分布式中,3种属性:一致性 consistency, 可用性 avaliablity, 分区容错 partition tolerance,没法 3个同时知足。

以转帐为例,A 系统扣款,通知 B 系统进行入帐,有可能出现网络中断致使通知失败的状况,A 能够采起:

1. 放弃可用性,禁止对这个帐户进行的任何操做,直到获得 B 确认
2. 放弃强一致性,后台任务继续通知 B 系统,同时容许对帐户的继续操做

可是没法放弃分区容错的,在分布式系统中,必然是有多个节点的,也就是 P 是必然,所以一个分布式系统要么以 CP 、要么以 AP 为设计目标。在不一样的应用中,会采用不一样的策略,像 zookeeper 用的 cp ,而 eureka 以 ap 为目标。

通常而言,实现分布式强一致的代价会比较高,并会致使整个服务暂时不可用。试想下,一笔转帐尚未获得确认前,全部的其它的动帐操做都不可执行,怎么向脾气火爆的客户解析系统不支持继续操做,只由于对方系统恰好在发出了交易消息后挂了!固然,也不能让转帐的事实丢失,扣款后对方系统没有入帐,要作些补偿操做,实现最终一致性。

所谓的最终一致性,即系统是某个时间段存在不一致性,可是过了此段时间后会是一致的(经过一些补偿操做)。最终一致性只在分布式下有意义,其在本地也是强一致性的,不然没法作补偿。为此,又提出了 BASE 模型

BASE 模型

BASE 模型从另外的角度来看待分布式系统,提出 3 方面的想法:

1. 基本可用 Basically avaliable
2. 软状态(柔性事务) Soft-state
3. 最终一致 Eventually consitence

BASE 模型不追求经典数据库系统的 ACID ,而是和分布式系统的特色结合起来,在可用性、一致性上争取平衡。

  1. 基本可用

    容许分布式系统的若干节点出现不可预测的问题,在设计时应该在部分节点出现问题时,系统能够以低一点的效率运行,系统其它部分还能提供基本的服务。

    在这样的设计约束下,基本上没法追求强一致性的目标,不然就会把全部的服务锁死。

  2. 软状态(柔性事务)

    容许数据存在中间状态,在中间状态下暂时不一致。中间状态为最终一致服务,可提交或撤销。

  3. 最终一致

    系统必须能在某个时间内实现最终一致性。对于同一个逻辑全局事务,各分支事务必须都提交或都撤销。通常分红几种:

    1. 因果一致性
         A 节点操做后致使 B 节点操做,那么 B 操做时得到的状态是 A 的结果状态,和 A 无关的操做则无此要求
     2. 读本身已写
         同一个节点老是能读到本身已处理后的结果状态,属于特殊的因果一致性,也可理解为单节点本身的本地一致性
     3. 会话一致性
         同一个会话中,客户端老是能读到本身更新的最新状态,这是要求比较高的因果一致性
     4. 单调读一致性
         在得到某个读取状态以后,全部的读取的状态都不能取得比此状态更旧的状态,也就是读取的结果只能愈来愈新
     5. 单调写一致性
         系统保证来自同一节点的写操做老是按顺序执行

要留意到一点,并不能保证全部节点的写一致性。时间的肯定在分布式系统中是比较复杂的问题,在单点系统中,系统只有一个时间源,操做的前后顺序是比较肯定的,即便是多线程的操做中,时间相差通常不会超过 100 毫秒级(从取当前时间值到用此时间进行写操做);但分布系统时间中,各个节点都要依赖于本身的时间源,即便使用 ntp 进行定时校对,也会比单节点系统的时间差别大,网络的延迟更加放大了时间的差别,通常在收到交易的时候要分别记上客户端时间和服务器时间,并抛弃时间差别过大的交易。

好了,有了这些基础能够去看看 seata 框架了。

seata 框架

seata 是 alibaba 开源的一套分布式事务方案,同时支持 TCC/AT/SAGA/XA 等多种分布式事务模型。它由 alibaba 的 fescar 升级而来,并合并了一些其它的技术。

seata 适合于微服务架构,其云版本 GTS 是 aliyun 企业分布式应用服务 EDAS 的一个组件。

基本概念

TC transaction coordinator 事务协调器

维护全局事务的运行状态、协调和驱动全局事务的提交或回滚

TM transaction manager 事务管理器

控制全局事务的边界,负责开启全局事务,并最终发起全局提交或回滚动

RM resource manager 资源管理器

控制子事务,负责子事务开启和状态管理,接收 TC 的指令,驱动子事务的提交和回滚动。

GT global transaction 全局事务

在全局范围内执行的事务

BT branch transaction 分支事务(本地事务)

在本地范围内执行的事务

seata 分红两个部分,seata server 和 seata client。

seata server 是一个独立的可运行组件,担当 TC 的角色,它能够注册到微服务的注册中心,被 client 发现和调用。

AT 模式

AT automatic transaction 自动事务模式,AT 模式基于 XA 演化,是 2PC 的改进,须要本地数据库支持的 ACID 。

须要分支事务支持 ACID,每一个分支事务独立地完成工做,基本流程:

1. prepare 准备
2. 提交或回滚

基本条件:1) 本地数据库支持 ACID ;2) java 应用使用 jdbc 驱动访问数据库

seata 基于 jdbc 分析 sql 条件,从而肯定 sql 的执行范围,在执行前、执行后分别得到数据的镜像数据,并写入到 undo_log 中,从而在失败时进行回滚。

机制

阶段1:提交业务数据 + 建立 undo 日志,而后释放锁和链接资源;

阶段2:
结果为 commit ,那么异步提交各个主事务;
结果为 rollback ,进行补偿,使用阶段1的回滚日志进行回滚;

写隔离

在阶段1提交前,先要得到一个全局锁,若是阶段1获取全局锁失败,则不能提交,能够进行屡次尝试,超时以后取消本地事务。

两个写事务时,单个流程以下:

tx1: 
启动本地事务,本地锁
update m=m-100 where id=1
获取全局锁
本地事务提交
释放本地锁
全局提交
释放全局锁

没有脏写问题

读隔离

本地数据库读已提交,无锁状况下,全局读未提交(若是改成读已提交,须要使用 select for update,至关于启动一次写事务)

select for update 得到一个全局锁,从而实现读已提交

执行过程

对 product 表执行 update product set name = 'GTS' where name = 'TXC'

过程以下:

阶段1

  1. 预备 sql,经过 sql 得到类型为 update,表名 product,经过 where 子句查出数据行,并取出数据

    进行数据定位

    select id, name, sice from productt where name = 'TXC'

    经过表结构,可得知 id 为键,并获得执行前数据镜像

    id  |  name | since
     1   |  TXC  | 2014
  2. 执行 update 语句,而后再次查询获得更新后的数据镜像

    查询语句

    select id, name, since from product where id = 1;

    结果

    id  | name  | since
     1   | GTS   | 2014
  3. 建立一个 undo_log (回滚日志),标记事务 id ,执行前、执行后结果

    日志格式

    {
         "branchId": 641789253,
         "undoItems": [{
             "afterImage": {
                 "rows": [{
                     "fields": [{
                         "name": "id",
                         "type": 4,
                         "value": 1
                     }, ...}],
                 }],
                 "tableName": "product"
             },
             "beforeImage": {
                 "rows": [{
                     "fields": [{
                         "name": "id",
                         "type": 4,
                         "value": 1
                     }, ...}]
                 }],
                 "tableName": "product"
             },
             "sqlType": "UPDATE"
         }],
         "xid": "xid:xxx"
     }
  4. 提交前,经过 TC 得到 product 表全局锁,主键为 1

  5. 提交本地事务,更新 product 表和 undo_log 表,向 TC 汇报执行结果

阶段2

若是节点收到 TC 的 rollback 通知,那么经过 xid 和 子事务 id 找到 undo_log 日志,比较当前状态和 afterImage 的数据,若是一致则进行回滚

若是不一致,就要通知 TC

收到 commit 通知,那么把请求直接放到一个队列,立刻返回,再异步在本地逐个删除队列的 undo_log 日志。

undo_log 格式

与数据库类型相关,不一样数据库有点不同,但基本构成都是 branch_id ,xid ,beforeImage/afterImage (rollback_info),时间 等。

TCC 模式

AT 借助了数据库的 ACID 特性,但分布式事务中,部分步骤不具有 ACID 能力,如长时间的人工操做、库存清点等,TCC (try-comfirm-cancel) 模式比较
适合这种场景。

TCC 须要一个微服务提供 3 种操做:

1. try 资源预留,扣减资源并建立 undo_log
2. commit 成功提交,删除 undo_log
3. cancel 取消,根据 undo_log 进行回滚

流程

TM -> TC : 启动 GT,获得 xid
服务 -> RM : prepare
RM -> TC : 注册 branch
RM -> TC : branch 状态报告
TC -> RM : commit / rollback

SAGA 模式

SAGA 是对长耗时事务的解决方案,好比一些交易步骤须要到人工参与的审核,或须要调用外部服务时,就是 saga 的合适场景。它与 TCC 不一样,没有 prepare 动做。 见 https://www.jianshu.com/p/e4b662407c66?from=timeline&isappinstalled=0

每一个 SAGA 由一系列的子事务组成,为 T1,T2..Tn ,每一个 Ti 对应一个补偿动做 Ci,用于撤销 Ti 形成的结果。

执行时,执行 T1,T2..Tn,若是 Ti 出错,那么就要进行倒过来执行补偿动做,以撤销前 i-1 个结果(栈方式)。

每一个本地事务都是原子的,可是全局不能实现读写隔离。

用于与外部系统集成、或其它组件不支持 TCC 的状况。

相关文章
相关标签/搜索