事务基本概念
首先事务的基本概念为 ABC三个操作缺一不可,要么全部成功 要么全部失败。
事务具有ACID特性 即原子性、一致性、隔离性、持久性。
传统单体服务中的事务
在传统的单体服务架构与单个数据中,我们只需要增加一个@Transactional注解就能实现事务
Transactional事务的核心其实是依托于JDBC的事务,在默认情况下,我们每执行一条SQL语句,就会进行隐式的提交一个事务,若失败则会隐式的回滚事务
![](https://i-blog.csdnimg.cn/blog_migrate/e805a263f45145ab14ef260ccb318379.png)
这张图是一个简单的事务的使用
spring中的Transactional则是在JDBC事务的基础上 进行AOP的切(TransactionInterceptor,同时定义事务的传播行为与隔离级别
这张图就简单说明了 单体服务中的事务
![](https://i-blog.csdnimg.cn/blog_migrate/9e015fb1fc8a158d63f6157a5c700171.png)
分布式事务
但是在分布式系统之下,服务之间用RPC进行通讯,模块之间对应的数据库也不一致,那么此刻数据库的事务就会有问题
原因是我在A数据库内不知道BC的数据库中执行情况。
![](https://i-blog.csdnimg.cn/blog_migrate/37cd916aef8a2c025087869e8ea35d0b.png)
基于这样一种场景 我们又两种处理方式
1.增加一个渠道,使得ABC之间数据库可以通讯,A可以知道BC的执行情况 并根据情况进行处理(不太清楚能不能做)
2.增加一个监控对象,获取ABC三者的所有数据,在由这个监控对象进行执行(常规)
XA规范与DTP模型
那么基于第二种模式,我们首先需要引入概念是XA规范
XA是由X/Open组织提出的分布式事务的规范。XA规范主要定义了(全局)事务管理器(Transaction Manager)和(局部)资源管理器(Resource Manager)之间的接口。XA接口是双向的系统接口,在事务管理器(Transaction Manager)以及一个或多个资源管理器(Resource Manager)之间形成通信桥梁。XA引入的事务管理器充当上文所述全局事务中的“协调者”角色。事务管理器控制着全局事务,管理事务生命周期,并协调资源。资源管理器负责控制和管理实际资源(如数据库或JMS队列)。目前,Oracle、Informix、DB2、Sybase和PostgreSQL等各主流数据库都提供了对XA的支持。
X/Open DTP 模型 包括应用程序( AP )、事务管理器( TM )、资源管理器( RM )、通信资源管理器( CRM )四部分。
![](https://i-blog.csdnimg.cn/blog_migrate/943c441fa042852e296425dd20466e17.png)
1.本地数据库事务注册到TM管理中心中
2.TM管理中心,开启事务执行SQL操作
3.由本地事务返回操作的结果对象
4.如果操作均为成功,则TM进行统一Commit,如果存在失败则进行事务回滚
2PC与3PC
XA协议的具体实现包括2PC 二阶段分段提交 与 3PC 三阶段分段提交
这下面两张图就是2pc的正常逻辑与错误逻辑(本质上就是上面的DTP模型)
![](https://i-blog.csdnimg.cn/blog_migrate/e57a5ea8c8a9f40a0e6025b440317491.png)
![](https://i-blog.csdnimg.cn/blog_migrate/2b5655fa2990a7c3d71edc2c5e05e569.png)
2PC会有很多问题
* 性能问题。从流程上我们可以看得出,其最大缺点就在于它的执行过程中间,节点都处于阻塞状态。各个操作数据库的节点此时都占用着数据库资源,只有当所有节点准备完毕,事务协调者才会通知进行全局提交,参与者进行本地事务提交后才会释放资源。这样的过程会比较漫长,对性能影响比较大。
* 协调者单点故障问题。事务协调者是整个XA模型的核心,一旦事务协调者节点挂掉,会导致参与者收不到提交或回滚的通知,从而导致参与者节点始终处于事务无法完成的中间状态。
* 丢失消息导致的数据不一致问题。在第二个阶段,如果发生局部网络问题,一部分事务参与者收到了提交消息,另一部分事务参与者没收到提交消息,那么就会导致节点间数据的不一致问题。
三阶段提交又称3PC,
其在两阶段提交的基础上增加了CanCommit阶段,并引入了超时机制。一旦事务参与者迟迟没有收到协调者的Commit请求,就会自动进行本地commit,这样相对有效地解决了协调者单点故障的问题。
第一阶段 canCommit
![](https://i-blog.csdnimg.cn/blog_migrate/93c7d7106ac90f3d35ec78f2232f4929.png)
事务询问操作,检查自身是否可以执行事务,
第二阶段 preCommit
![](https://i-blog.csdnimg.cn/blog_migrate/24d860c58d966bc2307586bd28a8aea9.png)
参与者开始进行事务操作,并将
Undo与
Redo信息记录到日志中(事务未提交),并将操作结果返回给TM
第三阶段 DoCommit
![](https://i-blog.csdnimg.cn/blog_migrate/1e7bcb6ce5a6c8e1953e148526ef5959.png)
最终依靠TM进行事务提交或者回滚,同时返回事务操作结果
相比较2PC而言,3PC对于协调者(Coordinator)和参与者(Partcipant)都设置了超时时间,而2PC只有协调者才拥有超时机制这个优化点,主要是避免了参与者在长时间无法与协调者节点通讯(协调者挂掉了)的情况下,无法释放资源的问题,因为参与者自身拥有超时机制会在超时后,自动进行本地commit从而进行释放资源。而这种机制也侧面降低了整个事务的阻塞时间和范围。
另外,通过CanCommit、PreCommit、DoCommit三个阶段的设计,相较于2PC而言,多设置了一个缓冲阶段保证了在最后提交阶段之前各参与节点的状态是一致的。
以上所说的DTP主要是强一致性的,他的性能始终是会存在问题的,虽然在一定程度上保证了数据的一致性,但是性能受到了限制
而这种可用性与一致性之间的权衡,我们需要引入CAP理论
CAP理论
CAP指的是 一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)无法同时满足
但是在实际场景中,我们会将P作为第一条件,即在任何情况下,我们都不能保证消息发送的可靠性(由于网络波动和服务器宕机等等场景)
那么在CP与AP中,如何选择,这个是要根据实际和业务来的
刚刚提到了分段提交是强一致性的处理方案,但是性能差,其本质是由于CAP理论的支撑,在分布式演变的过程中,我们提出了BASE的概念。
BASE
base即是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)
这是一种对一致性和可用性的权衡结果,核心意义在于,在保证一定可用性的前提下,达到最终一致性
比如说 双11,当时节点的瞬间流量是很恐怖的,哪怕在各种削峰,限流之下,单个节点的压力还是很大,在这种情况下,我们认为客户界面卡个几秒钟是可以接受的,反正最后下单成功了。
但是需要强调的是弱一致性并不等于不可用,你要是双11卡个1分钟,最后告诉你库存没了,那就很过分了
其中说到BASE的软状态(S),就不得不扯一下状态机的这个概念
状态机
状态机在我的理解中是用于描述业务场景中的中间状态,在进行复杂业务设计中,我们通常会使用不同的状态来描述业务的流转,这样做的好处是,根据不同的状态我可以有不同的业务逻辑处理。
那么以支付为例,我的最终状态必然是支付成功或者支付失败。但是我在这个最终状态之前可以加入很多中间状态,比如说,已支出未收到,支付超时。
针对于前者,我们可以定时1s一次去拉支付账表,然后去判断究竟是成功还是失败,对于后者我们可以告诉客户,你的网络状态不好,支付失败。
而在我的理解中软状态描述的就是这些中间状态。
同样的,状态机的存在可以使得操作变得幂等,在实际的开发过程中,当我们使用Dubbo的@reference注解时,它会在一定时间为获得相应的前提下进行多次请求,针对于查询和删除等本身具有幂等的操作倒还好说,可是如果一旦进行多次修改数据(qty = qty -1),可能会导致数据多次被修改,这个时候如果加上过滤条件状态等于啥,那么便不会有这种问题
基于场景的优化
其实以上说到的无论是CAP还是BASE,其本质上还有一个是基于场景进行编程。在平衡可用性与一致性的情况下,我们会基于整个业务场景,进行一些代码的改造
比如说,客户下单或者退单,我们必然会有一个实时库存表,用于记录每个仓库里面的每件商品的库存数,在这样一种业务场景下,本身的并发量是很高的,如果恰好这件商品是爆款的话,我们会频繁的锁表行记录
但是,可以转化一些思路,我们将这个行为改为,不停的往数据库表里插入数据,本身insert的性能会高于update,而且还不会导致数据行锁。
最终一致性解决方案
事实上,JAVA本身提供了强一致性的API,是在java transaction api中,本身是依据之前提到的XA规范和DTP模型。同样的也有atomikos强一致性的开源实现。
最终一致性 分为三种具体实现:最大努力通知型,TCC补偿机制与基于可靠消息的最终一致性
最大努力通知型
具体实现
1.业务活动的主动方,在完成业务处理之后,向业务活动的被动方发送消息,允许消息丢失。
2.主动方可以设置时间阶梯型通知规则,在通知失败后按规则重复通知,直到通知N次后不再通知。
3.主动方提供校对查询接口给被动方按需校对查询,用于恢复丢失的业务消息。
4.业务活动的被动方如果正常接收了数据,就正常返回响应,并结束事务。
5.如果被动方没有正常接收,根据定时策略,向业务活动主动方查询,恢复丢失的业务消息。
简单的画了一个流程图,大致流程如下,简单的来说,就是用消息对象进行消息投递,在发送失败之后,进行自身业务逻辑处理,同时根据逻辑继续投递还是回写错误日志,保证消息不丢失
这种处理机制适用于对时间不太敏感的系统操作(就比如这个短信发送)
![](https://i-blog.csdnimg.cn/blog_migrate/cb1545f7da174a1cfcbdfd76702bf48b.png)
TCC补偿
TCC是Try,confirm,cancel的简称,描述的是事务的三个阶段
Try阶段 : 完成所有业务检查(一致性),预留业务资源(准隔离性)
Confirm阶段:确认执行业务操作,不做任何业务检查, 只使用Try阶段预留的业务资源。
Cancel : 取消Try阶段预留的业务资源。
TCC与XA二阶段提交的差异
![](https://i-blog.csdnimg.cn/blog_migrate/709486e8c94ea28a494bad8b93bf51ae.png)
区别在于XA是资源层面的分布式事务,强一致性,在二阶段提交中一直会保持资源的锁
而TCC是业务层面的分布式事务,是最终一致性的,并不会一直保持对资源的锁
同时XA是不可见的,二阶段提交内部对开发人员是屏蔽的,但是TCC的confrim与cancel接口开发人员是可以自己定义的
画了一张比较简单的图,用于理解
![](https://i-blog.csdnimg.cn/blog_migrate/3f4dc13dbce884e677266c16011a4ffe.png)
基于可靠消息的最终一致性
基于可靠消息的传输本身也是一种解决方案,mq提供了一种transcationMQ的消息方案,其本质的核心在于发送到客户端消费的消息必然是成功的。
由下图可知,mq将消息包装了起来,在第一阶段时,消息不可见,只有在发送端将消息处理完成之后,在全部成功的情况下,才可以将消息提交到broker(中间状态为unknown),再到customer进行消费
如果消息发送失败,broker回查数据,同时消费消息不可见
![](https://i-blog.csdnimg.cn/blog_migrate/434557270ab2411ec40f0d5bcb334c17.png)