分布式事务解决方案

2020-12-27  本文已影响0人  麦大大吃不胖

by shihang.mai

1. 事件表+定时任务+MQ

分布式事务-定时任务+MQ+事件表

事件表核心表结构

字段 备注
Id 主键
order_type 事件类型,例如派单
state 状态
content 发送到mq的数据,包括本表事件id

加入定时任务,是为了做补偿机制。过了零点,扫面前一天的数据,重新投放到mq即可。

当工单模块消费mq时,创建一样的一条记录到工单系统的事件表。即两个事件表一样的。这样为了做幂等处理。

需要做幂等原因:

  1. 当工单系统完成后返回ack,mq挂了,那么就会出现状态已完成,但是mq还是未消费成功。mq还会重试发送到工单系统

2. TXLCN-LCN

用在本身带事务的场景,如mysql

2.1 原理

lcn组件架构图


lcn组件

lcn原理图如下,摘自tx-lcn官网


lcn官方原理

图中第1 2 3步是第1阶段,第4步是第2阶段(lcn中的n,执行提交或者回滚)

lcn步骤

2.2 原理

核心类DataSourceAspect

@Aspect
@Component
@Slf4j
public class DataSourceAspect implements Ordered {

   private final TxClientConfig txClientConfig;

   private final DTXResourceWeaver dtxResourceWeaver;

   public DataSourceAspect(TxClientConfig txClientConfig, DTXResourceWeaver dtxResourceWeaver) {
       this.txClientConfig = txClientConfig;
       this.dtxResourceWeaver = dtxResourceWeaver;
   }


   @Around("execution(* javax.sql.DataSource.getConnection(..))")
   public Object around(ProceedingJoinPoint point) throws Throwable {
       return dtxResourceWeaver.getConnection(() -> (Connection) point.proceed());
   }


   @Override
   public int getOrder() {
       return txClientConfig.getResourceOrder();
   }


}

核心类LcnConnectionProxy

public class LcnConnectionProxy implements Connection {
  @Override
    public void setAutoCommit(boolean autoCommit) throws SQLException {
        connection.setAutoCommit(false);
    }

    @Override
    public void commit() throws SQLException {
        //connection.commit();
    }

    @Override
    public void rollback() throws SQLException {
        //connection.rollback();
    }

    @Override
    public void close() throws SQLException {
        //connection.close();
    }
}
  1. 利用aop,在getConnection的时候做一个环绕,返回一个Connection,这个Connection是LcnConnectionProxy。直接接管了原来的datasource的Connection。
  2. 在原本连接close阶段假释放,将请求和连接关系保存起来Map<请求Id,Connection>。那么进行第2阶段时就可以找回之前的操作的连接了。

代理datasource,保持请求和连接的对应

2.3 补偿

补偿机制,用到了redis。做了标记(那些Tx-client是异常的),做记录(通知的具体事项或者需要执行的sql)

2.4 核心步骤

TX-manager
mysql执行sql:
        jar包下有对应的创表sql
添加jar:
        <dependency>
            <groupId>com.codingapi.txlcn</groupId>
            <artifactId>txlcn-tm</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>com.codingapi.txlcn</groupId>
            <artifactId>txlcn-tc</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.codingapi.txlcn</groupId>
            <artifactId>txlcn-txmsg-netty</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
添加配置:
        tx-lcn.manager.admin-key=xxx
        tx-lcn.logger.enabled=true
        tx-lcn.logger.driver-class-name=com.mysql.cj.jdbc.Driver
        tx-lcn.logger.jdbc-url=jdbc:mysql://localhost:3306/tx-manager?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
        tx-lcn.logger.username=xxx
        tx-lcn.logger.password=xxx
添加注解:
        启动类上@EnableTransactionManagerServer

TX-client
添加jar:
        <dependency>
            <groupId>com.codingapi.txlcn</groupId>
            <artifactId>txlcn-tc</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>com.codingapi.txlcn</groupId>
            <artifactId>txlcn-txmsg-netty</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
添加配置:
        tx-lcn:
          client:
            manager-address: 127.0.0.1:8070
添加注解:
        启动类@EnableDistributedTransaction
        业务类@LcnTransaction

3. TXLCN-TCC

用在本身不带事务的场景,如redis

3.1 核心步骤

/**
* TX-manager TX-client的添加jar、配置、启动类注解都与lcn一致
*/
//业务类
    @Transactional(rollbackFor = Exception.class)
    @TccTransaction
    public String add(@RequestBody TblOrder bean){

        
        return "xxx";
    }

    public String confirmAdd(TblOrder bean){

        System.out.println("xxx ");
        return "xxx";
    }

    //用来保存第1阶段操作过的id,其中key为(机器-方法-时间戳)等能唯一表示的key
    private static Map<String,Integer> maps = new HashMap<>();

    public String cancelAdd(TblOrder bean){
        //自己做回滚逻辑
        return "xxx";
    }

4. seata

它有4种模式,分别是AT、TCC、SAGA、XA模式

seata中角色

4.1 AT模式

以下有3个库,分别模拟正常流程和异常流程

seata-server global_table, lock_table , branch_table
a yw, undo_log
b yw, undo_log
seata-角色.png

正常流程

  1. 请求到达TM,TM向TC注册一个全局事务,获得一个全局锁,TC向seata-server的global_table插入一条数据
  2. 在TM执行自己业务逻辑时,向TC注册一个分支事务,TC向branch_table和lock_table各插入一条数据,TM获得一个本地锁,然后将业务数据插入a库的yw表和对应的undolog插入a库的undolog表,然后释放本地锁(不是删除库记录,这是容许其他线程竞争)
  3. TM调用RM,RM向TC注册一个分支事务,TC向branch_table和lock_table各插入一条数据,RM获得一个本地锁,然后将业务数据插入b库的yw表和对应的undolog插入b库的undolog表,然后释放本地锁(不是删除库记录,这是容许其他线程竞争)
  4. TM向TC发送commit,TC删除global_table、lock_table、branch_table记录,TC通知TM和RM提交,即通知它们分别删除自己的undo_log表记录

异常流程

  1. 请求到达TM,TM向TC注册一个全局事务,获得一个全局锁,TC向seata-server的global_table插入一条数据
  2. 在TM执行自己业务逻辑时,向TC注册一个分支事务,TC向branch_table和lock_table各插入一条数据,TM获得一个本地锁,然后将业务数据插入a库的yw表和对应的undolog插入a库的undolog表,然后释放本地锁(不是删除库记录,这是容许其他线程竞争)
  3. TM调用RM,RM向TC注册一个分支事务,TC向branch_table和lock_table各插入一条数据,RM获得一个本地锁,然后将业务数据插入b库的yw表和对应的undolog插入b库的undolog表,然后释放本地锁(不是删除库记录,这是容许其他线程竞争)
  4. 如果此时调用链上还有一个RM,那么重复3的步骤,但是其中发生了异常
  5. TM向TC发送roll back,TC通知TM和所有RM回滚,即通知它们分别执行自己的undo_log表记录,在执行undo_log前需要获取本地锁。然后TC删除global_table、lock_table、branch_table记录

4.2 TCC

4.2.1 空回滚

try未执行,执行cancel
解决:加事务控制表

字段 含义
tx_id 全局事务id
branch_id 分支id
state 状态(try、confirm、cancel)

执行cancel时,检查是否有try的记录,没的话进行空回滚

4.2.2 幂等

多次执行conform、cancel
解决:加事务控制表

字段 含义
tx_id 全局事务id
branch_id 分支id
state 状态(事务初始化、已提交、已回滚)

在执行前先检查库记录即可。执行过就不执行

4.2.3 悬挂

执行cancel在try之前
解决:加事务控制表

字段 含义
tx_id 全局事务id
branch_id 分支id
state 状态(事务初始化、已提交、已回滚)

cnacel的时候,如果原来没记录,执行空方法,再向里面插入一条已回滚记录,try执行的时候发现有已回滚记录,空try

上一篇下一篇

猜你喜欢

热点阅读