赵俊java
京东金融mysql
高级Java开发工程师git
一切从ACID开始提及。ACID是本地事务所具备的四大特征:github
事务做为总体来执行,要么所有执行,要么全不执行。golang
事务应确保数据从一个一致的状态转变为另外一个一致的状态。sql
多个事务并发执行时,一个事务的执行不该影响其余事务的执行。数据库
已提交的事务修改数据会被持久保持。apache
关系型数据库的本地事务完美的提供了对ACID的原生支持。但在分布式的场景下,它却成为系统性能的桎梏。如何让数据库在分布式场景下知足ACID的特性或找寻相应的替代方案,是本文将要阐述的话题。后端
对于互联网应用而言,随着访问量和数据量的激增,传统的单体架构模式将没法知足业务的高速发展。这时,开发者须要把单体应用拆分为多个独立的小应用,把单个数据库按照分片规则拆分为多个库和多个表。服务器
数据拆分后,如何在多个数据库节点间保证本地事务的ACID特性则成为一个技术难题,而且由此而衍生出了CAP和BASE经典理论。
CAP理论指出,对于分布式的应用而言,不可能同时知足C(一致性),A(可用性),P(分区容错性),因为网络分区是分布式应用的基本要素,所以开发者须要在C和A上作出平衡。
因为C和A互斥性,其权衡的结果就是BASE理论。
对于大部分的分布式应用而言,只要数据在规定的时间内达到最终一致性便可。咱们能够把符合传统的ACID叫作刚性事务,把知足BASE理论的最终一致性事务叫作柔性事务。
一味的追求强一致性,并不是最佳方案。对于分布式应用来讲,刚柔并济是更加合理的设计方案,即在本地服务中采用强一致事务,在跨系统调用中采用最终一致性。如何权衡系统的性能与一致性,是十分考验架构师与开发者的设计功力的。
具体到分布式事务的实现上,业界主要采用了XA协议的强一致规范以及柔性事务的最终一致规范。
XA是X/Open CAE Specification (Distributed Transaction Processing)模型中定义的TM(Transaction Manager)与RM(Resource Manager)之间进行通讯的接口。
Java中的javax.transaction.xa.XAResource定义了XA接口,它依赖数据库厂商对jdbc-driver的具体实现。
mysql-connector-java-5.1.30的实现可参考:
com.mysql.jdbc.jdbc2.optional.MysqlXAConnection。
在XA规范中,数据库充当RM角色,应用须要充当TM的角色,即生成全局的txId,调用XAResource接口,把多个本地事务协调为全局统一的分布式事务。
一阶段提交:弱XA
弱XA经过去掉XA的Prepare阶段,以达到减小资源锁定范围而提高并发性能的效果。典型的实现为在一个业务线程中,遍历全部的数据库链接,依次作commit或者rollback。弱XA同本地事务相比,性能损耗低,但在事务提交的执行过程当中,若出现网络故障、数据库宕机等预期以外的异常,将会形成数据不一致,且没法进行回滚。基于弱XA的事务无需额外的实现成本,所以Sharding-Sphere默认支持。
二阶段提交:2PC
二阶段提交是XA的标准实现。它将分布式事务的提交拆分为2个阶段:prepare和commit/rollback。
开启XA全局事务后,全部子事务会按照本地默认的隔离级别锁定资源,并记录undo和redo日志,而后由TM发起prepare投票,询问全部的子事务是否能够进行提交:当全部子事务反馈的结果为“yes”时,TM再发起commit;若其中任何一个子事务反馈的结果为“no”,TM则发起rollback;若是在prepare阶段的反馈结果为yes,而commit的过程当中出现宕机等异常时,则在节点服务重启后,可根据XA recover再次进行commit补偿,以保证数据的一致性。
2PC模型中,在prepare阶段须要等待全部参与子事务的反馈,所以可能形成数据库资源锁定时间过长,不适合并发高以及子事务生命周长较长的业务场景。
Sharding-Sphere支持基于XA的强一致性事务解决方案,能够经过SPI注入不一样的第三方组件做为事务管理器实现XA协议,如Atomikos和Narayana。
柔性事务是对XA协议的妥协和补偿,它经过对强一致性要求的下降,已达到下降数据库资源锁定时间的效果。柔性事务的种类不少,能够经过各类不一样的策略来权衡使用。
一阶段提交 + 补偿 :最大努力送达(BED)
最大努力送达,是针对于弱XA的一种补偿策略。它采用事务表记录全部的事务操做SQL,若是子事务提交成功,将会删除事务日志;若是执行失败,则会按照配置的重试次数,尝试再次提交,即最大努力的进行提交,尽可能保证数据的一致性,这里能够根据不一样的业务场景,平衡C和A,采用同步重试或异步重试。
这种策略的优势是无锁定资源时间,性能损耗小。缺点是尝试屡次提交失败后,没法回滚,它仅适用于事务最终必定可以成功的业务场景。所以BED是经过事务回滚功能上的妥协,来换取性能的提高。
TCC: Try-Confirm-Cancel
TCC模型是把锁的粒度彻底交给业务处理,它须要每一个子事务业务都实现Try-Confirm/Cancel接口。
尝试执行业务;
完成全部业务检查(一致性);
预留必须业务资源(准隔离性);
确认执行业务;
真正执行业务,不做任何业务检查;
只使用Try阶段预留的业务资源;
Confirm操做知足幂等性;
取消执行业务;
释放Try阶段预留的业务资源;
Cancel操做知足幂等性。
这三个阶段都会按本地事务的方式执行,不一样于XA的prepare,TCC无需将XA的投票期间的全部资源挂起,所以极大的提升了吞吐量。
下面对TCC模式下,A帐户往B帐户汇款100元为例子,对业务的改造进行详细的分析:
汇款服务和收款服务分别须要实现,Try-Confirm-Cancel接口,并在业务初始化阶段将其注入到TCC事务管理器中。
汇款服务
检查A帐户有效性,即查看A帐户的状态是否为“转账中”或者“冻结”;
检查A帐户余额是否充足;
从A帐户中扣减100元,并将状态置为“转帐中”;
预留扣减资源,将从A往B帐户转帐100元这个事件存入消息或者日志中;
不作任何操做;
A帐户增长100元;
从日志或者消息中,释放扣减资源。
收款服务
检查B帐户帐户是否有效;
读取日志或者消息,B帐户增长100元;
从日志或者消息中,释放扣减资源;
不作任何操做。
由此能够看出,TCC模型对业务的侵入强,改造的难度大。
消息驱动
消息一致性方案是经过消息中间件保证上下游应用数据操做的一致性。基本思路是将本地操做和发送消息放在一个事务中,下游应用向消息系统订阅该消息,收到消息后执行相应操做。本质上是依靠消息的重试机制,达到最终一致性。消息驱动的缺点是:耦合度高,须要在业务系统中引入MQ,致使系统复杂度增长。
SAGA
Saga起源于1987年Hector & Kenneth发表的论文Sagas。
参考地址:
https://www.cs.cornell.edu/an...
Saga工做原理
Saga模型把一个分布式事务拆分为多个本地事务,每一个本地事务都有相应的执行模块和补偿模块( TCC中的Confirm和Cancel)。当Saga事务中任意一个本地事务出错时,能够经过调用相关的补偿方法恢复以前的事务,达到事务最终的一致性。
当每一个Saga子事务 T1, T2, …, Tn 都有对应的补偿定义 C1, C2, …, Cn-1,那么Saga系统能够保证:
因为Saga模型中没有Prepare阶段,所以事务间不能保证隔离性,当多个Saga事务操做同一资源时,就会产生更新丢失、脏数据读取等问题,这时须要在业务层控制并发,例如:
Saga恢复方式
Saga支持向前和向后恢复:
显然,向前恢复没有必要提供补偿事务,若是你的业务中,子事务(最终)总会成功,或补偿事务难以定义或不可能,向前恢复更符合你的需求。理论上补偿事务永不失败,然而,在分布式世界中,服务器可能会宕机、网络可能会失败,甚至数据中心也可能会停电,这时须要提供故障恢复后回退的机制,好比人工干预。
总的来讲,TCC和MQ都是以服务为范围进行分布式事务的处理,而XA、BED、SAGA则是以数据库为范围进行分布式处理,咱们更趋向于选择后者,对于业务而言侵入小,改造的成本低。
Sharding-Sphere是一套开源的分布式数据库中间件解决方案组成的生态圈,它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar这3款相互独立的产品组成。它们均提供标准化的数据分片、读写分离、柔性事务和数据治理功能,可适用于如Java同构、异构语言、容器、云原生等各类多样化的应用场景。
项目地址:
https://github.com/sharding-s...
Sharding-Sphere同时支持XA和柔性事务,它容许每次对数据库的访问,能够自由选择事务类型。分布式事务对业务操做彻底透明,极大地下降了引入分布式事务的成本。
Sharding-Sphere事务管理器集成了XA和柔性事务模型:
下面将Sharding-Sphere内部如何用事件驱动方式,将事务从分片主流程中解耦进行详细说明:
从图能够看出在Sharding-core在调用执行引擎时,会根据SQL的种类产生事件进行分发。事务监听线程在收到符合要求的事件后,再调用对应的事务处理器进行处理。
Sharding-Proxy是基于netty开发的数据库中间代理层,实现了标准的MySQL协议,能够看作是一个实现了数据分片的数据库。Sharding-Proxy已经实现了基于Atomikos的XA事务,为了保证全部的子事务都处于同一个线程之中,整个Proxy的线程模型进行了以下的调整:
当开启事务后,Proxy后端的SQL命令执行引擎将采用一通道一线程的模式,此事务线程的生命周期同通道保持一致。事务处理的具体过程与Proxy完全解耦,即Proxy将发布事务类型的事件,而后Sharding-Sphere-TM根据传入的事务消息,选择具体的TM进行处理。
压测结果代表:XA事务的插入和更新的性能,基本上同跨库的个数呈线性关系,查询的性能基本不受影响,建议在并发量不大,每次事务涉及的库在10个之内时,可使用XA。
Atomikos事务管理器原理分析
Atomikos的事务管理器能够内嵌到业务进程中,当应用调用TransactionManager.begin时,将会建立本次XA事务,而且与当前线程关联。同时Atomikos也对DataSource中的connection作了二次封装,代理connection中含有本次事务相关信息的状态,而且拦截了connection的JDBC操做。
在createStatement时,调用XAResource.start进行资源注册;在close时,调用XAResource.end让XA事务处于idel可提交状态;在commit或rollback时,依次调用prepare和commit进行二阶段提交。
Sharding-Sphere的Saga事务实现
Sharding-Sphere经过与Apache Service Comb的合做,将采用Service Comb的Saga事务引擎做为的分布式事务实现。
Apache Service Comb是华为开源的微服务框架,其中微服务事务处理框架分为集中式和分布式协调器。将来会在Sharding-Sphere内部集成Saga集中式协调器,支持同一线程内不一样服务(本地)间的分布式事务。
参考连接:
https://github.com/apache/inc...
Service Comb 集中式事务协调器
集中式的协调器,包含了Saga调用请求接收、分析、执行以及结果查询的内容。任务代理模块须要预先知道Saga事务调用关系图,执行模块根据生成的调用图产生调用任务,调用相关微服务服务接口。若是服务调用执行出错,会调用服务的相关的补偿方法回滚。
Saga执行模块经过分析请求的JSON数据,来构建一个调用关系图。Sharding-Sphere是经过JSON描述Saga事务串行调用子事务或者并行调用子事务。关系调用图被Saga实现中的任务运行模块分解成为一个一个执行任务,执行任务由任务消费者获取并生成相关的调用 (同时支持串行和并行调用)。Saga任务会根据执行的状况向Saga Log中记录对应的Saga事务的关键事件,并能够经过事件查看器查查询执行状况。
Sharding-Sphere内嵌Saga事务管理器
Saga以jar包的形式提供分布式事务治理能力。
对Sharding-Sphere而言,confirm和cancel过程表明了子事务中的正常执行SQL和逆向执行SQL,(将来Sharding-Sphere将提供自动生成逆向SQL的能力)。当启用Saga柔性事务后,路由完成以后的物理数据源将开启本地自动提交事务,每次confirm和cancel都会直接提交。
在Sharding-Sphere内部,触发SQL执行引擎后,将会产生Saga事务事件,这时Sharding-Sphere事务监听器会注册本次子事务的confirm和cancel至Saga事务管理器的队列中;在业务线程触发commit和rollback后,Saga事务管理器再根据子事务执行的结果,判断进行confirm重试或者cancel流程。
将来Sharding-Sphere将按照文中介绍的Sharding-Sphere-TM逐步完善整个事务框架:
若是前面的分享太过冗长,那么千言万语汇聚成一张表格,欢迎阅读。
将来,咱们将不断优化当前的特性,陆续推出你们关注的柔性事务、数据治理等更多新特性。若是有什么想法、意见和建议,也欢迎留言交流,更欢迎加入到Sharding-Sphere的开源项目中:
Q1:基于XA的事物,能够应用到微服务架构中吗?
A1:目前咱们是把事务管理器内嵌到JVM进程中,对于并发量小,短事务的业务,能够用XA。
Q2:对于各个事务框架开发计划的前后顺序是基本什么来肯定的呢?
A2:基于难易程度,因此咱们把TCC放到了最后。
Q3:支持多语言吗?好比golang?
A3:多语言能够用Sharding-Proxy。
Q4:此次是Proxy实现分布式事务吧?我记得以前Sharding-JDBC有实现。
A4:此次是整个SS的事务实现,包含Sharding-JDBC和Proxy,目前SJ的实现是弱XA和BED(最大努力送达),之后会增长SAGA和TCC。
Q5:若是我只想用SS里的事务模块,能够吗?
A5:SS是以事件驱动的方式进行的架构,将来事务模块只负责事务相关的处理。
Q6:SAGA不支持ACID中的I,我们这边怎么考虑的呢?
A6:目前暂不支持隔离性,从此咱们有增长I的规划,其实全部的柔性事务都不支持I,TCC增长了Try阶段,能够理解是准隔离性,使用SAGA时,能够在业务层面控制并发,防止脏读等产生。
Q7:那意思,如今3的版本还不能单独用事务的模块?
A7:如今3.0版本,事务模块依赖了Sharding-JDBC模块,事务模块须要监听Sharding-JDBC和Proxy中的事件,而后进行事务操做。若是你想单独用事务模块,须要按Core中定义的事件,在你的业务里进行发布。