这张图是从官网上下载下来的,
说明:
- 对于sql的执行,在执行前记录日志,如果执行成功,把日志删除,如果执行失败,重试一定次数(如果未达到最大尝试次数便执行成功了,一样删除日志)。
- 异步任务不断扫描执行日志,如果重试次数未达到最大上限,尝试重新执行,如果执行成功,删除日志。
从上面两点分析可以看出,由于采用的是重试的模式,也就是说同一条语句,是有可能被多次执行的,所以官方提到了柔性事务的适用场景:
- 根据主键删除数据。
- 更新记录永久状态,如更新通知送达状态。
而且它还有一定的限制: 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包才能使用哦
事物总结图: