分布式事务一:基于数据库原生分布式事务方案实现

声明: 本篇主要对所用到的技术做了归纳总结,对源码讲解较少,如果有基础的朋友可以直接下载源码结合时序图更能容易理解;基础比较弱的朋友建议先看看资料自看源码这样更容易理解。这里的部分资料来源于网络,所以这里对那些资料提供者表达衷心的感谢。

方案:

  • 业务流程:Tss库向Saas转移库存,order为记录表。
  • 技术栈: Springboot+mysql+postgreSQL+atomikos+mybatis
  • 项目代码地址:https://github.com/bao17634/springboot-kafka-demo.git
1、分布式事务模型 ACID 实现
1.1、X/Open XA 协议(XA)

最早的分布式事务模型是 X/Open 国际联盟提出的 X/Open Distributed Transaction Processing(DTP)模型,也就是大家常说的 X/Open XA 协议,简称XA 协议。
DTP模型如图:
在这里插入图片描述

  • TM:全局事务管理器
  • RM:多个资源管理器
  • AP:应用程序

全局事务管理器负责管理全局事务状态与参与的资源,协同资源一起提交或回滚;资源管理器则负责具体的资源操作。
XA 协议主要描述了 TM 与 RM 之间的接口,允许多个资源在同一分布式事务中访问。
基于 DTP 模型的分布式事务流程大致如下: [外链图片转存失败(img-btzVj3Tn-1569375514498)(en-resource://database/1206:1)]
XA接口详解

  • XA接口时双向的系统接口,用于事务管理器和多个资源管理器之间形成桥梁。
  • 事务管理器控制JTA事务,管理事务生命周期,并协调资源,在JTA中,事务管理器抽象为TransactionManager接口,并通过底层事务服务JTS(java事务服务)实现,资源管理器负责控制和管理实际资源(如数据库或者JMS(java消息服务)队列)。
    下图说明事务管理器、资源管理器,以及JTA环境中客户应用之间的关系: [外链图片转存失败(img-08KxE5U1-1569375514502)(en-resource://database/1216:1)]
1.2、阶段提交(2PC)

(1)概念: 二阶段提交(Two-phaseCommit)是指,为了使基于分布式系统架构下的所有节点在进行事务提交时保持一致性而设计的一种算法(Algorithm),通常二阶段提交也被称为是一种协议
(2)二阶段算法思路: 参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作。
(3)协议假设

  • 在分布式系统中,存在一个节点作为协调者,则其他节点作为参与者,且各个节点之间可以进行网络通讯。
  • 所有节点都采用预写式日志,切日志被写入后即被保持在可靠的存储设备上,即使节点损坏不会导致日志数据的消失。
  • 所有节点不会永久损失,即使损坏后任然可以恢复。
    参与者与协调者之间的关系如图:[外链图片转存失败(img-TiQ0S2kG-1569375514504)(en-resource://database/1448:1)]
    (4)第一阶段(提交请求阶段)
  1. 协调者节点向所有参与者节点询问是否可以执行提交操作,并开始等待各参与者节点的响应。
  2. 参与者节点执行询问发起为止的所有事务操作,并将信息写入日志。
  3. 各参与者节点响应协调者节点发起的询问。如果参与者节点的事务操作实际执行成功,则它返回一个"同意"消息;如果参与者节点的事务操作实际执行失败,则它返回一个"中止"消息。

(5)第二阶段(提交执行阶段)

  • 成功:

当协调者节点从所有参与者节点获得的相应消息都为"同意"时:

  1. 协调者节点向所有参与者节点发出"正式提交"的请求。
  2. 参与者节点正式完成操作,并释放在整个事务期间内占用的资源。
  3. 参与者节点向协调者节点发送"完成"消息。
  4. 协调者节点收到所有参与者节点反馈的"完成"消息后,完成事务。
  • 失败

如果任一参与者节点在第一阶段返回的响应消息为"终止",或者 协调者节点在第一阶段的询问超时之前无法获取所有参与者节点的响应消息时:

  1. 协调者节点向所有参与者节点发出"回滚操作"的请求。
  2. 参与者节点利用之前写入的日志信息执行回滚,并释放在整个事务期间内占用的资源。
  3. 参与者节点向协调者节点发送"回滚完成"消息。
  4. 协调者节点收到所有参与者节点反馈的"回滚完成"消息后,取消事务。

(6)用事务来解释二阶段
所谓的二阶段时将提交过程分为两个阶段:

  • 1.准备阶段
  • 2.提交阶段[外链图片转存失败(img-UfGFguVN-1569375514504)(en-resource://database/1516:1)]

(7)JTA(Java Transaction API)与atomikos实现分布式事务主要代码如下:

  • 1> 配置数据源: DruidXADataSource 连接池实现了XAResource接口用来进行对资源操作
@Bean(name = "systemDataSource")@Primarypublic 
DataSource systemDataSource(){     
    AtomikosDataSourceBean ds = new AtomikosDataSourceBean();     
    ds.setXaProperties(PojoUtil.obj2Properties(postgreSqlProperties));     
    ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");     
    ds.setUniqueResourceName("systemDataSource");     
    ds.setPoolSize(5);    
    ds.setTestQuery("SELECT 1");     
return ds;
}
  • 2> 配置事务: 配置spring的JtaTransactionManager,其底层交给atomikos进行事务处理。
@Bean(name = "transactionManager")
@DependsOn({"userTransaction","atomikosTransactionManager"})
public PlatformTransactionManager transactionManager() throws Throwable {      
     UserTransaction userTransaction = userTransaction();   
     TransactionManager atomikosTransactionManager = atomikosTransactionManager();    
     JtaTransactionManager jtaTransactionManager = new 
     JtaTransactionManager(userTransaction, atomikosTransactionManager);                    
     jtaTransactionManager.setAllowCustomIsolationLevels(true);    
return jtaTransactionManager;
}
2、@Transactional注解式事务详解
  • spring事务管理是指在业务代码在 出现异常之后,对之前的操作进行回滚,保证数据库数据的一致性。

2.1、配置式、注解式事务管理。

(1) 编程式:

TransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def)
try{
      businessLogic();
      transactionManager.commit(status);
  }catch(Exception ex) {
      transactionManager. rollback(status);
  throw ex;
  }

(2)xml配置式

<tx:advice id="txAdvice" transaction-manager="txManager"><tx:attributes>
  <tx:method name="create*" propagation="REQUIRED" timeout="300" rollback-for="java.lang.Exception" />
  <tx:method name="delete*" propagation="REQUIRED" timeout="300" rollback-for="java.lang.Exception" />
  <tx:method name="update*" propagation="REQUIRED" timeout="300" rollback-for="java.lang.Exception" />
  <tx:method name="get*" propagation="REQUIRED" read-only="true" timeout="300" />
  <tx:method name="*" propagation="REQUIRED" read-only="true" timeout="300" /></tx:attributes></tx:advice>
<aop:config><aop:pointcut id="txPointcut" expression="execution(* com.mico.emptyspring.service.*ServiceA.*(..))" /><aop:advisor pointcut-ref="txPointcut" advice-ref="txAdvice" /></aop:config>

(3)注解式
在配置文件中添加配置:<tx:annotation-driven transaction-manager="txManager"/>或者transaction-manager="txManager接着在需要事务的方法或类上面添加@Transactional注解。

2.2、@Transactional配置项

属性类型描述
valueString可选的限定描述符,指定使用的事务管理器
propagationenum: Propagation可选的事务传播行为设置
isolationenum:Isolation可选的事务隔离级别设置
readOnlyboolean读写或只读事务,默认读写
timeoutint(in seconds granularity)事务超时时间设置
rollbackForClass对象数组,必须继承自Throwable导致事务回滚的异常类数组
rollbackForClassName类名数组,必须继承自Throwable导致回滚的异常类名字数组
noRollbackForClass对象数组,必须继承自Throwable不会导致事务回滚的异常类数组
noRollbackForClassName类名数组,必须继承自Throwable不会导致事务回滚的异常类名字数组
  • value主要是用来指定不同的事务管理器;主要用来满足在同一个系统中,存在不同的事务管理器。 比如:在Spring中,声明两种事务管理器txManager1, txManager2,然后用户可以根据这个参数来根据需要指定特定的txManager。在一个系统中,需要访问多个数据源或者多个数据库,则必然会配置多个事务管理器。
3、事务隔离级别

3.1、事务的隔离级别(由高到低)

Read uncommitted(读未提交)

  • 所有事务都可以看到其他未提交事务的执行结果。通俗地讲就是,在一个事务中可以读取到另一个事务中新增或修改但未提交的数据。
  • 该隔离级别可能导致的问题是脏读。因为另一个事务可能回滚,所有在第一个事务中读取到的数据很可能是无效的脏数据,造成脏读现象。

Read committed(读提交):

  • 一个事务从开始直到提交之前,所做的任何修改对其他事务都是不看见的。
  • 该隔离界别可能导致的问题是不可重复读,因为两次执行同样的查询,可能得到的不一样的结果。

Repeatable read(重复读):

  • 确保一个事务的多个实例在并发读取数据时,会看到同样的数据行。也就是说,可重复读在一个事务读取数据,怎么读都不发生变化,除非提交了该事务,再次进行读取。
  • 该隔离级别存在的问题就是幻读。

Serializable(序列化):

  • 这是最高的隔离级别
  • 他通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。例如:有两个事务都操作同一个数据行,那么这个数据行就会被锁定,只允许先读取/操作到数据行的事务优先操作,只有当事务提交了,数据才会解锁,后一个事务才能操作成功这个数据行,否则只能一直等待。
  • 该隔离级别可能导致大量的超市现象和锁竞争。在这里插入图片描述
    3.2、隔离级别对并发性能的影响
    在这里插入图片描述
4、事务的传播属性

4.1、解释: 如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为,即事务方法的嵌套调用会产生事务传播。

  • TransactionDefinition.PROPAGATION_REQUIRED:
    如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
  • TransactionDefinition.PROPAGATION_REQUIRES_NEW:
    创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_SUPPORTS:
    如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED:
    以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NEVER:
    以非事务方式运行,如果当前存在事务,则抛出异常。
  • TransactionDefinition.PROPAGATION_MANDATORY:
    如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  • TransactionDefinition.PROPAGATION_NESTED:
    如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。

4.2、REQUIRED_NEW和NESTED两种不同的传播机制的区别:

(1) REQUIRED_NEW: 事务内部的事务独立运行,在各自的作用域中,可以独立的回滚或者提交;而外部的事务将不受内部事务的回滚状态影响.启动一个新的, 不依赖于环境的 “内部” 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行.
(2)NESTED: 事务基于单一的事务来管理,提供了多个保存点。这种多个保存点的机制允许内部事务的变更触发外部事务的回滚。如果外部事务 commit, 嵌套事务也会被 commit;如果外部事务 roll back, 嵌套事务也会被 roll back 。开始一个 “嵌套的” 事务, 它是已经存在事务的一个真正的子事务. 嵌套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 嵌套事务是外部事务的一部分, 只有外部事务结束后它才会被提交

5、此方案总结

此方案是基于XA协议的两阶段提交方案,这也一种是传统的分布式事务方案。
5.1 存在问题:

  • 1) 性能问题: 所有参与者在事务提交阶段处于同步阻塞状态,占用系统资源,容易导致性能瓶颈。
  • 2) 可靠性问题: 如果协调者存在单点故障问题,或出现故障,提供者将一直处于锁定状态。
  • 3) 数据一致性问题: 在阶段 2 中,如果出现协调者和参与者都挂了的情况,有可能导致数据不一致。

优点:

  • 尽量保证了数据的强一致,适合对数据强一致要求很高的关键领域(其实也不能100%保证强一致),适用于低并发性能场景。

缺点:

  • 实现复杂,牺牲了可用性,对性能影响较大,不适合高并发高性能场景。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值