摘要: 原創出處 www.iocoder.cn/Sharding-JD… 「芋道原始碼」歡迎轉載,保留摘要,謝謝!
本文主要基於 Sharding-JDBC 1.5.0 正式版
???關注微信公眾號:【芋道原始碼】有福利:
- RocketMQ / MyCAT / Sharding-JDBC 所有原始碼分析文章列表
- RocketMQ / MyCAT / Sharding-JDBC 中文註釋原始碼 GitHub 地址
- 您對於原始碼的疑問每條留言都將得到認真回覆。甚至不知道如何讀原始碼也可以請教噢。
- 新的原始碼解析文章實時收到通知。每週更新一篇左右。
- 認真的原始碼交流微信群。
- 掘金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()
釋出事件,同步呼叫訂閱邏輯
- 推薦閱讀文章:《Guava學習筆記:EventBus》
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()
還來不及執行。
我們一起去《柔性事務》尋找答案。
道友,分享一波朋友圈可好?