分布式事务方案XA/JTA/2PC/3PC/TCC/SAGA

1. 基础概念

CAP理论

  • 一致性(Consistency) :在分布式系统中所有的数据备份,在同一时刻都保持一致状态,如无法保证状态一致,直接返回错误;
  • 可用性(Availability):在集群中一部分节点故障,也能保证客户端访问系统并得到正确响应,允许一定时间内数据状态不一致;
  • 分区容错性(Partition tolerance):分布式系统在遇到任何网络分区故障时,仍然能保证对外提供满足一致性和可用性的服务,除非整个网络环境都发生故障;

本地事务四大特性(ACID)

  • 原子性(atomicity):一个事务中的所有操作,不可分割,要么全部成功,要么全部失败;
  • 一致性(consistency):一个事务执行前与执行后数据的完整性必须保持一致;
  • 隔离性(isolation):一个事务的执行,不能被其他事务干扰,多并发时事务之间要相互隔离;
  • 持久性(durability):一个事务一旦被提交,它对数据库中数据的改变是永久性的。

BASE理论

基本可用(Basically Available):分布式系统在出现故障时,保证核心可用,允许损失部分可用性。(响应时间上的损失、功能上的损失
软状态(Soft State):系统中的数据允许存在中间状态,中间状态不影响系统的整体可用性。(支付中处理中等)
最终一致性(Eventually Consistent):系统中的数据不可一直处于软状态,必须在有时间期限,在期限过后应当保证数据的一致性。(支付中变为支付成功

  • 相比于本地事务的ADIC强一致性模型,BASE理论提出通过牺牲一定的强一致性来获得可用性;
  • 不同业务单元和业务组件对数据一致性的要求不一样,因此分布式系统中BASE理论和ACID特性会结合使用。

幂等性设计

  • 幂等(Idempotent)是一个数学与计算机学中的概念。f(n) = 1^n // 无论n等于多少,f(n)永远值等于1
  • 在程序中,使用相同参数执行同一个方法,每一次执行结果都是相同的,即具有幂等性
  • 以订单状态处理为例的幂等性设计(简单示例),不论执行多少次orderProcess()方法,都只会扣减一次库存,并且返回true
/** 订单处理 */
public boolean orderProcess(OrderEntity orderEntity){
 //查询原订单信息
 OrderEntity oldOrderEntity = getOrderInfo(orderEntity)
  //判断订单状态,如订单状态为 ok 则直接返回 true
 if("ok".equals(oldOrderEntity.getOrderState())){
     return true;
 }
 //更新订单状态
 boolean upBoolean = updateOrderInfo(orderEntity);
 if(!upBoolean){
   return false;
 }
 if("ok".equals(orderEntity.getOrderState())){
   //扣减库存
   boolean reduceBoolean = reduceInventories(orderEntity);
   if(!reduceBoolean){
     return false;
   }
 }
 return true;
}

2. 事务的演进过程

单体架构

对于一些小型项目,使用单体架构可以快速的开发,并且很容易控制我们的业务逻辑和事务处理。通过本地事务的ACID特性,保证我们数据的一致性。


单体架构

分库

实际项目中,我们可能会遇到一种情况就是,并发量不大,但常年累积下来数据量很大,这时候我们考虑到了分库策略。由于垮库情况下本地事务已经无法保证多库之间的数据一致性,这时我们就需要考虑分布式事务了。


分库

分布式架构

也有可能所使用的数据库性能比较好,但我们的单应用的性能无法满足业务需求,这时候我们可以选择对服务进行拆分的策略。此时虽然还是使用同一数据库,但我们多个服务之间互相调用来完成原有单体架构下的业务逻辑,这种情况下原有的本地事务也无法保证数据的一致性,这时我们也需要考虑分布式事务。


服务拆分

微服务架构

服务的拆分可以提高应用性能,让应用更专注于处理自己所负责的事情。数据库的拆分,一定程度上提升IO性能、数据库连接数、单机硬件资源的瓶颈。这时,服务间互相调用,每个服务都存在一个自己特定的业务数据库,所以我们需要考虑分布式事务。


服务拆分、数据库拆分

2. 分布式事务

XA规范(协议)

X/Open组织(现在的Open Group)定义了一套DTP(Distributed Transaction Processing)分布式事务处理模型,主要包含以下四部分:

  • AP(应用程序)
  • TM(事务管理器):交易中间件
  • RM(资源管理器):数据库
  • CRM(通信资源管理器):消息中间件

**XA规范**则是DTP模型定义TM和RM之间通讯的接口规范。XA接口函数由数据库厂商提供。TM用它来通知数据库事务的开始、结束、提交、回滚。基于XA规范衍生出下面的二阶段提交(2PC)、三阶段提交(3PC)。

XA规范包括两套函数,以xa_开头的及以ax_开头的。
以下的函数使事务管理器可以对资源管理器进行的操作:

  • xa_open,xa_close:建立和关闭与资源管理器的连接。
  • xa_start,xa_end:开始和结束一个本地事务。
  • xa_prepare,xa_commit,xa_rollback:预提交、提交、回滚一个本地事务。
  • xa_recover:回滚一个已进行预提交的事务。
  • ax_开头的函数使资源管理器可以动态地在事务管理器中进行注册,并可以对XID(TRANSACTION IDS)进行操作。
  • ax_reg,ax_unreg;允许一个资源管理器在一个TMS(TRANSACTION MANAGER SERVER)中动态注册或撤消注册。

XA的一些问题:

  • 性能(阻塞、响应时间增加、死锁);
  • 依赖于独立的J2EE中间件,WeblogicJboss,后期轻量级的AtomikosNarayanaBitronix
  • 不是所有资源(RM)都支持XA协议;

JTA(Java Transaction API)

即Java的事务API,基于XA实现,也就是RM需要支持XA,所以也有JTA(XA)的说法,JTA仅定义了接口。主要包括javax.sql.XADataResourcejavax.sql.XAConnectionjavax.sql.XAExceptionjavax.transaction.xa.XAResourcejavax.transaction.Xid
目下JTA的实现有几种形式:

  • J2EE容器提供的JTA实现(Weblogic、JBoss );
  • JOTM(Java Open Transaction Manager)、Atomikos,可独立于J2EE容器的环境下实现JTA;

二阶段提交(2PC)

2PC就是分布式事务中将事务分为两步进行提交。基于数据库的XA协议完成事务本质上就是二阶段提交(XA、JTA/JTS)。

  • 协调者(Coordinater):事务管理器(TM)
  • 参与者(participants):资源管理器(RM)
  • 准备阶段:
    协调者参与者发送prepare信息,以询问参与者是否能够提交事务;
    参与者在收到prepare信息后,进行本地事务的预处理,但不提交。并根据处理结果返回,失败not commit or 成功ready
  • 提交阶段:
    协调者收到参与者的失败消息,则向每个参与者发送rollback消息进行回滚;
    所有参与者都返回ready,则向每个参与者发送提交commit消息,通知参与者进行事务提交;

二阶段提交的一些问题:

  • 同步阻塞,事务执行过程中所有参与者都是阻塞型的,第三方参与者访问参与者占有的资源时会被阻塞;
  • 单点故障,协调者一旦发生故障,参与者会被阻塞。尤其在提交阶段,所有参与者都处于锁定资源状态中,无法完成事务操作;(可以选择新的协调者,但无法解决参与者被阻塞的问题);
  • 数据不一致,提交阶段协调者向参与者发送commit信息,发生局部网络故障,会导致存在参与者未收到commit信息无法提交事务情况,导致出现数据不一致现象;

三阶段提交(3PC)

相比于2PC,3PC把2PC的准备阶段再次进行拆分,并且3PC引入了参与者超时机制。

  • canCommit:协调者询问参与者,是否具备执行事务的条件,参与者进行自身事务必要条件的检查;
  • preCommit:协调者通知参与者进行事务的预提交;
  • doCommit:协调者根据preCommit阶段参与者的反馈结果通知参与者是否进行事务提交或是进行事务回滚;

事务补偿方案TCC

TCC的核心思想就是校验、资源锁定、补偿,对每个操作(Try)都提供确认(Confirm)和取消(cancel)的操作,这样根据操作的结果,来确认是进行Confirm还是Cancel。
可以看出XA的两阶段提交是基于资源层面的,而TCC也是一种两阶段提交,但它是基于应用层面的。

  • Try:主要负责对业务进行数据检查和资源预留,例如:对资金进行冻结;对状态更改为处理中
  • Confirm:确认执行业务的操作,例如:进行实际资金扣除;更改状态为最终结果
  • Cancel:取消执行业务的操作,例如:解冻资金;更改状态为未处理

TCC存在的一些问题:

  • 业务操作的是不同服务的Try来进行资源预留,每个Try都是独立完成本地事务,因此不会对资源一直加锁。
  • 业务服务需要提供try、confirm、cancel,业务侵入性强,如不适用三方框架要做到对各阶段状态的感知,比较麻烦。
  • 常用TCC框架:tcc-transactionByteTCCspring-cloud-rest-tccHimly
  • Confirm/Cancel要做幂等性设计。

常见的微服务系统大部分接口调用是同步的,这时候使用TCC来保证一致性是比较合适的。

Saga(补偿)

Saga的核心是补偿,与TCC不同的是Saga不需要Try,而是直接进行confirmcancel操作。

  • Confirm:依次按顺序依次执行资源操作,各个资源直接处理本地事务,如无问题,二阶段什么都不用做;
  • Cancel:异常情况下需要调用的补偿事务(逆操作)来保证数据的一致性。

可以看出,Saga和TCC有些类似,都是补偿型事务

优势:

  • 一阶段提交本地事务,无锁,高性能;
  • 事件驱动模式,参与者可异步执行,高吞吐;
  • 应用成本低,补偿服务易于实现;

劣势:

  • 无法保证隔离性(脏写)

可靠消息最终一致性(RocketMQ)

有一些情况,服务间调用时异步的,服务A将消息发送到MQ,服务B进行消息的消费。这时我们就需要用到可靠消息最终一致性来解决分布式事务问题。首先字面理解,

  • 可靠消息:即这个消息一定是可靠的,并且最终一定需要被消费的。
  • 最终一致性:过程中数据存在一定时间内的不一致,但超过限定时间后,需要最终会保持一致。

确保以上两点情况下,通过消息中间件(RocketMQ)来完成分布式事务处理,因为RocketMQ支持事务消息,可以方便的让我们进行分布式事务控制。
因此首先需要了解一下,RocketMQ的事务消息的原理。


摘自网络

half message:半消息,此时消息不能被consumer所发现和消费,需producer进行二次消息确认。

    1. producer发送half messageMQ Server
    1. producer根据MQ Server应答结果判断half message是否发送成功;
    1. producer处理本地事务;
    1. producer发送最终确认消息commit / rollback
      commitconsumer对消息可见并进行消费;
      rollbackdiscard抛弃消息,consumer无法进行消息消费;
    1. 如遇异常情况下step4最终确认消息为达到MQ ServerMQ Server会定期查询当前处于半消息状态下的消息,主动进行消息回查来询问producer该消息的最终状态;
    1. producer检查本地事务执行的最终结果;
    1. producer根据检查到的结果,再次提交确认消息,MQ Server仍然按照step4进行后续操作。

事务消息发送对应步骤1、2、3、4,事务消息回查对应步骤5、6、7。
由以上步骤可以看出通过事务性消息的两步操作,避免了消息直接投递所产生一些问题。最终投递到MQ Server的消息,是真实可靠且必须被消费的。

Seata

阿里开源的Seata 是一款分布式事务解决方案,提供了 AT、TCC、SAGA 和 XA 事务模式。

Seata架构的亮点主要有几个:

  • 应用层基于SQL解析实现了自动补偿,从而最大程度的降低业务侵入性;
  • 将分布式事务中TC(事务协调者)独立部署,负责事务的注册、回滚(支持多种注册中心形式以及本地文件形式);
  • 通过全局锁实现了写隔离与读隔离。

结尾

在实际业务开发中,考虑设计一套好分布式事务框架,需要根据具体业务情况结合上述一些理论,进行权衡取舍。
需要考虑的特性

  • 实现复杂度:事务模式与当前业务结合,实施成本是否过高;
  • 业务侵入性:基于注解、XML、补偿逻辑;
  • TC/TM部署:独立部署、与应用部署;
  • 性能:回滚概率、回滚所付出的代价、响应时间、吞吐量;
  • 高可用:数据库、注册中心、配置中心
  • 持久化:文件、数据库;
  • 同步/异步:分布式事务执行过程中是否阻塞,还是非阻塞;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269