sharding-jdbc系列之柔性事物(十三)

这张图是从官网上下载下来的,

说明:

  1. 对于sql的执行,在执行前记录日志,如果执行成功,把日志删除,如果执行失败,重试一定次数(如果未达到最大尝试次数便执行成功了,一样删除日志)。
  2. 异步任务不断扫描执行日志,如果重试次数未达到最大上限,尝试重新执行,如果执行成功,删除日志。

从上面两点分析可以看出,由于采用的是重试的模式,也就是说同一条语句,是有可能被多次执行的,所以官方提到了柔性事务的适用场景:

  • 根据主键删除数据。
  • 更新记录永久状态,如更新通知送达状态。

而且它还有一定的限制: SQL需要满足幂等性,具体为:

  • INSERT语句要求必须包含主键,且不能是自增主键。
  • UPDATE语句要求幂等,不能是UPDATE xxx SET x=x+1
  • DELETE语句无要求。

事件发布

private <T> T executeInternal(final SQLType sqlType, final BaseStatementUnit baseStatementUnit, final List<List<Object>> parameterSets, 
                              private AbstractExecutionEvent getExecutionEvent(final SQLType sqlType, final BaseStatementUnit baseStatementUnit, final List<Object> parameters) {
        AbstractExecutionEvent result;
        if (SQLType.DQL == sqlType) {
            result = new DQLExecutionEvent(baseStatementUnit.getSqlExecutionUnit().getDataSource(), baseStatementUnit.getSqlExecutionUnit().getSql(), parameters);
        } else {
            result = new DMLExecutionEvent(baseStatementUnit.getSqlExecutionUnit().getDataSource(), baseStatementUnit.getSqlExecutionUnit().getSql(), parameters);
        }
        return result;
    }final ExecuteCallback<T> executeCallback, 
                          final boolean isExceptionThrown, final Map<String, Object> dataMap) throws Exception {
        synchronized (baseStatementUnit.getStatement().getConnection()) {
            T result;
            ExecutorExceptionHandler.setExceptionThrown(isExceptionThrown);
            ExecutorDataMap.setDataMap(dataMap);
              // 设置事件集合
            List<AbstractExecutionEvent> events = new LinkedList<>();
            if (parameterSets.isEmpty()) {
                  // 如果参数为空则放入一个空的参数集合,并且获取执行事件
                events.add(getExecutionEvent(sqlType, baseStatementUnit, Collections.emptyList()));
            }
            for (List<Object> each : parameterSets) {
                  // 将有参数的构建一个执行事件,放入集合
                events.add(getExecutionEvent(sqlType, baseStatementUnit, each));
            }
            for (AbstractExecutionEvent event : events) {
                  // 发布执行事件,用于监听事件的执行结果
                EventBusInstance.getInstance().post(event);
            }
            try {
                  // SQL 执行
                result = executeCallback.execute(baseStatementUnit);
            } catch (final SQLException ex) {
                  // SQL执行 出现异常
                for (AbstractExecutionEvent each : events) {
                      // 循环事件,将事件的执行结果设置为失败
                    each.setEventExecutionType(EventExecutionType.EXECUTE_FAILURE);
                    each.setException(Optional.of(ex));
                      // 发布事件
                    EventBusInstance.getInstance().post(each);
                    // 收集异常
                    ExecutorExceptionHandler.handleException(ex);
                }
                return null;
            }
            for (AbstractExecutionEvent each : events) {
                  // 执行的很成功,修改事件的执行状态为SUCCESS
                each.setEventExecutionType(EventExecutionType.EXECUTE_SUCCESS);
                  // 发布
                EventBusInstance.getInstance().post(each);
            }
            return result;
        }
    }
private AbstractExecutionEvent getExecutionEvent(final SQLType sqlType, final BaseStatementUnit baseStatementUnit, final List<Object> parameters) {
        AbstractExecutionEvent result;
        if (SQLType.DQL == sqlType) { //查询语句
            result = new DQLExecutionEvent(baseStatementUnit.getSqlExecutionUnit().getDataSource(), baseStatementUnit.
                                           getSqlExecutionUnit().getSql(), parameters);
        } else { //其他操作
            result = new DMLExecutionEvent(baseStatementUnit.getSqlExecutionUnit().getDataSource(), baseStatementUnit.
                                           getSqlExecutionUnit().getSql(), parameters);
        }
        return result;

说明:

1.上面的源码,是在SQL最终执行的时候执行的代码,从上面可以看到,在执行SQL之前,sharding-jdbc创建了一系列的事件,通过EventBus这个gauva的工具类发布事件,最终由带有@Subscribe这个注解的方法监听到, 说白了,就是这个地方发布事件,然后,有一个监听器进行监听到。

2.当执行SQL发生异常的时候,会发布失败事件

3.SQL执行完成,发布成功事件

监听器

源码位置:io.shardingsphere.transaction.bed.sync.BestEffortsDeliveryListener

public final class BestEffortsDeliveryListener {

    /**
     * Listen event.
     * 
     * @param event dml execution event
     */
    @Subscribe
    @AllowConcurrentEvents
    public void listen(final DMLExecutionEvent event) {
        if (!isProcessContinuously()) {
            return;
        }
          // 获取柔性事物的配置
        SoftTransactionConfiguration transactionConfig = SoftTransactionManager.getCurrentTransactionConfiguration().get();
          // 获取日志存储
        TransactionLogStorage transactionLogStorage = TransactionLogStorageFactory.createTransactionLogStorage(transactionConfig.buildTransactionLogDataSource());
          // 
        BEDSoftTransaction bedSoftTransaction = (BEDSoftTransaction) SoftTransactionManager.getCurrentTransaction().get();
        switch (event.getEventExecutionType()) {
            case BEFORE_EXECUTE: 
                //TODO for batch SQL need split to 2-level records
                // SQL 开始执行,将传入的时间,构建成功事物日志,插入存储
                transactionLogStorage.add(new TransactionLog(event.getId(), bedSoftTransaction.getTransactionId(), bedSoftTransaction.getTransactionType(), 
                        event.getDataSource(), event.getSqlUnit().getSql(), event.getParameters(), System.currentTimeMillis(), 0));
                return;
            case EXECUTE_SUCCESS: 
                // 事物执行成功,则移除日志
                transactionLogStorage.remove(event.getId());
                return;
            case EXECUTE_FAILURE: 
                boolean deliverySuccess = false;
                // SQL执行失败,现在开始重试,getSyncMaxDeliveryTryTimes为最大重试次数
                for (int i = 0; i < transactionConfig.getSyncMaxDeliveryTryTimes(); i++) {
                    if (deliverySuccess) {
                        return;
                    }
                    boolean isNewConnection = false;
                    Connection conn = null;
                      // 获取jdbc连接,进行SQL重试
                    PreparedStatement preparedStatement = null;
                    try {
                        conn = bedSoftTransaction.getConnection().getConnection(event.getDataSource());
                        if (!isValidConnection(conn)) {
                            bedSoftTransaction.getConnection().release(conn);
                            conn = bedSoftTransaction.getConnection().getConnection(event.getDataSource());
                            isNewConnection = true;
                        }
                        preparedStatement = conn.prepareStatement(event.getSqlUnit().getSql());
                        //TODO for batch event need split to 2-level records
                        for (int parameterIndex = 0; parameterIndex < event.getParameters().size(); parameterIndex++) {
                            preparedStatement.setObject(parameterIndex + 1, event.getParameters().get(parameterIndex));
                        }
                        preparedStatement.executeUpdate();
                        deliverySuccess = true; // 重试成功
                        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);
                    }
                }
                return;
            default: 
                throw new UnsupportedOperationException(event.getEventExecutionType().toString());
        }
    }

我们默认使用sharding-jdbc是没有事物的,如果要用他的柔性事物,需要引入他的transaction的jar包才能使用哦

事物总结图:

猜你喜欢

转载自blog.csdn.net/u012394095/article/details/81775465
今日推荐