ShardingSphere源码解析之执行引擎(二)

上一篇开始,我们正式进入到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方法完成具体的执行工作,并添加了异常处理机制。

更多内容可以关注我的公众号:程序员向架构师转型。

发布了120 篇原创文章 · 获赞 14 · 访问量 12万+

猜你喜欢

转载自blog.csdn.net/lantian08251/article/details/104835657
今日推荐