分库分表中重要的一个模块就是数据切分,将数据根据必定的规则分布在多个DB中,那么这个过程当中涉及到了路由,即根据SQL中分片键经过规则(分片算法)计算出某个DB节点,这个过程称为SQL路由。web
sharding-JDBC路由入口统一在shardingStatement
和ShardingPreparedStatement
,分析shardingStatement
源码算法
private void shard(final String sql) {
ShardingRuntimeContext runtimeContext = connection.getRuntimeContext(); //路由引擎,改引擎中处理非预编译SQL,全部的非预编译SQL路由都在这处理 SimpleQueryShardingEngine shardingEngine = new SimpleQueryShardingEngine(runtimeContext.getRule(), runtimeContext.getProps(), runtimeContext.getMetaData(), runtimeContext.getDatabaseType(), runtimeContext.getParseEngine()); //根据路由引擎执行路由 sqlRouteResult = shardingEngine.shard(sql, Collections.emptyList()); } 复制代码
sqlRouteResult
是根据路由结果构建的数据结构,整个执行流程会将sqlRouteResult
做为参数传递。sql
shard方法执行路由逻辑数据结构
public SQLRouteResult shard(final String sql, final List<Object> parameters) {
//拷贝预编译参数,防止在路由过程当中对原始值修改 List<Object> clonedParameters = cloneParameters(parameters); //route SQLRouteResult result = executeRoute(sql, clonedParameters); //根据路由结果构建可执行单元,这块涉及到了SQL改写,分表须要改写SQL中的tableName ,好比原始表名acct,改写后acct_1,acct_2等 result.getRouteUnits().addAll(HintManager.isDatabaseShardingOnly() ? convert(sql, clonedParameters, result) : rewriteAndConvert(sql, clonedParameters, result)); //日志的方式输出路由结果,包含SQL和db节点信息,方便问题追踪 if (shardingProperties.getValue(ShardingPropertiesConstant.SQL_SHOW)) { boolean showSimple = shardingProperties.getValue(ShardingPropertiesConstant.SQL_SIMPLE); SQLLogger.logSQL(sql, showSimple, result.getOptimizedStatement(), result.getRouteUnits()); } return result; } 复制代码
executeRoute方法编辑器
private SQLRouteResult executeRoute(final String sql, final List<Object> clonedParameters) {
//routingHook注入的钩子方法,获取路由过程当中的信息,此处用到的是SPI扩展技术,用户可经过SPI将具体的钩子实现注入,实现本身的逻辑,好比获取路由执行时间等 routingHook.start(sql); try { //路由 SQLRouteResult result = route(sql, clonedParameters); //执行钩子中finishSuccess方法 routingHook.finishSuccess(result, metaData.getTable()); return result; // CHECKSTYLE:OFF } catch (final Exception ex) { // CHECKSTYLE:ON //执行钩子中finishFailure方法 routingHook.finishFailure(ex); throw ex; } } 复制代码
route方法ide
//抽象方法,实现类有两个 SimpleQueryShardingEngine和PreparedQueryShardingEngine,一个处理预编译路由,另一个处理非预编译SQL路由
protected abstract SQLRouteResult route(String sql, List<Object> parameters); 复制代码
咱们分析非预编译SQL路由SimpleQueryShardingEngine
url
public SimpleQueryShardingEngine(final ShardingRule shardingRule, final ShardingProperties shardingProperties, final ShardingMetaData metaData, final DatabaseType databaseType, final SQLParseEngine sqlParseEngine) { super(shardingRule, shardingProperties, metaData); //StatementRoutingEngine 路由引擎 routingEngine = new StatementRoutingEngine(shardingRule, metaData, databaseType, sqlParseEngine); } @Override protected List<Object> cloneParameters(final List<Object> parameters) { return Collections.emptyList(); } @Override protected SQLRouteResult route(final String sql, final List<Object> parameters) { //执行路由 return routingEngine.route(sql); } 复制代码
public SQLRouteResult route(final String logicSQL) {
//sql 解析 SQLStatement sqlStatement = shardingRouter.parse(logicSQL, false); //shardingRouter.route 根据SQL解析结果路由 return masterSlaveRouter.route(shardingRouter.route(logicSQL, Collections.emptyList(), sqlStatement)); } 复制代码
public SQLRouteResult route(final String logicSQL, final List<Object> parameters, final SQLStatement sqlStatement) {
OptimizedStatement optimizedStatement = ShardingOptimizeEngineFactory.newInstance(sqlStatement).optimize(shardingRule, shardingMetaData.getTable(), logicSQL, parameters, sqlStatement); //是否须要将子查询分片值与外部查询分片值进行合并,关于sharding-JDB中子查询的分析,在个人另一篇文章中已经分析,有兴趣的能够去看看 boolean needMergeShardingValues = isNeedMergeShardingValues(optimizedStatement); if (optimizedStatement instanceof ShardingConditionOptimizedStatement && needMergeShardingValues) { //对子查询的分片键进行检查,主要肯定与外部是否同一个分片键 checkSubqueryShardingValues(optimizedStatement, ((ShardingConditionOptimizedStatement) optimizedStatement).getShardingConditions()); mergeShardingConditions(((ShardingConditionOptimizedStatement) optimizedStatement).getShardingConditions()); } //路由,工厂模式RoutingEngineFactory建立路由算法,而后执行路由,返回RoutingResult RoutingResult routingResult = RoutingEngineFactory.newInstance(shardingRule, shardingMetaData.getDataSource(), optimizedStatement).route(); if (needMergeShardingValues) { //不支持跨分片子查询 Preconditions.checkState(1 == routingResult.getRoutingUnits().size(), "Must have one sharding with subquery."); } if (optimizedStatement instanceof ShardingInsertOptimizedStatement) { setGeneratedValues((ShardingInsertOptimizedStatement) optimizedStatement); } SQLRouteResult result = new SQLRouteResult(optimizedStatement); result.setRoutingResult(routingResult); return result; } 复制代码
主要是根据表配置的路由策略构建具体的路由算法实现类spa
shardingRule:
tables: t_order: actualDataNodes: ds_${0..1}.t_order #表路由策略 tableStrategy: #路由算法类型 inline: #算法参数 shardingColumn: order_id algorithmExpression: t_order_${order_id % 2} databaseStrategy: inline: shardingColumn: order_id algorithmExpression: ds_${order_id % 2} 复制代码
路由算法类型可有多种类型选择,好比complex,standard等,同理能够本身实现自定义算法,在算法参数中加入自定义路由实现类便可.日志
sharding-JDBC同时也支持多个字段路由(多维度路由)code
sharding-JDBC SQL router相对还算简单,总体流程已经介绍,与预编译的SQL路由不一样之处是,预编译须要提取?参数,有兴趣的能够翻阅源码,后续会对SQL结果集合并作分析,包括内存合并和流式合并,这块相对复杂些.