分布式事务与seata(oracle版)的使用

背景

前端时间公司的Oracle项目改造发觉需要用到分布式事务,顾继续回顾加强学习一下。

分布式事务

事务

要说分布式事务,首先还是从事务的基本特征说起。

  • A 原子性 :在事务的执行过程中,要么全部执行成功,要么都不成功。
  • C 一致性 :事务在执行前后,不能破坏数据的完整性。一致性更多的说的是通过 AID 来达到目的,数据应该符合预先的定义和约束,由应用层面来保证,还有的说法是 C 是强行为了 ACID 凑出来的。
  • I 隔离性:多个事务之间是互相隔离的,事务之间不能互相干扰,涉及到不同事务的隔离级别的问题。
  • D 持久性:一旦事务提交,数据库中数据的状态就应该是永久性的。.

分布式事务产生的原因

  • 服务SOA化
  • 数据库分库分表
XA

XA(eXtended Architecture)是指由 X/Open 组织提出的分布式事务处理的规范,他是一个规范或者说是协议,定义了事务管理器 TM(Transaction Manager),资源管理器RM(Resource Manager),和应用程序。

事务管理器 TM 就是事务的协调者,资源管理器 RM 可以认为就是一个数据库。

在这里插入图片描述

java代码:

A factory for XAConnection objects that is used internally. An object that implements the XADataSource interface is typically registered with a naming service that uses the Java Naming and Directory Interface™ (JNDI).
An implementation of XADataSource must include a public no-arg constructor.
Since:
1.4
public interface XADataSource extends CommonDataSource {
。。。
}

代码规范http://www.nssi.org.cn/nssi/front/3650570.html

XA与JTA

XA : XA是一个规范或是一个事务的协议.XA协议由Tuxedo首先提出的,并交给X/Open组织,作为资源管理器(数据库)与事务管理器的接口标准.

JTA(Java Transaction Manager) : 是Java规范,是XA在Java上的实现
  • TransactionManager : 常用方法,可以开启,回滚,获取事务. begin(),rollback()…
  • XAResouce : 资源管理,通过Session来进行事务管理,commit(xid)…
  • XID : 每一个事务都分配一个特定的XID
实现例子
  • 相关的依赖,spring-boot-starter-jta-atomikos 依赖,这是一个开源的事务管理器类
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
 
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
 
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
 
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
 
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jta-atomikos</artifactId>
        </dependency>
    </dependencies>
  • 定义数据源
/**
 * Created by zhangjunwei on 2017/8/2.
 */
@Configuration
public class DataSourceConfig {


    /**
     * db1的 XA datasource
     *
     * @return
     */
    @Bean(name = "symbolOrder")
    @Primary
    @Qualifier("symbolOrder")
    public AtomikosDataSourceBean symbolOrderBean() {
        AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
        atomikosDataSourceBean.setUniqueResourceName("symbolOrder");
        atomikosDataSourceBean.setXaDataSourceClassName(
                "com.mysql.jdbc.jdbc2.optional.MysqlXADataSource");
        Properties properties = new Properties();
        properties.put("URL","jdbc:mysql://localhost:3306/datamanage");
        properties.put("user", "root");
        properties.put("password", "123456");
        atomikosDataSourceBean.setXaProperties(properties);
        return atomikosDataSourceBean;
    }


    /**
     * db2的 XA datasource
     *
     * @return
     */
    @Bean(name = "symbolPosition")
    @Qualifier("symbolPosition")
    public AtomikosDataSourceBean symbolPositionDataSourceBean() {
        AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
        atomikosDataSourceBean.setUniqueResourceName("symbolPosition");
        atomikosDataSourceBean.setXaDataSourceClassName(
                "com.mysql.jdbc.jdbc2.optional.MysqlXADataSource");
        Properties properties = new Properties();
        properties.put("URL", "jdbc:mysql://localhost:3306/symbol_position");
        properties.put("user", "root");
        properties.put("password", "123456");
        atomikosDataSourceBean.setXaProperties(properties);
        return atomikosDataSourceBean;
    }

    /**
     * transaction manager
     *
     * @return
     */
    @Bean(destroyMethod = "close", initMethod = "init")
    public UserTransactionManager userTransactionManager() {
        UserTransactionManager userTransactionManager = new UserTransactionManager();
        userTransactionManager.setForceShutdown(true);
        return userTransactionManager;
    }

    /**
     * jta transactionManager
     *
     * @return
     */
    @Bean
    public JtaTransactionManager transactionManager() {
        JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
        jtaTransactionManager.setTransactionManager(userTransactionManager());
        return jtaTransactionManager;
    }

}

//测试类
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = UserManageStart.class)
public class DataSouceTest {
    @Autowired
    @Qualifier("symbolOrder")
    private AtomikosDataSourceBean symbolOrder;
 
    @Autowired
    @Qualifier("symbolPosition")
    private AtomikosDataSourceBean symbolPosition;
 
 
    @Transactional
    @Test
    public void test() {
        //。。。
    }

Jta特点
  • JTA的特点就是能够支持多数据库事务同时事务管理,满足分布式系统中的数据的一致性.但是也有对应的弊端:
    • 两阶段提交
    • 事务时间太长,锁数据太长
    • 低性能,低吞吐量
2PC
* XA 定义了规范,那么 2PC 和 3PC 就是它的具体实现方式。
* 2PC 叫做二阶段提交,分为投票阶段和执行阶段两个阶段。
  • 投票阶段
    TM 向所有的参与者发送 prepare 请求,询问是否可以执行事务,等待各个参与者的响应。
    这个阶段可以认为只是执行了事务的 SQL 语句,但是还没有提交。
    如果都执行成功了就返回 YES,否则返回 NO。
    在这里插入图片描述

  • 执行阶段
    执行阶段就是真正的事务提交的阶段,但是要考虑到失败的情况。
    如果所有的参与者都返回 YES,那么就执行发送 commit 命令,参与者收到之后执行提交事务。
    反之,只要有任意一个参与者返回的是 NO 的话,就发送 rollback 命令,然后执行回滚的操作。
    在这里插入图片描述

  • 2PC 的缺陷

  • 同步阻塞,可以看到,在执行事务的过程当中,所有数据库的资源都被锁定,如果这时候有其他人来访问这些资源,将会被阻塞,这是一个很大的性能问题。

  • TM 单点问题,只有一个 TM,一旦 TM 宕机,那么整个流程无法继续完成。

  • 数据不一致,如果在执行阶段,参与者脑裂或者其他故障导致没有收到 commit 请求,部分提交事务,部分未提交,那么数据不一致的问题就产生了。

3PC

既然 2PC 有这么多问题,所以就衍生出了 3PC 的概念,也叫做三阶段提交,他把整个流程分成了 CanCommit、PreCommit、DoCommit 三个步骤,相比 2PC,增加的就是 CanCommit 阶段。

  • CanCommit
    这个阶段就是先询问数据库是否执行事务,发送一个 canCommit 的请求去询问,如果可以的话就返回 YES,反之返回 NO。
    在这里插入图片描述
    但是,这个地方的区别在于参与者有了超时机制,如果参与者超时未收到 doCommit 命令的话,将会默认去提交事务。
  • DoCommit
    DoCommit 阶段对应到2PC的执行阶段,如果上一个阶段都是收到 YES 的话,那么就发送 doCommit 命令去提交事务,反之则会发送 abort 命令去中断事务的执行。
    在这里插入图片描述
  • 相比 2PC 的改进
  • 对于 2PC 的同步阻塞的问题,我们可以看到因为 3PC 加入了参与者的超时机制,所以原来 2PC 的如果某个参与者故障导致的同步阻塞的问题时间缩短了,这是一个优化,但是并没有完全避免。
  • 第二个单点故障的问题,同样因为超时机制的引入,一定程度上也算是优化了。但是数据不一致的问题,这个始终没有得到解决。
  • 举个栗子:
    在 PreCommit 阶段,某个参与者发生脑裂,无法收到 TM 的请求,这时候其他参与者执行 abort 事务回滚,而脑 裂的参与者超时之后继续提交事务,还是有可能发生数据不一致的问题。 那么,为什么要加入 DoCommit 这个阶段呢?就是为了引入超时机制,事先我们先确认数据库是否都可以执行事务,如果都 OK,那么才会进入后面的步骤,所以既然都可以执行,那么超时之后说明发生了问题,就自动提交事务。
TCC

TCC 的模式叫做 Try、Confirm、Cancel,实际上也就是 2PC 的一个变种而已。
实现这个模式,一个事务的接口需要拆分成3个,也就是 Try 预占、Confirm 确认提交、最后Cancel回滚。
对于 TCC 来说,实际生产我基本上就没看见过有人用,考虑到原因,首先是程序员的本身素质参差不齐,多个团队协作你很难去约束别人按照你的规则来实现,另外一点就是太过于复杂。

SAGA

Saga 源于1987 年普林斯顿大学的 Hecto 和 Kenneth 发表的如何处理 long lived transaction(长活事务)论文。
主要思想就是将长事务拆分成多个本地短事务。
如果全部执行成功,就正常完成了,反之,则会按照相反的顺序依次调用补偿。
SAGA模式有两种恢复策略:

  • 向前恢复,这个模式偏向于一定要成功的场景,失败则会进行重试。
  • 向后恢复,也就是发生异常的子事务依次回滚补偿。
消息队列

基于消息队列来实现最终一致性的方案,一般来说有两种方式,基于本地消息表和依赖 MQ 本身的事务消息。
本地消息表的这个方案其实更复杂,实际上也没看到过真正谁来用。这里以 RocketMQ 的事务消息来举例,这个方式相比本地消息表则更完全依赖MQ本身的特性做了解耦,释放了业务开发的复杂工作量。
在这里插入图片描述

  • 业务发起方,调用远程接口,向 MQ 发送一条半事务消息,MQ 收到消息之后会返回给生产者一个 ACK。
  • 生产者收到 ACK 之后,去执行事务,但是事务还没有提交。
  • 生产者会根据事务的执行结果来决定发送 commit 提交或者 rollback 回滚到 MQ。
  • 这一点是发生异常的情况,比如生产者宕机或者其他异常导致 MQ 长时间没有收到 commit 或者 rollback 的消息,这时候 MQ 会发起状态回查。
  • MQ 如果收到的是 commit 的话就会去投递消息,消费者正常消费消息即可。如果是 rollback 的话,则会在设置的固定时间期限内去删除消息。
  • 这个方案基于 MQ 来保证消息事务的最终一致性,还算是一个比较合理的解决方案,只要保证 MQ 的可靠性就可以正常实施应用,业务消费方根据本身的消息重试达到最终一致性。
主要代码实现:
  • 实现RocketMQLocalTransactionListener
@Component
@RocketMQTransactionListener(txProducerGroup = "rocket")
public class TransactionListener implements RocketMQLocalTransactionListener{

    @Autowired
    private PayLogService payLogService;


    /****
     * 当向RocketMQ的Broker发送Half消息成功之后,调用该方法
     * @param msg:发送的消息
     * @param arg:额外参数
     * @return
     */
    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        try {
            //========================本地事务控制===================
            //消息
            String result = new String((byte[]) msg.getPayload(),"UTF-8");
            PayLog payLog = JSON.parseObject(result,PayLog.class);
            payLogService.add(payLog);
            //========================本地事务控制===================
        } catch (Exception e) {
            e.printStackTrace();
            return RocketMQLocalTransactionState.ROLLBACK;
        }
        return RocketMQLocalTransactionState.COMMIT;
    }

    /***
     * 超时回查
     * @param msg
     * @return
     */
    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
        return RocketMQLocalTransactionState.COMMIT;
    }
}
  • 实现RocketMQPushConsumerLifecycleListener
    ``
    @Component
    @RocketMQMessageListener(topic = “log”,consumerGroup = “resultgroup”)
    public class OrderResultListener implements RocketMQListener,RocketMQPushConsumerLifecycleListener {
    @Override
    public void onMessage(Object message) {
    }

    /****

    • 消息监听
    • @param consumer
      */
      @Override
      public void prepareStart(DefaultMQPushConsumer consumer) {
      consumer.registerMessageListener(new MessageListenerConcurrently() {
      @Override
      public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) {
      try {
      for (MessageExt msg : msgs) {
      String result = new String(msg.getBody(),“UTF-8”);
      System.out.println(“result::::::”+result);
      }
      } catch (UnsupportedEncodingException e) {
      e.printStackTrace();
      }
      return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
      }
      });
      }
      }
      ``
框架

GTS(Global Transaction Service 全局事务服务)是阿里云的中间件产品,只要你用阿里云,付钱就可以用 GTS。

Seata(Simple Extensible Autonomous Transaction Architecture)则是开源的分布式事务框架,提供了对 TCC、XA、Saga 以及 AT 模式的支持。(官网:https://seata.io/zh-cn/)

  • AT 模式
  • 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
  • 二阶段:
    • 提交异步化,非常快速地完成。
    • 回滚通过一阶段的回滚日志进行反向补偿。

执行的流程如下:

  • TM 向 TC 注册全局事务,获得 XID。
  • RM 则会去代理 JDBC 数据源,生成镜像的 SQL,形成 UNDO_LOG,然后向 TC 注册分支事务,把数据更新和 UNDO_LOG 在本地事务中一起提交。
  • TC 如果收到 commit 请求,则会异步去删除对应分支的 UNDO_LOG,如果是 rollback,就去查询对应分支的 UNDO_LOG,通过 UNDO_LOG 来执行回滚。
    在这里插入图片描述
  • seata实现步骤,网上Oracle相关的资料少,亲测demo可用
    • 创建undo_log表
      CREATE TABLE undo_log ( ​ id NUMBER(19) NOT NULL, ​ branch_id NUMBER(19) NOT NULL, ​ xid VARCHAR2(100) NOT NULL, ​ context VARCHAR2(128) NOT NULL, ​ rollback_info BLOB NOT NULL, ​ log_status NUMBER(10) NOT NULL, ​ log_created TIMESTAMP(0) NOT NULL, ​ log_modified TIMESTAMP(0) NOT NULL, ​ PRIMARY KEY (id), ​ CONSTRAINT ux_undo_log UNIQUE (xid, branch_id) );
    • 启动seata服务
    • 配置file.conf,registry.conf,和application.properties事务组,如:
      	properties
        spring.application.name=order-service
        server.port=8082
        #spring.datasource.url=jdbc:mysql://localhost:3306/fescar?useSSL=false&serverTimezone=UTC
        #spring.datasource.username=root
        #spring.datasource.password=root		  
        spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
        spring.datasource.url=jdbc:oracle:thin:@47.107.253.254:1521/helowinXDB
        spring.datasource.username=system
        spring.datasource.password=system		  
        spring.cloud.alibaba.seata.tx-service-group=my_test_tx_group
        logging.level.org.springframework.cloud.alibaba.seata.web=debug
        logging.level.io.seata=debug
        eureka.instance.hostname=127.0.0.1
        eureka.instance.prefer-ip-address=true
        eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:8761/eureka/
        feign.hystrix.enabled=true
        spring.main.allow-bean-definition-overriding=true
      
    • 需要用的代码打上@GlobalTransactional标签
    • https://github.com/David0101/seata-samples.git
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值