从上一篇开始,我们正式进入到ShardingSphere执行引擎的源码分析,我们给出了与执行引擎相关的整体结构,也给出了对ShardingExecuteEngine类的介绍。我们了解了ShardingExecuteEngine本质上只是提供了一个多线程执行环境,具体的执行过程是由传入的回调函数进行完成,这是一个非常巧妙的设计。今天我们就先从这个设计开始展开讨论。
1. ShardingGroupExecuteCallback
回调ShardingGroupExecuteCallback的定义如下所示:
public interface ShardingGroupExecuteCallback<I, O> {
Collection<O> execute(Collection<I> inputs, boolean isTrunkThread, Map<String, Object> shardingExecuteDataMap) throws SQLException;
}
该接口根据传入的泛型inputs集合和shardingExecuteDataMap完成真正的执行操作。在ShardingSphere中,使用匿名方法实现ShardingGroupExecuteCallback接口的地方有很多,但显式实现这一接口的只有一个类,即SQLExecuteCallback类。
SQLExecuteCallback是一个抽象类,它的输入inputs变量的类型是StatementExecuteUnit,定义如下:
public final class StatementExecuteUnit {
private final RouteUnit routeUnit;
private final Statement statement;
private final ConnectionMode connectionMode;
}
我们在介绍改写引擎的《ShardingSphere源码解析之改写引擎(三)》已经看到了RouteUnit的定义,它包含的就是目前的数据库、所需要执行的SQL语句以及相关参数。这里的Statement也是JDBC中的既有对象,而ConnectionMode则是ShardingSphere执行引擎中非常重要的一个概念,即连接模式,它是一个枚举,定义如下:
public enum ConnectionMode {
MEMORY_STRICTLY, CONNECTION_STRICTLY
}
可以看到有两种具体的连接模式,即MEMORY_STRICTLY和CONNECTION_STRICTLY,前者代表内存限制模式,后者则代表连接限制模式。连接模式是ShardingSphere所提出的一个特有概念,背后体现的是一种设计上的平衡思想。从数据库访问资源的角度来看,一方面是对数据库连接资源的控制保护,一方面是采用更优的归并模式达到对中间件内存资源的节省,如何处理好两者之间的关系,是ShardingSphere执行引擎需求解决的问题。为此,ShardingSphere提出了连接模式的概念。简单来讲,当采用内存限制模式时,对于同一数据源,如果有10张分表,那么执行时会获取10个连接进行并行执行;而当采用连接限制模式时,执行过程中只会获取1个连接进行串行执行,示意图如下所示(来自ShardingSphere官网):
今天我们无意对连接模式做过多展开,在后面内容中我们会结合具体源码分析两种连接模式下的处理流程。关于连接模式的更多讨论,可以参考ShardingSphere官网中的内容:https://shardingsphere.apache.org/document/current/cn/features/sharding/principle/execute/。
让我们回到ShardingGroupExecuteCallback回调接口的实现类SQLExecuteCallback,这是一个抽象类,它的execute方法如下所示:
@Override
public final Collection<T> execute(final Collection<StatementExecuteUnit> statementExecuteUnits, final boolean isTrunkThread, final Map<String, Object> shardingExecuteDataMap) throws SQLException {
Collection<T> result = new LinkedList<>();
for (StatementExecuteUnit each : statementExecuteUnits) {
result.add(execute0(each, isTrunkThread, shardingExecuteDataMap));
}
return result;
}
对于每个输入的StatementExecuteUnit,上述execute方法会进一步执行execute0方法,如下所示:
private T execute0(final StatementExecuteUnit statementExecuteUnit, final boolean isTrunkThread, final Map<String, Object> shardingExecuteDataMap) throws SQLException {
//设置ExecutorExceptionHandler
ExecutorExceptionHandler.setExceptionThrown(isExceptionThrown);
//获取DataSourceMetaData,这里用到了缓存机制
DataSourceMetaData dataSourceMetaData = getDataSourceMetaData(statementExecuteUnit.getStatement().getConnection().getMetaData());
//初始化SQLExecutionHook
SQLExecutionHook sqlExecutionHook = new SPISQLExecutionHook();
try {
RouteUnit routeUnit = statementExecuteUnit.getRouteUnit();
//启动执行钩子
sqlExecutionHook.start(routeUnit.getDataSourceName(), routeUnit.getSqlUnit().getSql(), routeUnit.getSqlUnit().getParameters(), dataSourceMetaData, isTrunkThread, shardingExecuteDataMap);
//执行SQL
T result = executeSQL(routeUnit.getSqlUnit().getSql(), statementExecuteUnit.getStatement(), statementExecuteUnit.getConnectionMode());
//成功钩子
sqlExecutionHook.finishSuccess();
return result;
} catch (final SQLException ex) {
//失败钩子
sqlExecutionHook.finishFailure(ex);
//异常处理
ExecutorExceptionHandler.handleException(ex);
return null;
}
}
这段代码每一句的含义都比较明确,这里引入了一个ExecutorExceptionHandler用于异常处理。同时也引入了一个SPISQLExecutionHook对执行过程嵌入钩子。关于基于SPI机制的Hook实现我们在前面的SQL解析和路由引擎中已经看到过很多次,这里不再赘述。我们看到,真正执行SQL的过程是交给executeSQL模板方法进行完成,需要SQLExecuteCallback的各个子类实现这一模板方法。
在ShardingSphere,没有提供任何的SQLExecuteCallback实现类,可以大量采用匿名方法来完成executeSQL模板方法的实现。例如,在后面要介绍的StatementExecutor类中,就存在如下所示的一个SQLExecuteCallback匿名实现方法:
SQLExecuteCallback<QueryResult> executeCallback = new SQLExecuteCallback<QueryResult>(getDatabaseType(), isExceptionThrown) {
@Override
protected QueryResult executeSQL(final String sql, final Statement statement, final ConnectionMode connectionMode) throws SQLException {
return getQueryResult(sql, statement, connectionMode);
}
};
2. SQLExecuteTemplate
本文的另一个主题是讨论SQLExecuteTemplate,它是ShardingExecuteEngine的直接使用者。从命名上看,这是一个典型的模板工具类,定位上就像Spring中的JdbcTemplate一样。但凡这种模板工具类,其实现一般都比较简单,基本就是对底层对象的简单封装。SQLExecuteTemplate也不例外,它要做的就是对ShardingExecuteEngine中的入口方法进行封装和处理。
ShardingExecuteEngine的核心方法就只有一个,即如下所示的executeGroup方法:
public <T> List<T> executeGroup(final Collection<ShardingExecuteGroup<? extends StatementExecuteUnit>> sqlExecuteGroups, final SQLExecuteCallback<T> firstCallback, final SQLExecuteCallback<T> callback) throws SQLException {
try {
return executeEngine.groupExecute((Collection) sqlExecuteGroups, firstCallback, callback, serial);
} catch (final SQLException ex) {
ExecutorExceptionHandler.handleException(ex);
return Collections.emptyList();
}
}
可以看到,上述方法做的事情就是直接调用ShardingExecuteEngine的groupExecute方法完成具体的执行工作,并添加了异常处理机制。
更多内容可以关注我的公众号:程序员向架构师转型。