二、分布式事务(多数据源事务JTA解决、笔记以及实验)

一、本地事务(笔记以及实验)

一、资源管理器(RM)和事务管理器(TM )

  1. 资源管理器(resourceManager):管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
    这个具体来说就是指数据库
  2. 事务管理器(transactionManager):定义全局事务的范围:开始全局事务、提交或回滚全局事务。这个就是指全局事务,可以操作多个资源管理器,进行协调所有资源完成数据一致。
    (注:来源:《凤凰架构》

二、tomcat配置数据源,使用全局事务框架进行多数据源分布式事务

代码地址:https://gitee.com/ClumsyBird/learn-demo/tree/master/jta/demo1/demo1

  1. 最终效果:
    1.1 tomcat的context.xml配置
    在这里插入图片描述
    1.2 spring配置atomikos的全局事务
    在这里插入图片描述
    1.3 jndi配置加载数据源
    在这里插入图片描述
    在这里插入图片描述
    1.4 测试类
    在这里插入图片描述
    在这里插入图片描述
    两个数据源在一个事务里,第一次插入id=6都插入进去;删除1库的id=6数据,再次执行,2库由于有id=6的数据报主键冲突,查看1库也进行回滚没有执行插入。
  2. tomcat配置普通的数据源,并且集成jdbcTemplate
    2.1 首先在tomcat的context.xml文件中配置:
<Resource name="jdbc_db1"      
			auth="Container"  
			type="javax.sql.DataSource"
			driverClassName="com.mysql.cj.jdbc.Driver"  
			url="jdbc:mysql://localhost:8023/test?serverTimezone=UTC&amp;useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=false"  
			username="root"  
			password="root"  
			maxActive="500"  
			maxIdle="500"  
			maxWait="36000"  
			factory="com.alibaba.druid.pool.DruidDataSourceFactory"
			/> 
<Resource name="jdbc_db2"      
			auth="Container"  
			type="javax.sql.DataSource"
			driverClassName="com.mysql.cj.jdbc.Driver"  
			   url="jdbc:mysql://localhost:8023/test?serverTimezone=UTC&amp;useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=false&amp;allowPublicKeyRetrieval=true" 
			username="root"  
			password="root"  
			maxActive="30"  
			maxIdle="20"  
			maxWait="36000"
			factory="com.alibaba.druid.pool.DruidDataSourceFactory"
			/>

说明:
2.1.1 此处使用的druid连接池,故而需要把druid的jar包以及mysql的jar包都放到tomcat的lib目录下,否则tomcat是找不到类报错的
2.1.2 在xml中 &符号要使用 & 转义字符进行拼接,否则会报错
2.1.3 是在节点下,弄错了不好使
2.1.4 链接参数要配置准确,不然报错也不太准确,可以先用jdbc试一下,再把链接参数放到这里

  1. 集成全局事务框架,进行多数据源事务配置(此处使用的atomikos)
    3.1 先测试tomcat的配置能否正常使用:
public void testJNDI2() throws Exception{
    Context ctx = new InitialContext();
    DataSource ds = (DataSource) ctx.lookup("java:comp/env/jdbc_db1");
    Connection conn = ds.getConnection();
    System.out.println(conn);
}

这个代码运行后不报错就,正常获取conn就ok

3.2 spring的配置文件中加入:

<bean id="db1"  name="db1" class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiName" value="java:comp/env/jdbc_db1" />
<!--        <property name="resourceRef" value="true"/>-->
    </bean>
    <bean id="db2"  name="db2" class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiName" value="jdbc_db2" />
        <property name="resourceRef" value="true"/>
    </bean>

注:jndiName:第一步中所配置单独JNDI名称。
resourceRef:可以让我们指定jndiName时,省略前缀“java:comp/env/”。
坑:这里的JndiObjectFactoryBean不是这个bean的类型,这玩意儿的类型是tomcat的context.xml配置的工厂生成的datasource… 然而idea中提示的这个配置的类型是JndiObjectFactoryBean,而且自动生成的代码也是这个类型,接着就报错了,故而ref这个bean的时候要写datasource的类型,写JndiObjectFactoryBean类型就报错了。

3.3 这玩意是datasource类型,那么就可以直接塞到jdbctemplate


<bean id="jdbcTemplate1" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="db1" />
</bean>
<bean id="jdbcTemplate2" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="db2" />
</bean>

DruidDataSource db1 = applicationContextHelper.popBean("db1", DruidDataSource.class);
this.jdbcTemplate1 = new JdbcTemplate(db1);
DruidDataSource db2 = applicationContextHelper.popBean("db2", DruidDataSource.class);
this.jdbcTemplate2 = new JdbcTemplate(db2);

以上xml配置或者代码塞进去都可以,idea中xml配置db1、db2会飘红,是不受影响的

3.4 接着正常测试就可以了

String sql = "insert into test(id, name) values(6, '444')";
jdbcTemplate1.execute(sql);  // 1库
jdbcTemplate2.execute(sql);  // 2库
  1. 集成全局事务框架,进行多数据源事务配置(此处使用的atomikos)
    4.1 pom文件

<dependency>
  <groupId>com.atomikos</groupId>
  <artifactId>transactions-jdbc</artifactId>
  <version>5.0.9</version>
</dependency>
<dependency>
  <groupId>com.atomikos</groupId>
  <artifactId>transactions-jdbc</artifactId>
  <version>5.0.9</version>
</dependency>

4.2 spring中配置分布式事务


<!-- 分布式事务 -->
<bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init" destroy-method="close">
    <property name="forceShutdown" value="true"/>
</bean>
<bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
    <property name="transactionTimeout" value="300"/>
</bean>
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
    <property name="transactionManager" ref="atomikosTransactionManager"/>
    <property name="userTransaction" ref="atomikosUserTransaction"/>
</bean>
<!--配置TX注解解析器-->
<tx:annotation-driven transaction-manager="transactionManager"/>

<mvc:annotation-driven></mvc:annotation-driven>

4.3 注解也加进去


<!--配置DI注解解析器-->
<context:annotation-config/>
<!--配置IoC注解解析器-->
<context:component-scan base-package="com"></context:component-scan>
<!-- 这个玩意是能够用类注入,默认是jdk的接口代理注入 -->
<aop:config proxy-target-class="true"></aop:config>

4.4 无论@Transactional还是硬编码UserTransaction userTransaction = transactionManager.getUserTransaction();此处配置完测试是失败的,两个库直接数据报异常后并没有在一个事务管理器里

  1. tomcat配置AXDataSource数据源,加入atomikosDataSourceBean
    5.1 atomikosDataSourceBean中设置的是XADatasource类型,所以tomcat之中要先把生成dataSource转为生成XADataSource类型,最终的配置:

<Resource 
       name="jdbc_db1"	factory="org.apache.tomcat.jdbc.naming.GenericNamingResourcesFactory"
       type="com.mysql.cj.jdbc.MysqlXADataSource"
	   user="root"
       password="root"
   url="jdbc:mysql://localhost:8023/test?serverTimezone=UTC&amp;useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=false&amp;allowPublicKeyRetrieval=true"  
		driverClassName="com.mysql.cj.jdbc.Driver"  
    />
	
	<Resource 
       name="jdbc_db2"
	
	factory="org.apache.tomcat.jdbc.naming.GenericNamingResourcesFactory"
       type="com.mysql.cj.jdbc.MysqlXADataSource"
	   user="root"
       password="root"
   url="jdbc:mysql://localhost:8024/test?serverTimezone=UTC&amp;useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=false&amp;allowPublicKeyRetrieval=true"  
		driverClassName="com.mysql.cj.jdbc.Driver"  
    />

注:
GenericNamingResourcesFactory是灵活依据类型生成的工厂,这个工厂可以生成DataSource、XADatasource或者其他;type中要配置com.mysql.cj.jdbc.MysqlXADataSource,配置javax.sql.XADataSource是不生效的;DataSource中配置的是username,而此处XADatasource配置的是user;链接字符串中要加上allowPublicKeyRetrieval,否则链接时候是报错的。

百度有些配置factory="com.atomikos.tomcat.EnhancedTomcatAtomikosBeanFactory"说是可以实现,但是没有找到这个jar是哪个。

5.2 测试XADatasource是否配置正确
需要先在mysql执行:GRANT XA_RECOVER_ADMIN ON *.* TO root@'%' ;(否则会报错的)


public void testJNDI() throws Exception{
     Context ctx = new InitialContext();
    XADataSource ds = (XADataSource) ctx.lookup("java:comp/env/jdbc_db1");
     XAConnection conn = ds.getXAConnection();
     System.out.println(conn.getConnection());
 }

运行完无报错,正常输入链接就ok了

5.3 spring配置文件配置atomikosDataSourceBean


<bean id="atomikosDataSourceBean1" class="com.atomikos.jdbc.AtomikosDataSourceBean">
    <property name="xaDataSource" ref="db1" />
    <property name="uniqueResourceName" value="db1" />
    <property name="minPoolSize" value="5" />
    <property name="maxPoolSize" value="5" />
</bean>
<bean id="jdbcTemplate1" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="atomikosDataSourceBean1" />
</bean>
<bean id="atomikosDataSourceBean2" class="com.atomikos.jdbc.AtomikosDataSourceBean">
    <property name="xaDataSource" ref="db2" />
    <property name="uniqueResourceName" value="db2" />
    <property name="minPoolSize" value="5" />
    <property name="maxPoolSize" value="5" />
</bean>
<bean id="jdbcTemplate2" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="atomikosDataSourceBean2" />
</bean>

5.4 测试全局事务


public void test4() throws Exception{
       // GRANT XA_RECOVER_ADMIN ON *.* TO root@'%' ;

//        Object db1 = applicationContextHelper.getApplicationContext().getBean("db1");
//        XADataSource s=  (XADataSource)db1;
//        XAConnection conn = s.getXAConnection();
//        System.out.println(conn.getConnection());
        UserTransaction userTransaction = transactionManager.getUserTransaction();
        userTransaction.begin();
        try {
            String sql = "insert into test(id, name) values(6, '444')";
            jdbcTemplate1.execute(sql);
            jdbcTemplate2.execute(sql);
            userTransaction.commit();
        } catch (Exception e) {
            userTransaction.rollback();
            e.printStackTrace();

        }
    }

 @Transactional
    public void test() throws Exception{
            String sql = "insert into test(id, name) values(6, '444')";
            jdbcTemplate1.execute(sql);  // 1库
            jdbcTemplate2.execute(sql);  // 2库
    }

无论使用userTransaction还是@Transactional注解都是可以达到正常全局事务预期;第一次插入两个库均有数据,删除1库的id=6数据,再次执行,2库会有主键冲突,发现1库的数据也未插入。

三、分布式两阶段三阶段的弊端

  1. 两阶段:
    1.1 单点问题:协调者一旦宕机,所有参与者都会受到影响。如果协调者一直没 有恢复,没有正常发送 Commit 或者 Rollback 的指令,那所有参与者都必须一直等待。
    1.2 性能问题:两段提交过程中, 要经过两次远程服务调用,三次数据持久化(准备阶段写重做日志,协调者做状态持久 化,提交阶段在日志写入 Commit Record),整个过程将持续到参与者集群中最慢的那一个处理操作结束为止,这决定了两段式提交的性能通常都较差。
    1.3 一致性风险:两段式提交的成立是有前提条件的,当网络稳定性和宕机 恢复能力的假设不成立时,仍可能出现一致性问题。如果协调者在发出准备指令后,根据 收到各个参与者发回的信息确定事务状态是可以提交的,协调者会先持久化事务状态,提交自己的事务,如果这时候网络忽然被断开,无法再通过网络向所有参与者发出 C ommit 指令的话,就会导致部分数据(协调者的)已提交,但部分数据(参与者的)既 未提交,也没有办法回滚,产生了数据不一致的问题。
  2. 三阶段:
    2.1 三段式提交对单点问题和回滚时的性能问题有所改善,因为新增了 CanCommit, 是一个询问阶段
    2.2 在事务能够正常提交的场景中,三段式因为多了一次询问,还要比二段式稍微更差一些
    2.3 一致性风险问题对比二段式并未有任何改进,在这方面它面临的风险甚至反而是略有增加了的。譬如, 进入 PreCommit 阶段之后,协调者发出的指令不是 Ack 而是 Abort,而此时因网络问题, 有部分参与者直至超时都未能收到协调者的 Abort 指令的话,这些参与者将会错误地提交 事务,这就产生了不同参与者之间数据不一致的问题
    参考链接:
    分布式事务两段提交原理
    分布式事务
    两段和三段式事务

三、分布式事务(多服务多数据源事务TCC解决、笔记以及实验)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值