美文网首页开发经验随笔程序员码农的世界
分布式事务解决方案实战专题-1

分布式事务解决方案实战专题-1

作者: 山东大葱哥 | 来源:发表于2019-04-19 00:02 被阅读10次

多数据源下分布式事务2PC解决方案

背景

在传统架构中可以使用spring的@Transactional 进行声明式或者编程式的事务管理,但如果我们代码中涉及到多数据源操作,就会发现spring的@Transactional事务管理机制会失灵,这种情况下我们就可以考虑使用两阶段提交的解决方案。
我们以mysql为例,mysql在5.0版本后支持了XA规范,也就是支持2PC形式的分布式事务。

mysql XA

相关sql语句

XA start 'global_id','branch_id';
update user set age=22 where id=12;
update order set amount=1000.01 where id=1234;
XA end  'global_id','branch_id';

XA prepare   'global_id','branch_id';
XA RECOVER;  -- 查看当前所有处于准备状态的XA事务
XA commit;-- 真正提交事务
XA rollback;-- 回滚事务

Java 代码

使用druid管理连接池,其支持XA
import com.alibaba.druid.pool.xa.DruidXADataSource;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;

import com.alibaba.druid.pool.xa.DruidXADataSource;
import com.mysql.jdbc.jdbc2.optional.MysqlXid;

import javax.sql.XAConnection;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import java.sql.Connection;
import java.sql.Statement;
import java.util.Properties;
/**
 * @author Jam Fang  https://www.jianshu.com/u/0977ede560d4
 * @version 创建时间:2019/4/14 13:58
 */
public class TwoPhaseCommitApplication {
    public void multiDataSourceTest() throws Exception {
        String propertyfile = "/app.properties";
        Properties props = new Properties();
        props.load(getClass().getResourceAsStream(propertyfile));
        //初始化数据源
        DruidXADataSource xaDataSource_1 = initXADataSource(props, "db1.");
        //初始化XA连接
        XAConnection xaConnection_1 = xaDataSource_1.getXAConnection();
        //初始化XA资源
        XAResource xaResource_1 = xaConnection_1.getXAResource();
        //获得数据库连接
        Connection connection_1 = xaConnection_1.getConnection();
        connection_1.setAutoCommit(false);
        //创建XID
        Xid xid_1 = new MysqlXid("globalid".getBytes(), "branch-1".getBytes(), 0);
        //关联事务start end
        xaResource_1.start(xid_1, XAResource.TMNOFLAGS);
        Statement stmt = connection_1.createStatement();
        String sql_1 = "INSERT INTO `order`(orderid,amount,product) values('00001','3000.00','苹果笔记本');";//"delete from test3 where pk_t=3;";
        stmt.executeUpdate(sql_1);
        xaResource_1.end(xid_1, XAResource.TMSUCCESS);
        //事务准备
        int result_1 = xaResource_1.prepare(xid_1);

        DruidXADataSource xaDataSource_2 = initXADataSource(props, "db2.");
        XAConnection xaConnection_2 = xaDataSource_2.getXAConnection();
        XAResource xaResource_2 = xaConnection_2.getXAResource();
        Connection connection_2 = xaConnection_2.getConnection();
        connection_2.setAutoCommit(false);
        Xid xid_2 = new MysqlXid("globalid".getBytes(), "branch-2".getBytes(), 0);
        xaResource_2.start(xid_2, XAResource.TMNOFLAGS);
        Statement stmt2 = connection_2.createStatement();
        String sql_2 = "update shipping set address='北京黄浦江畔' where id=1;";
        stmt2.executeUpdate(sql_2);
        xaResource_2.end(xid_2, XAResource.TMSUCCESS);
        int result_2 = xaResource_2.prepare(xid_2);
        //XA事务 准备阶段
        if (result_1 == XAResource.XA_OK &&
                result_2 == XAResource.XA_OK) {
            //都返回OK的话,进行提交阶段
            xaResource_1.commit(xid_1, false);
            xaResource_2.commit(xid_2, false);
        } else {
            //回滚事务
            xaResource_1.rollback(xid_1);
            xaResource_2.rollback(xid_2);
        }
    }

    DruidXADataSource initXADataSource(Properties props, String prefix) {
        DruidXADataSource xaDataSource = new DruidXADataSource();
        xaDataSource.setDbType(props.getProperty(prefix + "dbtype"));
        xaDataSource.setUrl(props.getProperty(prefix + "url"));
        xaDataSource.setUsername(props.getProperty(prefix + "username"));
        xaDataSource.setPassword(props.getProperty(prefix + "password"));
        return xaDataSource;
    }

    public static void main(String args[]) {
        try {
            new TwoPhaseCommitApplication().multiDataSourceTest();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

app.properties文件

db1.dbtype=mysql
db1.url=jdbc:mysql://127.0.0.1:3306/archdemo1?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
db1.username=root
db1.password=123456
db2.dbtype=mysql

db2.url=jdbc:mysql://127.0.0.1:3306/archdemo2?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
db2.username=root
db2.password=123456

分析

在这种方案下,我们的代码充当了TM也就是事务资源协调者,而两个不同的数据源mysql充当了RM资源管理着角色,在我们代码中对每个事务的准备情况进行判断,如果都OK则提交事务,如果有没有准备好的则rollback事务.

修改为一个不能正常执行的sql,来查看他的执行过程

image.png

通过断点分析,我们发现程序在执行到这句的时候就会出现异常,也就是在prepare之前sql语句已经在执行了,只不过我们设置了事务不自动提交,所以在数据库中看不到sql_1的执行结果.

修改正常sql,在prepare阶段加断点

image.png

我们在数据库中查看事务情况,因为我是在一个数据库服务器上做的的跨库数据源,所以我们能看到两条xa记录


image.png

我们继续执行到commit语句,放多第一条commit


image.png
这时候可以发现已经第一个事务的xa信息已经没有了,也就是第一个事务分支已经提交成功了.
image.png
数据库中可以看到新插入成功了一条数据
image.png

这是我们尝试修改结构或者插入一条语句,都会发现数据库处于锁定状态


image.png
等我们把断点放行之后,才可以看到其他语句正常执行,也就是说xa在提交阶段会对数据库进行加锁处理,经过进一步的分析我们发现xa在进入xa end后就对整个表进行加锁操作,因为该sql是update语句,所以在xa end 一直到事务提交或者回滚之前,整个表都处于锁定状态.
image.png

延伸

我们很容易将这种XA机制扩展到到微服务情况,需要各个微服务提供相应的机制,各个微服务提供对应的prepare接口、commit接口、rollback接口。

缺点

xA的性能问题

XA的性能很低。一个数据库的事务和多个数据库间的XA事务性能对比可发现,性能差10倍左右。因此要尽量避免XA事务,例如可以将数据写入本地,用高性能的消息系统分发数据。或使用数据库复制等技术。只有在这些都无法实现,且性能不是瓶颈时才应该使用XA

这种机制假定prepare ok的事务都可以正常commit

也就是进入prepare返回ok后,在执行commit阶段两个事务就有可能出现一些异常情况,比如第一个正常提交了,但第二个却出现了某种异常失败了。

相关文章

  • 分布式事务解决方案实战专题-1

    多数据源下分布式事务2PC解决方案 背景 在传统架构中可以使用spring的@Transactional 进行声明...

  • 微服务中分布式事务解决方案

    分布式事务解决方案 1、阿里巴巴seata分布式事务 2、 京东ShardingSphere分布式事务 3、tcc...

  • 微服务 14:初探微服务分布式事务 - Seata

    1:什么是事务,什么是ACID 2:什么是分布式事务 3:分布式事务解决方案 4:Seata 分布式事务框架 5:...

  • 分布式事务的解决方案

    本文从以下几个方面介绍分布式事务的解决方案: 为什么会有分布式事务分布式事务经典模型分布式事务解决方案 为什么会有...

  • 微服务分布式事务--破局

    微服务架构下分布式事务设计实战 商品 订单 支付 分布式事务->长事务本地事务->短事务 分布式事务: 比如 下...

  • 分布式事务解决方案实战专题-3

    基于微服务的TCC解决方案 TCC 事务介绍 在08年的软件开发2.0技术大会上,支付宝程立在PPT大规模SOA系...

  • 分布式事务

    目录 分布式事务解决方案 长事务: saga 短事务: 设计的时候尽量短事务,能不用分布式事务尽量不用,分布式事务...

  • 分布式事务

    最近整理了下分布式事务相关知识及典型应用场景解决方案,主要内容如下: 1、分布式事务1)事务简介2)本地事务讲解3...

  • RocketMQ

    RocketMQ实战(一)RocketMQ实战(二)RocketMQ实战(三):分布式事务RocketMQ实战(四...

  • Java微服务下的分布式事务介绍及其解决方案2

    1.前言 本文将详细介绍分布式的解决方案–消息队列实现分布式事务的解决方案,需要大家对我第一篇对分布式事务的介绍来...

网友评论

    本文标题:分布式事务解决方案实战专题-1

    本文链接:https://www.haomeiwen.com/subject/pnqnwqtx.html