sharding-jdbc 事务支持部分观后感

官网说明支持的事务

  • 图就不上了,官网有http://shardingjdbc.io/docs/02-guide/transaction/
  • 几个关键字弱XA事务支持,非跨库事务,不支持因网络、硬件异常导致的跨库事务
  • 开始撸代码官网demo,柔性事务管理器,见名知意。功能同spring,事务管理器大同小异,定义getTransaction方法,中间操作包装后的jdbc Connection,后面可看见更直观

柔性事务管理器DEMO

// 1. 配置SoftTransactionConfiguration
    SoftTransactionConfiguration transactionConfig = new SoftTransactionConfiguration(dataSource);
    transactionConfig.setXXX();
    // 2. 初始化SoftTransactionManager
    SoftTransactionManager transactionManager = new SoftTransactionManager(transactionConfig);
    transactionManager.init();
    // 3. 获取BEDSoftTransaction
    BEDSoftTransaction transaction = (BEDSoftTransaction) transactionManager.getTransaction(SoftTransactionType.BestEffortsDelivery);
    // 4. 开启事务
    transaction.begin(connection);
    // 5. 执行JDBC
    // 6.关闭事务
    transaction.end();
  • 分布式事务除了强XA,无非都是借住外部存储,心存这个概念,看代码顺畅很多。
  • @1处可发现,事务库可支持数据库或内存hashMap存储,可配置。
  • @2处,先准备数据,创建事务库的sql,本质是个table,记录相关信息,包括sql,参数,时间,重试次数等。 上下文参数保存传递,通过threadlocal包装后的工具,很通用,不做重点说明 其中关键方法SoftTransactionManager 中的getTransaction

柔性事务管理器

@RequiredArgsConstructor
public final class SoftTransactionManager {
    private static final String TRANSACTION = "transaction";
    private static final String TRANSACTION_CONFIG = "transactionConfig";
    @Getter
    private final SoftTransactionConfiguration transactionConfig;
	
    public void init() throws SQLException {
        //@3
        EventBusInstance.getInstance().register(new BestEffortsDeliveryListener());    
        //@1
        if (TransactionLogDataSourceType.RDB == transactionConfig.getStorageType()) {
            Preconditions.checkNotNull(transactionConfig.getTransactionLogDataSource());
            //@2
            createTable();
        }
		......略
    }
    private void createTable() throws SQLException {
        String dbSchema = "CREATE TABLE IF NOT EXISTS `transaction_log` ("
                + "`id` VARCHAR(40) NOT NULL, "
                + "`transaction_type` VARCHAR(30) NOT NULL, "
                + "`data_source` VARCHAR(255) NOT NULL, "
                + "`sql` TEXT NOT NULL, "
                + "`parameters` TEXT NOT NULL, "
                + "`creation_time` LONG NOT NULL, "
                + "`async_delivery_try_times` INT NOT NULL DEFAULT 0, "
                + "PRIMARY KEY (`id`));";
        try (
                Connection conn = transactionConfig.getTransactionLogDataSource().getConnection();
                PreparedStatement preparedStatement = conn.prepareStatement(dbSchema)) {
            preparedStatement.executeUpdate();
        }
    }
  • @3比较关键,guava的EventBus工具,没接触过的话,可理解为一个内存队列,在柔性事务管理器里注册消费者的行为,举例:如更新10条记录,执行前会发送这10个sql相关信息,在事务管理器里收到消息,记录到事务库。
  • @4 更改表之前,预先插入sql相关信息到事务库,@5表更新完成后,清除事务库对应记录,@6执行sql重试操作,包括处理重试次数,成功后删除事务库记录

最大努力推送监听器

@Slf4j
public final class BestEffortsDeliveryListener {
    
    @Subscribe
    @AllowConcurrentEvents
    public void listen(final DMLExecutionEvent event) {
        .....略
        BEDSoftTransaction bedSoftTransaction = (BEDSoftTransaction) SoftTransactionManager.getCurrentTransaction().get();
        switch (event.getEventExecutionType()) {
            //@4
            case BEFORE_EXECUTE:
                //TODO for batch SQL need split to 2-level records
                transactionLogStorage.add(new TransactionLog(event.getId(), bedSoftTransaction.getTransactionId(), bedSoftTransaction.getTransactionType(), 
                        event.getDataSource(), event.getSql(), event.getParameters(), System.currentTimeMillis(), 0));
                return;
            //@5
            case EXECUTE_SUCCESS: 
                transactionLogStorage.remove(event.getId());
                return;
            //@6
            case EXECUTE_FAILURE: 
                boolean deliverySuccess = false;
                for (int i = 0; i < transactionConfig.getSyncMaxDeliveryTryTimes(); i++) {
                    if (deliverySuccess) {
                        return;
                    }
                    boolean isNewConnection = false;
                    Connection conn = null;
                    PreparedStatement preparedStatement = null;
                    try {
                         .....
                        preparedStatement.executeUpdate();
                        .....
                        transactionLogStorage.remove(event.getId());
                    } catch (final SQLException ex) {
                        log.error(String.format("Delivery times %s error, max try times is %s", i + 1, transactionConfig.getSyncMaxDeliveryTryTimes()), ex);
                    } finally {
                        close(isNewConnection, conn, preparedStatement);
                    }
//2种事务实现
public AbstractSoftTransaction getTransaction(final SoftTransactionType type) {
        AbstractSoftTransaction result;
        switch (type) {
            case BestEffortsDelivery: 
                result = new BEDSoftTransaction();
                break;
            case TryConfirmCancel:
                result = new TCCSoftTransaction();
                break;
            default: 
                throw new UnsupportedOperationException(type.toString());
        }
//继续深入
public class BEDSoftTransaction extends AbstractSoftTransaction {
    public void begin(final Connection connection) throws SQLException {
        beginInternal(connection, SoftTransactionType.BestEffortsDelivery);
    }
  • 看到了熟悉的connection.setAutoCommit(true);开启自动提交,由事务管理器操作Connection完成
  • 因为是 单Connection里异常了数据也不会改变,跨Connection的事务由定时任务+事务库保证,所以设置自动提交。

开启事务

public abstract class AbstractSoftTransaction {
    
    private boolean previousAutoCommit;
     @Getter
    private ShardingConnection connection;
    @Getter
    private SoftTransactionType transactionType;
    @Getter
    private String transactionId;
    
    protected final void    beginInternal(final Connection conn, final SoftTransactionType type) throws SQLException {
        connection = (ShardingConnection) conn;
     
        previousAutoCommit = connection.getAutoCommit();

        connection.setAutoCommit(true);

    }
}
  • 到此,可大概了解事务管理实现分布式功能,操作要被事务管理的Connection,并且接受来自EventBus的消息,操作事务库,完成单Connection的事务

具体的Connection的包装,执行更改发送EventBus消息,下面继续

  • 比较直接的源码,根据查询流程一步步进到ShardingPreparedStatement
  • @1,route()根据分库分表生成路由PreoareStatement,到@2去执行

路由,执行

@Override
    public boolean execute() throws SQLException {
        try {
            //@1
            Collection<PreparedStatementUnit> preparedStatementUnits = route();
            //@2
            return new PreparedStatementExecutor(
                    getConnection().getShardingContext().getExecutorEngine(), routeResult.getSqlStatement().getType(), preparedStatementUnits, getParameters()).execute();
        } finally {
            clearBatch();
        }
    }
  • 一路跟进到ExecutorEngine执行引擎,发现最后更新表方法最后到execute方法
  • @1,不管成不成功,先插入事务表,成功再删。比较可靠的方法了....
  • @2,还是Guava的异步执行方法,见Guava API,事实上在这里已经执行完表更改,哪怕有异常,只要没有get(),一切继续
  • @3,获取异步执行结果,不懂的话,见JDK Future API
  • @4,异常出现了,投递消息去重试了
  • @5,一切正常,投递消息,清除事务库。前面已设置自动提交,或交给spring事务管理器等。操作定义在事务器里,下面继续

具体执行流程

@Slf4j
public final class ExecutorEngine implements AutoCloseable {
    
    private final ListeningExecutorService executorService;
    
    private  <T> List<T> execute(
            final SQLType sqlType, final Collection<? extends BaseStatementUnit> baseStatementUnits, final List<List<Object>> parameterSets, final ExecuteCallback<T> executeCallback) {
        if (baseStatementUnits.isEmpty()) {
            return Collections.emptyList();
        }
        //@1 执行前插入
        OverallExecutionEvent event = new OverallExecutionEvent(sqlType, baseStatementUnits.size());
        //投递到EventBus,事务管理区去处理
        EventBusInstance.getInstance().post(event);
        Iterator<? extends BaseStatementUnit> iterator = baseStatementUnits.iterator();
        BaseStatementUnit firstInput = iterator.next();
        //@2 
        ListenableFuture<List<T>> restFutures = asyncExecute(sqlType, Lists.newArrayList(iterator), parameterSets, executeCallback);
        T firstOutput;
        List<T> restOutputs;
        try {
            firstOutput = syncExecute(sqlType, firstInput, parameterSets, executeCallback);
            //@3 
            restOutputs = restFutures.get();
            //CHECKSTYLE:OFF
        } catch (final Exception ex) {
            //CHECKSTYLE:ON
            event.setException(ex);
            event.setEventExecutionType(EventExecutionType.EXECUTE_FAILURE);
            //@4
            EventBusInstance.getInstance().post(event);
            ExecutorExceptionHandler.handleException(ex);
            return null;
        }
        //@5
        event.setEventExecutionType(EventExecutionType.EXECUTE_SUCCESS);
        EventBusInstance.getInstance().post(event);
        List<T> result = Lists.newLinkedList(restOutputs);
        result.add(0, firstOutput);
        return result;
    }
  • @1,收集Connection的setAutoCommit操作,在准备执行sql前反射调用,更改数据库的自动提交。代码比较简单,一笔带过
public abstract class AbstractConnectionAdapter extends AbstractUnsupportedOperationConnection {
    
    @Getter
    private final Map<String, Connection> cachedConnections = new HashMap<>();
 
    @Override
    public final boolean getAutoCommit() throws SQLException {
        return autoCommit;
    }
    
    @Override
    public final void setAutoCommit(final boolean autoCommit) throws SQLException {
        this.autoCommit = autoCommit;
        //@1
        recordMethodInvocation(Connection.class, "setAutoCommit", new Class[] {boolean.class}, new Object[] {autoCommit});
        for (Connection each : cachedConnections.values()) {
            each.setAutoCommit(autoCommit);
        }
    }
    
    @Override
    public final void commit() throws SQLException {
        Collection<SQLException> exceptions = new LinkedList<>();
        for (Connection each : cachedConnections.values()) {
            try {
                    each.commit();
            } catch (final SQLException ex) {
                exceptions.add(ex);
            }
        }
        throwSQLExceptionIfNecessary(exceptions);
    }
    
    @Override
    public final void rollback() throws SQLException {
        Collection<SQLException> exceptions = new LinkedList<>();
        for (Connection each : cachedConnections.values()) {
            try {
                each.rollback();
            } catch (final SQLException ex) {
                exceptions.add(ex);
            }
        }
        throwSQLExceptionIfNecessary(exceptions);
    }

分布式事务在Sharding-jdbc的实现

  • 3个经典的JDBC操作,Sharding-jdbc,在这里实现了具体Connection操作的实现,这就比较灵活了,常用的如Spring的DataSourceTransactionManager事务管理器,在Connection上调用实现时,交给sharding-jdbc里的这些实现了。
  • 最后总结一下上面没说清的问题,分布式事务若XA在Sharding-jdbc中的流程。
  1. 开启自动提交,收集Connection到集合
  2. 执行前插入事务库
  3. 执行JDBC代码
  4. 异常,遍历集合中Connection,事务回滚/ 正常,遍历集合中Connection,事务提交
  5. 异常会有定时任务轮训重试事务库中sql/ 正常,清除事务库记录
  • 解释了上面的3个关键字弱XA事务支持,非跨库事务,不支持因网络、硬件异常导致的跨库事务
  1. 弱XA,单Connection事务保证
  2. 跨库,还是因为是基于Connection
  3. 硬件啊,网络异常,可能导致事务库记录有问题,没办法恢复。不像Mysql会有binlog崩溃备份恢复。
  • 分库分表的路由,sql改写,专门在另一篇研究

转载于:https://my.oschina.net/u/1432304/blog/1607391

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值