資料庫分庫分表中介軟體 Sharding-JDBC 原始碼分析 —— SQL 執行

weixin_34239169發表於2017-10-08

摘要: 原創出處 www.iocoder.cn/Sharding-JD… 「芋道原始碼」歡迎轉載,保留摘要,謝謝!

本文主要基於 Sharding-JDBC 1.5.0 正式版


???關注微信公眾號:【芋道原始碼】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有原始碼分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文註釋原始碼 GitHub 地址
  3. 您對於原始碼的疑問每條留言將得到認真回覆。甚至不知道如何讀原始碼也可以請教噢
  4. 新的原始碼解析文章實時收到通知。每週更新一篇左右
  5. 認真的原始碼交流微信群。
  6. 掘金Java QQ 群:217878901

1. 概述

越過千山萬水(SQL 解析、SQL 路由、SQL 改寫),我們終於來到了 SQL 執行。開森不開森?!

本文主要分享SQL 執行的過程,不包括結果聚合《結果聚合》 東半球第二良心筆者會更新,關注微信公眾號【芋道原始碼】完稿後第一時間通知您喲。

綠框部分 SQL 執行主流程。


Sharding-JDBC 正在收集使用公司名單:傳送門
? 你的登記,會讓更多人蔘與和使用 Sharding-JDBC。傳送門
Sharding-JDBC 也會因此,能夠覆蓋更多的業務場景。傳送門
登記吧,騷年!傳送門

2. ExecutorEngine

ExecutorEngine,SQL執行引擎。

分表分庫,需要執行的 SQL 數量從單條變成了多條,此時有兩種方式執行:

  • 序列執行 SQL
  • 並行執行 SQL

前者,編碼容易,效能較差,總耗時是多條 SQL 執行時間累加。
後者,編碼複雜,效能較好,總耗時約等於執行時間最長的 SQL。

? ExecutorEngine 當然採用的是後者,並行執行 SQL。

2.1 ListeningExecutorService

Guava( Java 工具庫 ) 提供的繼承自 ExecutorService 的執行緒服務介面,提供建立 ListenableFuture 功能。ListenableFuture 介面,繼承 Future 介面,有如下好處:

我們強烈地建議你在程式碼中多使用ListenableFuture來代替JDK的 Future, 因為:

  • 大多數Futures 方法中需要它。
  • 轉到ListenableFuture 程式設計比較容易。
  • Guava提供的通用公共類封裝了公共的操作方方法,不需要提供Future和ListenableFuture的擴充套件方法。

傳統JDK中的Future通過非同步的方式計算返回結果:在多執行緒運算中可能或者可能在沒有結束返回結果,Future是執行中的多執行緒的一個引用控制程式碼,確保在服務執行返回一個Result。

ListenableFuture可以允許你註冊回撥方法(callbacks),在運算(多執行緒執行)完成的時候進行呼叫, 或者在運算(多執行緒執行)完成後立即執行。這樣簡單的改進,使得可以明顯的支援更多的操作,這樣的功能在JDK concurrent中的Future是不支援的。

如上內容來自《Google Guava包的ListenableFuture解析
,文章寫的很棒。下文你會看到 Sharding-JDBC 是如何通過 ListenableFuture 簡化併發程式設計的

下面看看 ExecutorEngine 如何初始化 ListeningExecutorService

// ShardingDataSource.java
public ShardingDataSource(final ShardingRule shardingRule, final Properties props) {
    // .... 省略部分程式碼
   shardingProperties = new ShardingProperties(props);
   int executorSize = shardingProperties.getValue(ShardingPropertiesConstant.EXECUTOR_SIZE);
   executorEngine = new ExecutorEngine(executorSize);
   // .... 省略部分程式碼
}

// ExecutorEngine
public ExecutorEngine(final int executorSize) {
   executorService = MoreExecutors.listeningDecorator(new ThreadPoolExecutor(
           executorSize, executorSize, 0, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(),
           new ThreadFactoryBuilder().setDaemon(true).setNameFormat("ShardingJDBC-%d").build()));
   MoreExecutors.addDelayedShutdownHook(executorService, 60, TimeUnit.SECONDS);
}複製程式碼
  • 一個分片資料來源( ShardingDataSource ) 獨佔 一個 SQL執行引擎( ExecutorEngine )。
  • MoreExecutors#listeningDecorator() 建立 ListeningExecutorService,這樣 #submit()#invokeAll() 可以返回 ListenableFuture。
  • 預設情況下,執行緒池大小為 8。可以根據實際業務需要,設定 ShardingProperties 進行調整。
  • #setNameFormat() 併發程式設計時,一定要對執行緒名字做下定義,這樣排查問題會方便很多。
  • MoreExecutors#addDelayedShutdownHook()應用關閉時,等待所有任務全部完成再關閉。預設配置等待時間為 60 秒,建議將等待時間做成可配的。

2.2 關閉

資料來源關閉時,會呼叫 ExecutorEngine 也進行關閉。

// ShardingDataSource.java
@Override
public void close() {
   executorEngine.close();
}

// ExecutorEngine
@Override
public void close() {
   executorService.shutdownNow();
   try {
       executorService.awaitTermination(5, TimeUnit.SECONDS);
   } catch (final InterruptedException ignored) {
   }
   if (!executorService.isTerminated()) {
       throw new ShardingJdbcException("ExecutorEngine can not been terminated");
   }
}複製程式碼
  • #shutdownNow() 嘗試使用 Thread.interrupt() 打斷正在執行中的任務,未執行的任務不再執行。建議列印下哪些任務未執行,因為 SQL 未執行,可能資料未能持久化。
  • #awaitTermination() 因為 #shutdownNow() 打斷不是立即結束,需要一個過程,因此這裡等待了 5 秒。
  • 等待 5 秒後,執行緒池不一定已經關閉,此時丟擲異常給上層。建議列印下日誌,記錄出現這個情況。

2.3 執行 SQL 任務

ExecutorEngine 對外暴露 #executeStatement()#executePreparedStatement()#executeBatch()

三個方法分別提供給 StatementExecutor、PreparedStatementExecutor、BatchPreparedStatementExecutor 呼叫。而這三個方法,內部呼叫的都是 #execute() 私有方法。

// ExecutorEngine.java
/**
* 執行Statement.
* @param sqlType SQL型別
* @param statementUnits 語句物件執行單元集合
* @param executeCallback 執行回撥函式
* @param <T> 返回值型別
* @return 執行結果
*/
public <T> List<T> executeStatement(final SQLType sqlType, final Collection<StatementUnit> statementUnits, final ExecuteCallback<T> executeCallback) {
   return execute(sqlType, statementUnits, Collections.<List<Object>>emptyList(), executeCallback);
}

/**
* 執行PreparedStatement.
* @param sqlType SQL型別
* @param preparedStatementUnits 語句物件執行單元集合
* @param parameters 引數列表
* @param executeCallback 執行回撥函式
* @param <T> 返回值型別
* @return 執行結果
*/
public <T> List<T> executePreparedStatement(
       final SQLType sqlType, final Collection<PreparedStatementUnit> preparedStatementUnits, final List<Object> parameters, final ExecuteCallback<T> executeCallback) {
   return execute(sqlType, preparedStatementUnits, Collections.singletonList(parameters), executeCallback);
}

/**
* 執行Batch.
* @param sqlType SQL型別
* @param batchPreparedStatementUnits 語句物件執行單元集合
* @param parameterSets 引數列表集
* @param executeCallback 執行回撥函式
* @return 執行結果
*/
public List<int[]> executeBatch(
       final SQLType sqlType, final Collection<BatchPreparedStatementUnit> batchPreparedStatementUnits, final List<List<Object>> parameterSets, final ExecuteCallback<int[]> executeCallback) {
   return execute(sqlType, batchPreparedStatementUnits, parameterSets, executeCallback);
}複製程式碼

#execute() 執行過程大體流程如下圖:

/**
* 執行
*
* @param sqlType SQL 型別
* @param baseStatementUnits 語句物件執行單元集合
* @param parameterSets 引數列表集
* @param executeCallback 執行回撥函式
* @param <T> 返回值型別
* @return 執行結果
*/
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();
   }
   Iterator<? extends BaseStatementUnit> iterator = baseStatementUnits.iterator();
   BaseStatementUnit firstInput = iterator.next();
   // 第二個任務開始所有 SQL任務 提交執行緒池【非同步】執行任務
   ListenableFuture<List<T>> restFutures = asyncExecute(sqlType, Lists.newArrayList(iterator), parameterSets, executeCallback);
   T firstOutput;
   List<T> restOutputs;
   try {
       // 第一個任務【同步】執行任務
       firstOutput = syncExecute(sqlType, firstInput, parameterSets, executeCallback);
       // 等待第二個任務開始所有 SQL任務完成
       restOutputs = restFutures.get();
       //CHECKSTYLE:OFF
   } catch (final Exception ex) {
       //CHECKSTYLE:ON
       ExecutorExceptionHandler.handleException(ex);
       return null;
   }
   // 返回結果
   List<T> result = Lists.newLinkedList(restOutputs);
   result.add(0, firstOutput);
   return result;
}複製程式碼
  • 第一個任務【同步】呼叫 #executeInternal() 執行任務。
private <T> T syncExecute(final SQLType sqlType, final BaseStatementUnit baseStatementUnit, final List<List<Object>> parameterSets, final ExecuteCallback<T> executeCallback) throws Exception {
   // 【同步】執行任務
   return executeInternal(sqlType, baseStatementUnit, parameterSets, executeCallback, ExecutorExceptionHandler.isExceptionThrown(), ExecutorDataMap.getDataMap());
}複製程式碼
  • 第二個開始的任務提交執行緒池非同步呼叫 #executeInternal() 執行任務。
private <T> ListenableFuture<List<T>> asyncExecute(
       final SQLType sqlType, final Collection<BaseStatementUnit> baseStatementUnits, final List<List<Object>> parameterSets, final ExecuteCallback<T> executeCallback) {
   List<ListenableFuture<T>> result = new ArrayList<>(baseStatementUnits.size());
   final boolean isExceptionThrown = ExecutorExceptionHandler.isExceptionThrown();
   final Map<String, Object> dataMap = ExecutorDataMap.getDataMap();
   for (final BaseStatementUnit each : baseStatementUnits) {
       // 提交執行緒池【非同步】執行任務
       result.add(executorService.submit(new Callable<T>() {

           @Override
           public T call() throws Exception {
               return executeInternal(sqlType, each, parameterSets, executeCallback, isExceptionThrown, dataMap);
           }
       }));
   }
   // 返回 ListenableFuture
   return Futures.allAsList(result);
}複製程式碼
  • 我們注意下 Futures.allAsList(result);restOutputs = restFutures.get();。神器 Guava 簡化併發程式設計 的好處就提現出來了。ListenableFuture#get()所有任務都成功時,返回所有任務執行結果;當任何一個任務失敗時,馬上丟擲異常,無需等待其他任務執行完成。

_? Guava 真她喵神器,公眾號:【芋道原始碼】會更新 Guava 原始碼分享的一個系列喲!老司機還不趕緊上車?_

  • 為什麼會分同步執行和非同步執行呢?猜測,當SQL 執行是單表時,只要進行第一個任務的同步呼叫,效能更加優秀。等跟張亮大神請教確認原因後,我們會進行更新。
// ExecutorEngine.java
private <T> T executeInternal(final SQLType sqlType, final BaseStatementUnit baseStatementUnit, final List<List<Object>> parameterSets, 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<>();
       // 生成 Event
       if (parameterSets.isEmpty()) {
           events.add(getExecutionEvent(sqlType, baseStatementUnit, Collections.emptyList()));
       } else {
           for (List<Object> each : parameterSets) {
               events.add(getExecutionEvent(sqlType, baseStatementUnit, each));
           }
       }
       // EventBus 釋出 EventExecutionType.BEFORE_EXECUTE
       for (AbstractExecutionEvent event : events) {
           EventBusInstance.getInstance().post(event);
       }
       try {
           // 執行回撥函式
           result = executeCallback.execute(baseStatementUnit);
       } catch (final SQLException ex) {
           // EventBus 釋出 EventExecutionType.EXECUTE_FAILURE
           for (AbstractExecutionEvent each : events) {
               each.setEventExecutionType(EventExecutionType.EXECUTE_FAILURE);
               each.setException(Optional.of(ex));
               EventBusInstance.getInstance().post(each);
               ExecutorExceptionHandler.handleException(ex);
           }
           return null;
       }
       // EventBus 釋出 EventExecutionType.EXECUTE_SUCCESS
       for (AbstractExecutionEvent each : events) {
           each.setEventExecutionType(EventExecutionType.EXECUTE_SUCCESS);
           EventBusInstance.getInstance().post(each);
       }
       return result;
   }
}複製程式碼
  • result = executeCallback.execute(baseStatementUnit); 執行回撥函式。StatementExecutor,PreparedStatementExecutor,BatchPreparedStatementExecutor 通過傳遞執行回撥函式( ExecuteCallback )實現給 ExecutorEngine 實現並行執行。
public interface ExecuteCallback<T> {
    /**
     * 執行任務.
     * 
     * @param baseStatementUnit 語句物件執行單元
     * @return 處理結果
     * @throws Exception 執行期異常
     */
    T execute(BaseStatementUnit baseStatementUnit) throws Exception;
}複製程式碼
  • synchronized (baseStatementUnit.getStatement().getConnection()) 原以為 Connection 非執行緒安全,因此需要用同步,後翻查資料《資料庫連線池為什麼要建立多個連線》,Connection 是執行緒安全的。等跟張亮大神請教確認原因後,我們會進行更新。

    • 解答:MySQL、Oracle 的 Connection 實現是執行緒安全的。資料庫連線池實現的 Connection 不一定是執行緒安全,例如 Druid 的執行緒池 Connection 非執行緒安全

      FROM github.com/dangdangdot…
      druid的資料來源的stat這種filter在併發使用同一個connection連結時沒有考慮執行緒安全的問題,故造成多個執行緒修改filter中的狀態異常。
      改造這個問題時,考慮到mysql驅動在執行statement時對同一個connection是執行緒安全的。也就是說同一個資料庫連結的會話是序列執行的。故在sjdbc的executor對於多執行緒執行的情況也進行了針對資料庫連結級別的同步。故該方案不會降低sjdbc的效能。
      同時jdk1.7版本的同步採用了鎖升級技術,在碰撞較低的情況下開銷也是很小的。

  • ExecutionEvent 這裡先不解釋,在本文第四節【EventBus】分享。

  • ExecutorExceptionHandler、ExecutorDataMap 和 柔性事務 ( AbstractSoftTransaction ),放在《柔性事務》分享。

3. Executor

Executor,執行器,目前一共有三個執行器。不同的執行器對應不同的執行單元 (BaseStatementUnit)。

執行器類 執行器名 執行單元
StatementExecutor 靜態語句物件執行單元 StatementUnit
PreparedStatementExecutor 預編譯語句物件請求的執行器 PreparedStatementUnit
BatchPreparedStatementExecutor 批量預編譯語句物件請求的執行器 BatchPreparedStatementUnit
  • 執行器提供的方法不同,因此不存在公用介面或者抽象類。
  • 執行單元繼承自 BaseStatementUnit

3.1 StatementExecutor

StatementExecutor,多執行緒執行靜態語句物件請求的執行器,一共有三類方法:

  • #executeQuery()
// StatementExecutor.java
/**
* 執行SQL查詢.
* @return 結果集列表
*/
public List<ResultSet> executeQuery() {
   Context context = MetricsContext.start("ShardingStatement-executeQuery");
   List<ResultSet> result;
   try {
       result = executorEngine.executeStatement(sqlType, statementUnits, new ExecuteCallback<ResultSet>() {
           @Override
           public ResultSet execute(final BaseStatementUnit baseStatementUnit) throws Exception {
               return baseStatementUnit.getStatement().executeQuery(baseStatementUnit.getSqlExecutionUnit().getSql());
           }
       });
   } finally {
       MetricsContext.stop(context);
   }
   return result;
}複製程式碼
  • #executeUpdate() 因為有四個不同情況的#executeUpdate(),所以抽象了 Updater 介面,從而達到邏輯重用。
// StatementExecutor.java
/**
* 執行SQL更新.
* @return 更新數量
*/
public int executeUpdate() {
   return executeUpdate(new Updater() {
       @Override
       public int executeUpdate(final Statement statement, final String sql) throws SQLException {
           return statement.executeUpdate(sql);
       }
   });
}
private int executeUpdate(final Updater updater) {
   Context context = MetricsContext.start("ShardingStatement-executeUpdate");
   try {
       List<Integer> results = executorEngine.executeStatement(sqlType, statementUnits, new ExecuteCallback<Integer>() {
           @Override
           public Integer execute(final BaseStatementUnit baseStatementUnit) throws Exception {
               return updater.executeUpdate(baseStatementUnit.getStatement(), baseStatementUnit.getSqlExecutionUnit().getSql());
           }
       });
       return accumulate(results);
   } finally {
       MetricsContext.stop(context);
   }
}
/**
* 計算總的更新數量
* @param results 更新數量陣列
* @return 更新數量
*/
private int accumulate(final List<Integer> results) {
   int result = 0;
   for (Integer each : results) {
       result += null == each ? 0 : each;
   }
   return result;
}複製程式碼
  • #execute() 因為有四個不同情況的#execute(),所以抽象了 Executor 介面,從而達到邏輯重用。
/**
* 執行SQL請求.
* @return true表示執行DQL語句, false表示執行的DML語句
*/
public boolean execute() {
   return execute(new Executor() {

       @Override
       public boolean execute(final Statement statement, final String sql) throws SQLException {
           return statement.execute(sql);
       }
   });
}
private boolean execute(final Executor executor) {
   Context context = MetricsContext.start("ShardingStatement-execute");
   try {
       List<Boolean> result = executorEngine.executeStatement(sqlType, statementUnits, new ExecuteCallback<Boolean>() { 
           @Override
           public Boolean execute(final BaseStatementUnit baseStatementUnit) throws Exception {
               return executor.execute(baseStatementUnit.getStatement(), baseStatementUnit.getSqlExecutionUnit().getSql());
           }
       });
       if (null == result || result.isEmpty() || null == result.get(0)) {
           return false;
       }
       return result.get(0);
   } finally {
       MetricsContext.stop(context);
   }
}複製程式碼

3.2 PreparedStatementExecutor

PreparedStatementExecutor,多執行緒執行預編譯語句物件請求的執行器。比 StatementExecutor 多了 parameters 引數,方法邏輯上基本一致,就不重複分享啦。

3.3 BatchPreparedStatementExecutor

BatchPreparedStatementExecutor,多執行緒執行批量預編譯語句物件請求的執行器。

// BatchPreparedStatementExecutor.java
/**
* 執行批量SQL.
* 
* @return 執行結果
*/
public int[] executeBatch() {
   Context context = MetricsContext.start("ShardingPreparedStatement-executeBatch");
   try {
       return accumulate(executorEngine.executeBatch(sqlType, batchPreparedStatementUnits, parameterSets, new ExecuteCallback<int[]>() {

           @Override
           public int[] execute(final BaseStatementUnit baseStatementUnit) throws Exception {
               return baseStatementUnit.getStatement().executeBatch();
           }
       }));
   } finally {
       MetricsContext.stop(context);
   }
}
/**
* 計算每個語句的更新數量
*
* @param results 每條 SQL 更新數量
* @return 每個語句的更新數量
*/
private int[] accumulate(final List<int[]> results) {
   int[] result = new int[parameterSets.size()];
   int count = 0;
   // 每個語句按照順序,讀取到其對應的每個分片SQL影響的行數進行累加
   for (BatchPreparedStatementUnit each : batchPreparedStatementUnits) {
       for (Map.Entry<Integer, Integer> entry : each.getJdbcAndActualAddBatchCallTimesMap().entrySet()) {
           result[entry.getKey()] += null == results.get(count) ? 0 : results.get(count)[entry.getValue()];
       }
       count++;
   }
   return result;
}複製程式碼

眼尖的同學會發現,為什麼有 BatchPreparedStatementExecutor,而沒有 BatchStatementExecutor 呢?目前 Sharding-JDBC 不支援 Statement 批量操作,只能進行 PreparedStatement 的批操作。

// PreparedStatement 批量操作,不會報錯
PreparedStatement ps = conn.prepareStatement(sql)
ps.addBatch();
ps.addBatch();

// Statement 批量操作,會報錯
ps.addBatch(sql); // 報錯:at com.dangdang.ddframe.rdb.sharding.jdbc.unsupported.AbstractUnsupportedOperationStatement.addBatch複製程式碼

4. ExecutionEvent

AbstractExecutionEvent,SQL 執行事件抽象介面。

public abstract class AbstractExecutionEvent {
    /**
     * 事件編號
     */
    private final String id;
    /**
     * 資料來源
     */
    private final String dataSource;
    /**
     * SQL
     */
    private final String sql;
    /**
     * 引數
     */
    private final List<Object> parameters;
    /**
     * 事件型別
     */
    private EventExecutionType eventExecutionType;
    /**
     * 異常
     */
    private Optional<SQLException> exception;
}複製程式碼

AbstractExecutionEvent 有兩個實現子類:

  • DMLExecutionEvent:DML類SQL執行時事件
  • DQLExecutionEvent:DQL類SQL執行時事件

EventExecutionType,事件觸發型別。

  • BEFORE_EXECUTE:執行前
  • EXECUTE_SUCCESS:執行成功
  • EXECUTE_FAILURE:執行失敗

4.1 EventBus

那究竟有什麼用途呢? Sharding-JDBC 使用 Guava(沒錯,又是它)的 EventBus 實現了事件的釋出和訂閱。從上文 ExecutorEngine#executeInternal() 我們可以看到每個分片 SQL 執行的過程中會發布相應事件:

  • 執行 SQL 前:釋出型別型別為 BEFORE_EXECUTE 的事件
  • 執行 SQL 成功:釋出型別型別為 EXECUTE_SUCCESS 的事件
  • 執行 SQL 失敗:釋出型別型別為 EXECUTE_FAILURE 的事件

怎麼訂閱事件呢?非常簡單,例子如下:

EventBusInstance.getInstance().register(new Runnable() {

  @Override
  public void run() {
  }

  @Subscribe // 訂閱
  @AllowConcurrentEvents // 是否允許併發執行,即執行緒安全
  public void listen(final DMLExecutionEvent event) { // DMLExecutionEvent
      System.out.println("DMLExecutionEvent:" + event.getSql() + "\t" + event.getEventExecutionType());
  }

  @Subscribe // 訂閱
  @AllowConcurrentEvents // 是否允許併發執行,即執行緒安全
  public void listen2(final DQLExecutionEvent event) { //DQLExecutionEvent
      System.out.println("DQLExecutionEvent:" + event.getSql() + "\t" + event.getEventExecutionType());
  }

});複製程式碼
  • #register() 任何類都可以,並非一定需要使用 Runnable 類。此處例子單純因為方便
  • @Subscribe 註解在方法上,實現對事件的訂閱
  • @AllowConcurrentEvents 註解在方法上,表示執行緒安全,允許併發執行
  • 方法上的引數對應的類即是訂閱的事件。例如,#listen() 訂閱了 DMLExecutionEvent 事件
  • EventBus#post() 釋出事件,同步呼叫訂閱邏輯

Sharding-JDBC 正在收集使用公司名單:傳送門
? 你的登記,會讓更多人蔘與和使用 Sharding-JDBC。傳送門
Sharding-JDBC 也會因此,能夠覆蓋更多的業務場景。傳送門
登記吧,騷年!傳送門

4.2 BestEffortsDeliveryListener

BestEffortsDeliveryListener,最大努力送達型事務監聽器。

本文暫時暫時不分析其實現,僅僅作為另外一個訂閱者的例子。我們會在《柔性事務》進行分享。

public final class BestEffortsDeliveryListener {
    @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 對於批量執行的SQL需要解析成兩層列表
                transactionLogStorage.add(new TransactionLog(event.getId(), bedSoftTransaction.getTransactionId(), bedSoftTransaction.getTransactionType(), 
                        event.getDataSource(), event.getSql(), event.getParameters(), System.currentTimeMillis(), 0));
                return;
            case EXECUTE_SUCCESS: 
                transactionLogStorage.remove(event.getId());
                return;
            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 {
                        conn = bedSoftTransaction.getConnection().getConnection(event.getDataSource(), SQLType.UPDATE);
                        if (!isValidConnection(conn)) {
                            bedSoftTransaction.getConnection().release(conn);
                            conn = bedSoftTransaction.getConnection().getConnection(event.getDataSource(), SQLType.UPDATE);
                            isNewConnection = true;
                        }
                        preparedStatement = conn.prepareStatement(event.getSql());
                        //TODO 對於批量事件需要解析成兩層列表
                        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());
        }
    }
}複製程式碼

666. 彩蛋

本文完,但也未完。

跨分片事務問題。例如:

UPDATE t_order SET nickname = ? WHERE user_id = ?複製程式碼

A 節點 connection.commit() 時,應用突然掛了!B節點 connection.commit() 還來不及執行。
我們一起去《柔性事務》尋找答案。

道友,分享一波朋友圈可好?

相關文章