一个复杂的系统每每都是从一个小而简的系统发展衍化而来,为了知足日益增加的业务需求,不断的增长系统的复杂度,从单体架构逐步发展为分布式架构,而分布式系统架构的设计主要关注:高性能,高可用,高拓展java
高可用是指系统无中断的执行功能的能了,表明了系统的可用程度,是进行系统设计时必需要遵照的准则之一。mysql
而高可用的实现方案,无外乎就是冗余,就存储的高可用而言,问题不在于如何进行数据备份,而在于如何规避数据不一致对业务形成的影响git
对于分布式系统而言,要保证分布式系统中的数据一致性就须要一种方案,能够保证数据在子系统中始终保持一致,避免业务出现问题,这种实现方案就叫作分布式事务,要么一块儿成功,要么一块儿失败,必须是一个总体性的事务github
在讲解具体方案以前,有必要了解一下分布式中数据设计须要遵循的理论基础,CAP理论和BACS理论,为后面的实践铺平道路算法
CAP:Consistency Acailability Partition tolerance 的简写sql
Consistency:一致性数据库
对某个客户端来讲,读操做可以返回最新的写操做结果编程
Acailability:可用性设计模式
非故障节点在合理的时间内返回合理的响应网络
Partition tolerance:分区容错性
当出现网络分区后,系统可以继续提供服务 你知道什么是网络分区吗 ~~
由于分布式系统中系统确定部署在多台机器上,没法保证网络作到100%的可靠,因此网络分区必定存在,即P必定存在;
在出现网络分区后,就出现了可用性和一致性的问题,咱们必需要在这二者之间进行取舍,所以就有了两种架构:CP架构,AP架构;
当网络分区出现后,为了保证一致性,就必须拒接请求,不然没法保证一致性
上面这种方式就违背了可用性的要求,只知足一致性和分区容错,即CP
CAP理论是忽略网络延迟,从系统A同步数据到系统B的网络延迟是忽略的
CP架构保证了客户端在获取数据时必定是最近的写操做,或者获取到异常信息,毫不会出现数据不一致的状况
当网络分区出现后,为了保证可用性,系统B能够返回旧值,保证系统的可用性
上面这种方式就违背了一致性的要求,只知足可用性和分区容错,即AP
AP架构保证了客户端在获取数据时不管返回的是最新值仍是旧值,系统必定是可用的
CAP理论关注粒度是数据,而不是总体系统设计的策略
BASE理论指的是基本可用 Basically Available,软状态 Soft Stat,最终一致性 Eventual Consistency,核心思想是即使没法作到强一致性,但应该能够有采用适合的方式保证最终一致性
BASE:Basically Available Soft Stat Eventual Consistency的简写
BA:Basically Available 基本可用
分布式系统在出现故障的时候,容许损失部分可用性,即保证核心可用
S:Soft Stat 软状态
容许系统存在中间状态,而该中间状态不会影响系统总体可用性
E:Consistency 最终一致性
系统中的全部数据副本通过必定时间后,最终可以达到一致的状态
BASE理论本质上是对CAP理论的延伸,是对 CAP 中 AP 方案的一个补充
XA是一个分布式事务协议,由Tuxedo提出。XA规范主要定义了(全局)事务管理器(Transaction Manager)和(局部)资源管理器(Resource Manager)之间的接口。XA接口是双向的系统接口,在事务管理器Transaction Manager)以及一个或多个资源管理器(Resource Manager)之间造成通讯桥梁
XA协议采用两阶段提交方式来管理分布式事务。XA接口提供资源管理器与事务管理器之间进行通讯的标准接口
二阶段提交(Two-phase Commit),是指,为了使基于分布式系统架构下的全部节点在进行事务提交时保持一致性而设计的一种算法(Algorithm)。一般,二阶段提交也被称为是一种协议(Protocol)。在分布式系统中,每一个节点虽然能够知晓本身的操做时成功或者失败,却没法知道其余节点的操做的成功或失败。当一个事务跨越多个节点时,为了保持事务的ACID特性,须要引入一个做为协调者的组件来统一掌控全部节点(称做参与者)的操做结果并最终指示这些节点是否要把操做结果进行真正的提交(好比将更新后的数据写入磁盘等等)。所以,二阶段提交的算法思路能够归纳为: 参与者将操做成败通知协调者,再由协调者根据全部参与者的反馈情报决定各参与者是否要提交操做仍是停止操做
二阶段提交算法的成立基于如下假设:
二阶段提交分为两阶段:第一阶段:投票阶段,第二阶段:提交阶段
投票阶段 Prepares
提交阶段 commit
二阶段提交优势:尽可能保证了数据的强一致,但不是100%一致
缺点:
单点故障
因为协调者的重要性,一旦协调者发生故障,参与者会一直阻塞,尤为时在第二阶段,协调者发生故障,那么全部的参与者都处于锁定事务资源的状态中,而没法继续完成事务操做
同步阻塞
因为全部节点在执行操做时都是同步阻塞的,当参与者占有公共资源时,其余第三方节点访问公共资源不得不处于阻塞状态
数据不一致
在第二阶段中,当协调者想参与者发送提交事务请求以后,发生了局部网络异常或者在发送提交事务请求过程当中协调者发生了故障,这会致使只有一部分参与者接收到了提交事务请求。而在这部分参与者接到提交事务请求以后就会执行提交事务操做。可是其余部分未接收到提交事务请求的参与者则没法提交事务。从而致使分布式系统中的数据不一致
二阶段提交的问题
若是协调者在第二阶段发送提交请求以后挂掉,而惟一接受到这条消息的参与者执行以后也挂掉了,即便协调者经过选举协议产生了新的协调者并通知其余参与者进行提交或回滚操做的话,均可能会与这个已经执行的参与者执行的操做不同,当这个挂掉的参与者恢复以后,就会产生数据不一致的问题
三阶段提交(Three-phase commit),三阶段提交是为解决两阶段提交协议|的缺点而设计的。 与两阶段提交不一样的是,三阶段提交是“非阻塞”协议。三阶段提交在两阶段提交的第一阶段与第二阶段之间插入了一个准备阶段,使得原先在两阶段提交中,参与者在投票以后,因为协调者发生崩溃或错误,而致使参与者处于没法知晓是否提交或者停止的“不肯定状态”所产生的可能至关长的延时的问题得以解决
三阶段提交的三个阶段:CanCommit,PreCommit,DoCommit三个阶段
询问阶段 CanCommit
协调者向参与者发送commit请求,参与者若是能够提交就返回Yes响应,不然返回No响应
准备阶段 PreCommit
协调者根据参与者在询问阶段的响应判断是否执行事务仍是中断事务
参与者执行完操做以后返回ACK响应,同时开始等待最终指令
提交阶段 DoCommit
协调者根据参与者在准备阶段的响应判断是否执行事务仍是中断事务
协调者收到全部参与者的ACK响应,完成事务
解决二阶段提交时的问题
在三阶段提交中,若是在第三阶段协调者发送提交请求以后挂掉,而且惟一的接受的参与者执行提交操做以后也挂掉了,这时协调者经过选举协议产生了新的协调者,在二阶段提交时存在的问题就是新的协调者不肯定已经执行过事务的参与者是执行的提交事务仍是中断事务,可是在三阶段提交时,确定获得了第二阶段的再次确认,那么第二阶段必然是已经正确的执行了事务操做,只等待提交事务了,因此新的协调者能够从第二阶段中分析出应该执行的操做,进行提交或者中断事务操做,这样即便挂掉的参与者恢复过来,数据也是一致的。
因此,三阶段提交解决了二阶段提交中存在的因为协调者和参与者同时挂掉可能致使的数据一致性问题和单点故障问题,并减小阻塞,由于一旦参与者没法及时收到来自协调者的信息以后,他会默认执行提交事务,而不会一直持有事务资源并处于阻塞状态。
三阶段提交的问题
在提交阶段若是发送的是中断事务请求,可是因为网络问题,致使部分参与者没有接到请求,那么参与者会在等待超时以后执行提交事务操做,这样这些因为网络问题致使提交事务的参与者的数据就与接受到中断事务请求的参与者存在数据不一致的问题。
因此不管是2PC仍是3PC都不能保证分布式系统中的数据100%一致
举个栗子:
在电商网站中,用户对商品进行下单,须要在订单表中建立一条订单数据,同时须要在库存表中修改当前商品的剩余库存数量,两步操做一个添加,一个修改,咱们必定要保证这两步操做必定同时操做成功或失败,不然业务就会出现问题
创建时:
业务量不大,用户少,系统只是一个单体架构,订单表与库存表都在一个数据库中,这时可使用mysql的本地事务保证数据一致性
发展期:
业务发展迅速,用户量变多,单数据已经出现了性能瓶颈,按照业务纬度进行分库,分为订单库和库存库,因为跨库跨机器,mysql的本地事务不能再保证订单库和库存库的数据一致性
成熟期:
业务拓展,单体架构已经知足不了需求,进而衍化成了分布式系统,这时的订单和库存已经拆分为了两个子系统提供服务,子系统间使用rpc进行通讯,可是不管系统发展成什么样,咱们都要保证业务不出问题,保证订单和库存的数据一致,这时候要思考下在服务之间咱们应如何保证数据一致
单体架构多数据源,在业务开发中,确定是先执行对订单库的操做,可是不提交事务,再执行对库存库的操做,也不提交事务,若是两个操做都成功,在一块儿提交事务,若是有一个操做失败,则两个都进行回滚
咱们已经知道了2PC和XA协议的原理,而JTA是java规范,是XA在java上的实现
JTA(Java Transaction Manager) :
JTA主要的原理是二阶段提交,当整个业务完成了以后只是第一阶段提交,在第二阶段提交以前会检查其余全部事务是否已经提交,若是前面出现了错误或是没有提交,那么第二阶段就不会提交,而是直接回滚,这样全部的事务都会作回滚操做
基于JTA这种方案实现分布式事务的强一致性
JTA的特色:
实现可使用基于JTA实现的jar包Atomikos 使用例子能够本身百度一下
正常架构设计中是否应该出现这种跨库的操做,我以为是不该该的,若是过按业务拆分将数据源进行分库,咱们应该同时将服务也拆分出去才合适,应遵循一个系统只操做一个数据源(主从不要紧),避免后续可能会出现的多个系统调用一个数据源的状况
JTA方案适用于单体架构多数据源时实现分布式事务,但对于微服务间的分布式事务就无能为力了,咱们须要使用其余的方案实现分布式事务
本地消息表的核心思想是将分布式事务拆分红本地事务进行处理
以本文中例子,在订单系统新增一条消息表,将新增订单和新增消息放到一个事务里完成,而后经过轮询的方式去查询消息表,将消息推送到mq,库存系统去消费mq
执行流程:
订单系统中的消息有可能因为业务问题会一直重复发送,因此为了不这种状况能够记录一下 发送次数,当达到次数限制以后报警,人工接入处理;库存系统须要保证幂等,避免同一条消息被屡次消费形成数据一致;
本地消息表这种方案实现了最终一致性,须要在业务系统里增长消息表,业务逻辑中多一次插入的DB操做,因此性能会有损耗,并且最终一致性的间隔主要有定时任务的间隔时间决定
消息事务的原理是将两个事务经过消息中间件进行异步解耦
订单系统执行本身的本地事务,并发送mq消息,库存系统接收消息,执行本身的本地事务,乍一看,好像跟本地消息表的实现方案相似,只是省去 了对本地消息表的操做和轮询发送mq的操做,但实际上两种方案的实现是不同的
消息事务必定要保证业务操做与消息发送的一致性,若是业务操做成功,这条消息也必定投递成功
执行流程:
这种方案也是实现了最终一致性,对比本地消息表实现方案,不须要再建消息表,再也不依赖本地数据库事务了,因此这种方案更适用于高并发的场景
最大努力通知相比前两种方案实现简单,适用于一些最终一致性要求较低的业务,好比支付通知,短信通知这种业务
以支付通知为例,业务系统调用支付平台进行支付,支付平台进行支付,进行操做支付以后支付平台会尽可能去通知业务系统支付操做是否成功,可是会有一个最大通知次数,若是超过这个次数后仍是通知失败,就再也不通知,业务系统自行调用支付平台提供一个查询接口,供业务系统进行查询支付操做是否成功
这种方案也是实现了最终一致性
TCC Try-Confirm-Cancel的简称,针对每一个操做,都须要有一个其对应的确认和取消操做,当操做成功时调用确认操做,当操做失败时调用取消操做,相似于二阶段提交,只不过是这里的提交和回滚是针对业务上的,因此基于TCC实现的分布式事务也能够看作是对业务的一种补偿机制
TCC的三阶段:
在try阶段,是对业务系统进行检查及资源预览,好比订单和存储操做,须要检查库存剩余数量是否够用,并进行预留,预留操做的话就是新建一个可用库存数量字段,Try阶段操做是对这个可用库存数量进行操做
好比下一个订单减一个库存:
执行流程:
基于TCC实现分布式事务,代码逻辑想对复杂一些,须要将原来的接口的逻辑拆分为:try,confirm ,cancel 三个接口的逻辑
基于TCC实现的分布式事务框架:ByteTCC,tcc-transaction
ByteTCC:github.com/liuyangming…
tcc-transaction:github.com/changmingxi…
读完以后应该对分布式事务有了一个大体的了解,在实际生产中咱们要尽可能避免使用分布式事务,能转化为本地事务就用本地事务,若是必须使用分布式事务,还须要从业务角度多思考使用哪一种方案更适合
总之行动以前多思考
推荐阅读:
java并发编程 | 锁详解:AQS,Lock,ReentrantLock,ReentrantReadWriteLock
参考: