一、基础概念
CAP理论
一致性(Consistency) :在分布式系统中所有的数据备份,在同一时刻都保持一致状态,如无法保证状态一致,直接返回错误;
可用性(Availability):在集群中一部分节点故障,也能保证客户端访问系统并得到正确响应,允许一定时间内数据状态不一致;
分区容错性(Partition tolerance):分布式系统在遇到任何网络分区故障时,仍然能保证对外提供满足一致性和可用性的服务,除非整个网络环境都发生故障;
本地事务四大特性(ACID)
原子性(atomicity):一个事务中的所有操作,不可分割,要么全部成功,要么全部失败;
一致性(consistency):一个事务执行前与执行后数据的完整性必须保持一致;
隔离性(isolation):一个事务的执行,不能被其他事务干扰,多并发时事务之间要相互隔离;
持久性(durability):一个事务一旦被提交,它对数据库中数据的改变是永久性的。
BASE理论
BASE是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的结论,是基于CAP定理逐步演化而来的,其核心思想是即使无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性
基本可用(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;
}
二、事务的演进过程
1、单体架构
对于一些小型项目,使用单体架构可以快速的开发,并且很容易控制我们的业务逻辑和事务处理。通过本地事务的ACID特性,保证我们数据的一致性。
2、分库
实际项目中,我们可能会遇到一种情况就是,并发量不大,但常年累积下来数据量很大,这时候我们考虑到了分库策略。由于垮库情况下本地事务已经无法保证多库之间的数据一致性,这时我们就需要考虑分布式事务了。
3、分布式架构
也有可能所使用的数据库性能比较好,但我们的单应用的性能无法满足业务需求,这时候我们可以选择对服务进行拆分的策略。此时虽然还是使用同一数据库,但我们多个服务之间互相调用来完成原有单体架构下的业务逻辑,这种情况下原有的本地事务也无法保证数据的一致性,这时我们也需要考虑分布式事务。
4、微服务架构
服务的拆分可以提高应用性能,让应用更专注于处理自己所负责的事情。数据库的拆分,一定程度上提升IO性能、数据库连接数、单机硬件资源的瓶颈。这时,服务间互相调用,每个服务都存在一个自己特定的业务数据库,所以我们需要考虑分布式事务。
三. 分布式事务
1、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中间件,Weblogic、Jboss,后期轻量级的Atomikos、Narayana、Bitronix;
不是所有资源(RM)都支持XA协议;
2、JTA(Java Transaction API)
即Java的事务API,基于XA实现,也就是RM需要支持XA,所以也有JTA(XA)的说法,JTA仅定义了接口。主要包括javax.sql.XADataResource、javax.sql.XAConnection、javax.sql.XAException、javax.transaction.xa.XAResource、javax.transaction.Xid。
目下JTA的实现有几种形式:
J2EE容器提供的JTA实现(Weblogic、JBoss );
JOTM(Java Open Transaction Manager)、Atomikos,可独立于J2EE容器的环境下实现JTA;
3、二阶段提交(2PC)
2PC就是分布式事务中将事务分为两步进行提交。基于数据库的XA协议完成事务本质上就是二阶段提交(XA、JTA/JTS)。
协调者(Coordinater):事务管理器(TM)
参与者(participants):资源管理器(RM)
准备阶段:
协调者向参与者发送prepare信息,以询问参与者是否能够提交事务;
参与者在收到prepare信息后,进行本地事务的预处理,但不提交。并根据处理结果返回,失败not commit or 成功ready ;
提交阶段:
如协调者收到参与者的失败消息,则向每个参与者发送rollback消息进行回滚;
所有参与者都返回ready,则向每个参与者发送提交commit消息,通知参与者进行事务提交;
二阶段提交的一些问题:
同步阻塞,事务执行过程中所有参与者都是阻塞型的,第三方参与者访问参与者占有的资源时会被阻塞;
单点故障,协调者一旦发生故障,参与者会被阻塞。尤其在提交阶段,所有参与者都处于锁定资源状态中,无法完成事务操作;(可以选择新的协调者,但无法解决参与者被阻塞的问题);
数据不一致,提交阶段协调者向参与者发送commit信息,发生局部网络故障,会导致存在参与者未收到commit信息无法提交事务情况,导致出现数据不一致现象;
4、三阶段提交(3PC)
相比于2PC,3PC把2PC的准备阶段再次进行拆分,并且3PC引入了参与者超时机制。
canCommit:协调者询问参与者,是否具备执行事务的条件,参与者进行自身事务必要条件的检查;
preCommit:协调者通知参与者进行事务的预提交;
doCommit:协调者根据preCommit阶段参与者的反馈结果通知参与者是否进行事务提交或是进行事务回滚;
5、事务补偿方案TCC
TCC的核心思想就是校验、资源锁定、补偿,对每个操作(Try)都提供确认(Confirm)和取消(cancel)的操作,这样根据操作的结果,来确认是进行Confirm还是Cancel。
可以看出XA的两阶段提交是基于资源层面的,而TCC也是一种两阶段提交,但它是基于应用层面的。
Try:主要负责对业务进行数据检查和资源预留,例如:对资金进行冻结;对状态更改为处理中;
Confirm:确认执行业务的操作,例如:进行实际资金扣除;更改状态为最终结果;
Cancel:取消执行业务的操作,例如:解冻资金;更改状态为未处理;
TCC存在的一些问题:
业务操作的是不同服务的Try来进行资源预留,每个Try都是独立完成本地事务,因此不会对资源一直加锁。
业务服务需要提供try、confirm、cancel,业务侵入性强,如不适用三方框架要做到对各阶段状态的感知,比较麻烦。
常用TCC框架:tcc-transaction、ByteTCC、spring-cloud-rest-tcc、Himly
Confirm/Cancel要做幂等性设计。
常见的微服务系统大部分接口调用是同步的,这时候使用TCC来保证一致性是比较合适的。
6、Saga(补偿)
Saga的核心是补偿,与TCC不同的是Saga不需要Try,而是直接进行confirm、cancel操作。
Confirm:依次按顺序依次执行资源操作,各个资源直接处理本地事务,如无问题,二阶段什么都不用做;
Cancel:异常情况下需要调用的补偿事务(逆操作)来保证数据的一致性。
可以看出,Saga和TCC有些类似,都是补偿型事务
优势:
一阶段提交本地事务,无锁,高性能;
事件驱动模式,参与者可异步执行,高吞吐;
应用成本低,补偿服务易于实现;
劣势:
无法保证隔离性(脏写)
7、可靠消息最终一致性(RocketMQ)
有一些情况,服务间调用时异步的,服务A将消息发送到MQ,服务B进行消息的消费。这时我们就需要用到可靠消息最终一致性来解决分布式事务问题。首先字面理解,
可靠消息:即这个消息一定是可靠的,并且最终一定需要被消费的。
最终一致性:过程中数据存在一定时间内的不一致,但超过限定时间后,需要最终会保持一致。
确保以上两点情况下,通过消息中间件(RocketMQ)来完成分布式事务处理,因为RocketMQ支持事务消息,可以方便的让我们进行分布式事务控制。
事务消息发送对应步骤1、2、3、4,事务消息回查对应步骤5、6、7。
8、Seata
阿里开源的Seata 是一款分布式事务解决方案,提供了 AT、TCC、SAGA 和 XA 事务模式。
Seata架构的亮点主要有几个:
应用层基于SQL解析实现了自动补偿,从而最大程度的降低业务侵入性;
将分布式事务中TC(事务协调者)独立部署,负责事务的注册、回滚(支持多种注册中心形式以及本地文件形式);
通过全局锁实现了写隔离与读隔离。
四、结尾
在实际业务开发中,考虑设计一套好分布式事务框架,需要根据具体业务情况结合上述一些理论,进行权衡取舍。
需要考虑的特性
实现复杂度:事务模式与当前业务结合,实施成本是否过高;
业务侵入性:基于注解、XML、补偿逻辑;
TC/TM部署:独立部署、与应用部署;
性能:回滚概率、回滚所付出的代价、响应时间、吞吐量;
高可用:数据库、注册中心、配置中心
持久化:文件、数据库;
同步/异步:分布式事务执行过程中是否阻塞,还是非阻塞;
评论区