������关注微信公众号:【芋道源码】有福利:
1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
4. 新的源码解析文章实时收到通知。每周更新一篇左右。
5. 认真的源码交流微信群。
本文主要基于 Sharding-JDBC 1.5.0 正式版
本文分享分表分库路由相关的实现。涉及内容如下:
内容顺序如编号。
**Sharding-JDBC 正在收集使用公司名单:传送门。
�� 你的登记,会让更多人参与和使用 Sharding-JDBC。传送门
Sharding-JDBC 也会因此,能够覆盖更多的业务场景。传送门
登记吧,骚年!传送门**
SQL 路由大体流程如下:
经过 SQL解析、SQL路由后,产生SQL路由结果,即 SQLRouteResult。根据路由结果,生成SQL,执行SQL。
sqlStatement
:SQL语句对象,经过SQL解析的结果对象。executionUnits
:SQL最小执行单元集合。SQL执行时,执行每个单元。generatedKeys
:**插入**SQL语句生成的主键编号集合。目前不支持批量插入而使用集合的原因,猜测是为了未来支持批量插入做准备。ShardingStrategy,分片策略。目前支持两种分片:
分片资源:在分库策略里指的是库,在分表策略里指的是表。
【1】 计算静态分片(常用)
// ShardingStrategy.java
/**
* 计算静态分片.
* @param sqlType SQL语句的类型
* @param availableTargetNames 所有的可用分片资源集合
* @param shardingValues 分片值集合
* @return 分库后指向的数据源名称集合
*/
public Collection doStaticSharding(final SQLType sqlType, final Collection availableTargetNames, final Collection> shardingValues) {
Collection result = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
if (shardingValues.isEmpty()) {
Preconditions.checkState(!isInsertMultiple(sqlType, availableTargetNames), "INSERT statement should contain sharding value."); // 插入不能有多资源对象
result.addAll(availableTargetNames);
} else {
result.addAll(doSharding(shardingValues, availableTargetNames));
}
return result;
}
/**
* 插入SQL 是否插入多个分片
* @param sqlType SQL类型
* @param availableTargetNames 所有的可用分片资源集合
* @return 是否
*/
private boolean isInsertMultiple(final SQLType sqlType, final Collection availableTargetNames) {
return SQLType.INSERT == sqlType && availableTargetNames.size() > 1;
}
【2】计算动态分片
// ShardingStrategy.java
/**
* 计算动态分片.
* @param shardingValues 分片值集合
* @return 分库后指向的分片资源集合
*/
public Collection doDynamicSharding(final Collection> shardingValues) {
Preconditions.checkState(!shardingValues.isEmpty(), "Dynamic table should contain sharding value."); // 动态分片必须有分片值
Collection availableTargetNames = Collections.emptyList();
Collection result = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
result.addAll(doSharding(shardingValues, availableTargetNames));
return result;
}
TableRule.dynamic=true
�� 闷了,看起来两者没啥区别?答案在分片算法上。我们先看 #doSharding()
方法的实现。
// ShardingStrategy.java
/**
* 计算分片
* @param shardingValues 分片值集合
* @param availableTargetNames 所有的可用分片资源集合
* @return 分库后指向的分片资源集合
*/
private Collection doSharding(final Collection> shardingValues, final Collection availableTargetNames) {
// 无片键
if (shardingAlgorithm instanceof NoneKeyShardingAlgorithm) {
return Collections.singletonList(((NoneKeyShardingAlgorithm) shardingAlgorithm).doSharding(availableTargetNames, shardingValues.iterator().next()));
}
// 单片键
if (shardingAlgorithm instanceof SingleKeyShardingAlgorithm) {
SingleKeyShardingAlgorithm> singleKeyShardingAlgorithm = (SingleKeyShardingAlgorithm>) shardingAlgorithm;
ShardingValue shardingValue = shardingValues.iterator().next();
switch (shardingValue.getType()) {
case SINGLE:
return Collections.singletonList(singleKeyShardingAlgorithm.doEqualSharding(availableTargetNames, shardingValue));
case LIST:
return singleKeyShardingAlgorithm.doInSharding(availableTargetNames, shardingValue);
case RANGE:
return singleKeyShardingAlgorithm.doBetweenSharding(availableTargetNames, shardingValue);
default:
throw new UnsupportedOperationException(shardingValue.getType().getClass().getName());
}
}
// 多片键
if (shardingAlgorithm instanceof MultipleKeysShardingAlgorithm) {
return ((MultipleKeysShardingAlgorithm) shardingAlgorithm).doSharding(availableTargetNames, shardingValues);
}
throw new UnsupportedOperationException(shardingAlgorithm.getClass().getName());
}
public interface NoneKeyShardingAlgorithm<T extends Comparable>> extends ShardingAlgorithm {
String doSharding(Collection availableTargetNames, ShardingValue shardingValue);
}
public interface SingleKeyShardingAlgorithm<T extends Comparable>> extends ShardingAlgorithm {
String doEqualSharding(Collection availableTargetNames, ShardingValue shardingValue);
Collection doInSharding(Collection availableTargetNames, ShardingValue shardingValue);
Collection doBetweenSharding(Collection availableTargetNames, ShardingValue shardingValue);
}
ShardingValueType | SQL 操作符 | 接口方法 |
---|---|---|
SINGLE | = | #doEqualSharding() |
LIST | IN | #doInSharding() |
RANGE | BETWEEN | #doBetweenSharding() |
* 多片键算法:对应 MultipleKeysShardingAlgorithm 分片算法接口。
public interface MultipleKeysShardingAlgorithm extends ShardingAlgorithm {
Collection doSharding(Collection availableTargetNames, Collection >> shardingValues);
}
分片算法类结构如下:
来看看 Sharding-JDBC 实现的无需分库的分片算法 NoneDatabaseShardingAlgorithm (NoneTableShardingAlgorithm 基本一模一样):
public final class NoneDatabaseShardingAlgorithm implements SingleKeyDatabaseShardingAlgorithm<String>, MultipleKeysDatabaseShardingAlgorithm {
@Override
public Collection doSharding(final Collection availableTargetNames, final Collection>> shardingValues) {
return availableTargetNames;
}
@Override
public String doEqualSharding(final Collection availableTargetNames, final ShardingValue shardingValue) {
return availableTargetNames.isEmpty() ? null : availableTargetNames.iterator().next();
}
@Override
public Collection doInSharding(final Collection availableTargetNames, final ShardingValue shardingValue) {
return availableTargetNames;
}
@Override
public Collection doBetweenSharding(final Collection availableTargetNames, final ShardingValue shardingValue) {
return availableTargetNames;
}
}
#doEqualSharding()
返回的是第一个分片资源。再来看测试目录下实现的余数基偶分表算法 ModuloTableShardingAlgorithm 的实现:
// com.dangdang.ddframe.rdb.integrate.fixture.ModuloTableShardingAlgorithm.java
public final class ModuloTableShardingAlgorithm implements SingleKeyTableShardingAlgorithm<Integer> {
@Override
public String doEqualSharding(final Collection tableNames, final ShardingValue shardingValue) {
for (String each : tableNames) {
if (each.endsWith(shardingValue.getValue() % 2 + "")) {
return each;
}
}
throw new UnsupportedOperationException();
}
@Override
public Collection doInSharding(final Collection tableNames, final ShardingValue shardingValue) {
Collection result = new LinkedHashSet<>(tableNames.size());
for (Integer value : shardingValue.getValues()) {
for (String tableName : tableNames) {
if (tableName.endsWith(value % 2 + "")) {
result.add(tableName);
}
}
}
return result;
}
@Override
public Collection doBetweenSharding(final Collection tableNames, final ShardingValue shardingValue) {
Collection result = new LinkedHashSet<>(tableNames.size());
Range range = shardingValue.getValueRange();
for (Integer i = range.lowerEndpoint(); i <= range.upperEndpoint(); i++) {
for (String each : tableNames) {
if (each.endsWith(i % 2 + "")) {
result.add(each);
}
}
}
return result;
}
}
�� 来看看动态计算分片需要怎么实现分片算法。
// com.dangdang.ddframe.rdb.integrate.fixture.SingleKeyDynamicModuloTableShardingAlgorithm.java
public final class SingleKeyDynamicModuloTableShardingAlgorithm implements SingleKeyTableShardingAlgorithm<Integer> {
/**
* 表前缀
*/
private final String tablePrefix;
@Override
public String doEqualSharding(final Collection availableTargetNames, final ShardingValue shardingValue) {
return tablePrefix + shardingValue.getValue() % 10;
}
@Override
public Collection doInSharding(final Collection availableTargetNames, final ShardingValue shardingValue) {
Collection result = new LinkedHashSet<>(shardingValue.getValues().size());
for (Integer value : shardingValue.getValues()) {
result.add(tablePrefix + value % 10);
}
return result;
}
@Override
public Collection doBetweenSharding(final Collection availableTargetNames, final ShardingValue shardingValue) {
Collection result = new LinkedHashSet<>(availableTargetNames.size());
Range range = shardingValue.getValueRange();
for (Integer i = range.lowerEndpoint(); i <= range.upperEndpoint(); i++) {
result.add(tablePrefix + i % 10);
}
return result;
}
}
SQLRouter,SQL 路由器接口,共有两种实现:
它们实现 #parse()
进行SQL解析,#route()
进行SQL路由。
RoutingEngine,路由引擎接口,共有四种实现:
ComplexRoutingEngine 根据路由结果会转化成 SimpleRoutingEngine 或 ComplexRoutingEngine。下文会看相应源码。
路由结果有两种:
从图中,我们已经能大概看到两者有什么区别,更具体的下文随源码一起分享。
�� SQLRouteResult 和 RoutingResult 有什么区别?
一下子看到这么多“对象”,可能有点紧张。不要紧张,我们一起在整理下。
路由器 | 路由引擎 | 路由结果 |
---|---|---|
DatabaseHintSQLRouter | DatabaseHintRoutingEngine | RoutingResult |
ParsingSQLRouter | SimpleRoutingEngine | RoutingResult |
ParsingSQLRouter | CartesianRoutingEngine | CartesianRoutingResult |
�� 逗比博主给大家解决了“对象”,是不是应该分享朋友圈。
DatabaseHintSQLRouter,基于数据库提示的路由引擎。路由器工厂 SQLRouterFactory 创建路由器时,判断到使用数据库提示( Hint ) 时,创建 DatabaseHintSQLRouter。
// DatabaseHintRoutingEngine.java
public static SQLRouter createSQLRouter(final ShardingContext shardingContext) {
return HintManagerHolder.isDatabaseShardingOnly() ? new DatabaseHintSQLRouter(shardingContext) : new ParsingSQLRouter(shardingContext);
}
先来看下 HintManagerHolder、HintManager 部分相关的代码:
// HintManagerHolder.java
public final class HintManagerHolder {
/**
* HintManager 线程变量
*/
private static final ThreadLocal HINT_MANAGER_HOLDER = new ThreadLocal<>();
/**
* 判断是否当前只分库.
*
* @return 是否当前只分库.
*/
public static boolean isDatabaseShardingOnly() {
return null != HINT_MANAGER_HOLDER.get() && HINT_MANAGER_HOLDER.get().isDatabaseShardingOnly();
}
/**
* 清理线索分片管理器的本地线程持有者.
*/
public static void clear() {
HINT_MANAGER_HOLDER.remove();
}
}
// HintManager.java
public final class HintManager implements AutoCloseable {
/**
* 库分片值集合
*/
private final Map> databaseShardingValues = new HashMap<>();
/**
* 只做库分片
* {@link DatabaseHintRoutingEngine}
*/
@Getter
private boolean databaseShardingOnly;
/**
* 获取线索分片管理器实例.
*
* @return 线索分片管理器实例
*/
public static HintManager getInstance() {
HintManager result = new HintManager();
HintManagerHolder.setHintManager(result);
return result;
}
/**
* 设置分库分片值.
*
* 分片操作符为等号.该方法适用于只分库的场景
*
* @param value 分片值
*/
public void setDatabaseShardingValue(final Comparable> value) {
databaseShardingOnly = true;
addDatabaseShardingValue(HintManagerHolder.DB_TABLE_NAME, HintManagerHolder.DB_COLUMN_NAME, value);
}
}
那么如果要使用 DatabaseHintSQLRouter,我们只需要 HintManager.getInstance().setDatabaseShardingValue(库分片值)
即可。这里有两点要注意下:
HintManager#getInstance()
,每次获取到的都是新的 HintManager,多次赋值需要小心。HintManager#close()
,使用完需要去清理,避免下个请求读到遗漏的线程变量。看看 DatabaseHintSQLRouter 的实现:
// DatabaseHintSQLRouter.java
@Override
public SQLStatement parse(final String logicSQL, final int parametersSize) {
return new SQLJudgeEngine(logicSQL).judge(); // 只解析 SQL 类型
}
@Override
// TODO insert的SQL仍然需要解析自增主键
public SQLRouteResult route(final String logicSQL, final List
#parse()
只解析了 SQL 类型,即 SELECT / UPDATE / DELETE / INSERT 。actualTables
属性也是没有效果的。TODO
应该会支持。HintManager.getInstance().setDatabaseShardingValue(库分片值)
设置的库分片值使用的是 EQUALS,因而分库策略计算出来的只有一个库分片,即 TableUnit 只有一个,SQLExecutionUnit 只有一个。看看 DatabaseHintSQLRouter 的实现:
// DatabaseHintRoutingEngine.java
@Override
public RoutingResult route() {
// 从 Hint 获得 分片键值
Optional> shardingValue = HintManagerHolder.getDatabaseShardingValue(new ShardingKey(HintManagerHolder.DB_TABLE_NAME, HintManagerHolder.DB_COLUMN_NAME));
Preconditions.checkState(shardingValue.isPresent());
log.debug("Before database sharding only db:{} sharding values: {}", dataSourceRule.getDataSourceNames(), shardingValue.get());
// 路由。表分片规则使用的是 ShardingRule 里的。因为没 SQL 解析。
Collection routingDataSources = databaseShardingStrategy.doStaticSharding(sqlType, dataSourceRule.getDataSourceNames(), Collections.>singleton(shardingValue.get()));
Preconditions.checkState(!routingDataSources.isEmpty(), "no database route info");
log.debug("After database sharding only result: {}", routingDataSources);
// 路由结果
RoutingResult result = new RoutingResult();
for (String each : routingDataSources) {
result.getTableUnits().getTableUnits().add(new TableUnit(each, "", ""));
}
return result;
}
databaseShardingStrategy.doStaticSharding()
方法计算库分片。new TableUnit(each, "", "")
的 logicTableName
,actualTableName
都是空串,相信原因你已经知道。ParsingSQLRouter,需要解析的SQL路由器。
ParsingSQLRouter 使用 SQLParsingEngine 解析SQL。对SQL解析有兴趣的同学可以看看拙作《Sharding-JDBC 源码分析 —— SQL 解析》。
// ParsingSQLRouter.java
public SQLStatement parse(final String logicSQL, final int parametersSize) {
SQLParsingEngine parsingEngine = new SQLParsingEngine(databaseType, logicSQL, shardingRule);
Context context = MetricsContext.start("Parse SQL");
SQLStatement result = parsingEngine.parse();
if (result instanceof InsertStatement) {
((InsertStatement) result).appendGenerateKeyToken(shardingRule, parametersSize);
}
MetricsContext.stop(context);
return result;
}
#appendGenerateKeyToken()
会在《SQL 改写》分享ParsingSQLRouter 在路由时,会根据表情况使用 SimpleRoutingEngine 或 CartesianRoutingEngine 进行路由。
private RoutingResult route(final List
tableNames.iterator().next()
注意下,tableNames
变量是 new TreeMap<>(String.CASE_INSENSITIVE_ORDER)
。所以 SELECT * FROM t_order o join t_order_item i ON o.order_id = i.order_id
即使 t_order_item
排在 t_order
前面,tableNames.iterator().next()
返回的是 t_order
。当 t_order
和 t_order_item
为 BindingTable关系 时,计算的是 t_order
路由分片。tableRules
配置。配置该关系 TableRule 有如下需要遵守的规则: 举个例子:
SELECT * FROM t_order o join t_order_item i ON o.order_id = i.order_id
multi_db_multi_table_01
├── t_order_0 ├── t_order_item_01
└── t_order_1 ├── t_order_item_02
├── t_order_item_03
├── t_order_item_04
multi_db_multi_table_02
├── t_order_0 ├── t_order_item_01
└── t_order_1 ├── t_order_item_02
├── t_order_item_03
├── t_order_item_04
最终执行的SQL如下:
SELECT * FROM t_order_item_01 i JOIN t_order_01 o ON o.order_id = i.order_id
SELECT * FROM t_order_item_01 i JOIN t_order_01 o ON o.order_id = i.order_id
SELECT * FROM t_order_item_02 i JOIN t_order_02 o ON o.order_id = i.order_id
SELECT * FROM t_order_item_02 i JOIN t_order_02 o ON o.order_id = i.order_id
t_order_item_03
、t_order_item_04
无法被查询到。下面我们看看 #isAllBindingTables()
如何实现多表互为BindingTable关系。
// ShardingRule.java
// 调用顺序 #isAllBindingTables()=>#filterAllBindingTables()=>#findBindingTableRule()=>#findBindingTableRule()
/**
* 判断逻辑表名称集合是否全部属于Binding表.
* @param logicTables 逻辑表名称集合
*/
public boolean isAllBindingTables(final Collection logicTables) {
Collection bindingTables = filterAllBindingTables(logicTables);
return !bindingTables.isEmpty() && bindingTables.containsAll(logicTables);
}
/**
* 过滤出所有的Binding表名称.
*/
public Collection filterAllBindingTables(final Collection logicTables) {
if (logicTables.isEmpty()) {
return Collections.emptyList();
}
Optional bindingTableRule = findBindingTableRule(logicTables);
if (!bindingTableRule.isPresent()) {
return Collections.emptyList();
}
// 交集
Collection result = new ArrayList<>(bindingTableRule.get().getAllLogicTables());
result.retainAll(logicTables);
return result;
}
/**
* 获得包含任一在逻辑表名称集合的binding表配置的逻辑表名称集合
*/
private Optional findBindingTableRule(final Collection logicTables) {
for (String each : logicTables) {
Optional result = findBindingTableRule(each);
if (result.isPresent()) {
return result;
}
}
return Optional.absent();
}
/**
* 根据逻辑表名称获取binding表配置的逻辑表名称集合.
*/
public Optional findBindingTableRule(final String logicTable) {
for (BindingTableRule each : bindingTableRules) {
if (each.hasLogicTable(logicTable)) {
return Optional.of(each);
}
}
return Optional.absent();
}
[a, b, c]
,而不能是 [a, b], [b, c]
。SimpleRoutingEngine,简单路由引擎。
// SimpleRoutingEngine.java
private Collection routeDataSources(final TableRule tableRule) {
DatabaseShardingStrategy strategy = shardingRule.getDatabaseShardingStrategy(tableRule);
List>> shardingValues = HintManagerHolder.isUseShardingHint() ? getDatabaseShardingValuesFromHint(strategy.getShardingColumns())
: getShardingValues(strategy.getShardingColumns());
Collection result = strategy.doStaticSharding(sqlStatement.getType(), tableRule.getActualDatasourceNames(), shardingValues);
Preconditions.checkState(!result.isEmpty(), "no database route info");
return result;
}
private List>> getShardingValues(final Collection shardingColumns) {
List>> result = new ArrayList<>(shardingColumns.size());
for (String each : shardingColumns) {
Optional condition = sqlStatement.getConditions().find(new Column(each, logicTableName));
if (condition.isPresent()) {
result.add(condition.get().getShardingValue(parameters));
}
}
return result;
}
#getShardingValues()
我们看到了《SQL 解析(二)之SQL解析》分享的 Condition 对象。之前我们提到过Parser 半理解SQL的目的之一是:提炼分片上下文,此处即是该目的的体现。Condition 里只放明确影响路由的条件,例如:order_id = 1
, order_id IN (1, 2)
, order_id BETWEEN (1, 3)
,不放无法计算的条件,例如:o.order_id = i.order_id
。该方法里,使用分片键从 Condition 查找 分片值。�� 是不是对 Condition 的认识更加清晰一丢丢落。// SimpleRoutingEngine.java
private Collection routeTables(final TableRule tableRule, final Collection routedDataSources) {
TableShardingStrategy strategy = shardingRule.getTableShardingStrategy(tableRule);
List> shardingValues = HintManagerHolder.isUseShardingHint() ? getTableShardingValuesFromHint(strategy.getShardingColumns())
: getShardingValues(strategy.getShardingColumns());
Collection result = tableRule.isDynamic() ? strategy.doDynamicSharding(shardingValues)
: strategy.doStaticSharding(sqlStatement.getType(), tableRule.getActualTableNames(routedDataSources), shardingValues);
Preconditions.checkState(!result.isEmpty(), "no table route info");
return result;
}
dynamic
属性来判断调用 #doDynamicSharding()
还是 #doStaticSharding()
计算分片。// SimpleRoutingEngine.java
private RoutingResult generateRoutingResult(final TableRule tableRule, final Collection routedDataSources, final Collection routedTables) {
RoutingResult result = new RoutingResult();
for (DataNode each : tableRule.getActualDataNodes(routedDataSources, routedTables)) {
result.getTableUnits().getTableUnits().add(new TableUnit(each.getDataSourceName(), logicTableName, each.getTableName()));
}
return result;
}
// TableRule.java
/**
* 根据数据源名称过滤获取真实数据单元.
* @param targetDataSources 数据源名称集合
* @param targetTables 真实表名称集合
* @return 真实数据单元
*/
public Collection getActualDataNodes(final Collection targetDataSources, final Collection targetTables) {
return dynamic ? getDynamicDataNodes(targetDataSources, targetTables) : getStaticDataNodes(targetDataSources, targetTables);
}
private Collection getDynamicDataNodes(final Collection targetDataSources, final Collection targetTables) {
Collection result = new LinkedHashSet<>(targetDataSources.size() * targetTables.size());
for (String targetDataSource : targetDataSources) {
for (String targetTable : targetTables) {
result.add(new DataNode(targetDataSource, targetTable));
}
}
return result;
}
private Collection getStaticDataNodes(final Collection targetDataSources, final Collection targetTables) {
Collection result = new LinkedHashSet<>(actualTables.size());
for (DataNode each : actualTables) {
if (targetDataSources.contains(each.getDataSourceName()) && targetTables.contains(each.getTableName())) {
result.add(each);
}
}
return result;
}
ComplexRoutingEngine,混合多库表路由引擎。
// ComplexRoutingEngine.java
@Override
public RoutingResult route() {
Collection result = new ArrayList<>(logicTables.size());
Collection bindingTableNames = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
// 计算每个逻辑表的简单路由分片
for (String each : logicTables) {
Optional tableRule = shardingRule.tryFindTableRule(each);
if (tableRule.isPresent()) {
if (!bindingTableNames.contains(each)) {
result.add(new SimpleRoutingEngine(shardingRule, parameters, tableRule.get().getLogicTable(), sqlStatement).route());
}
// 互为 BindingTable 关系的表加到 bindingTableNames 里,不重复计算分片
Optional bindingTableRule = shardingRule.findBindingTableRule(each);
if (bindingTableRule.isPresent()) {
bindingTableNames.addAll(Lists.transform(bindingTableRule.get().getTableRules(), new Function() {
@Override
public String apply(final TableRule input) {
return input.getLogicTable();
}
}));
}
}
}
log.trace("mixed tables sharding result: {}", result);
if (result.isEmpty()) {
throw new ShardingJdbcException("Cannot find table rule and default data source with logic tables: '%s'", logicTables);
}
// 防御性编程。shardingRule#isAllBindingTables() 已经过滤了这个情况。
if (1 == result.size()) {
return result.iterator().next();
}
// 交给 CartesianRoutingEngine 形成笛卡尔积结果
return new CartesianRoutingEngine(result).route();
}
result.size == 1
,属于防御性编程。CartesianRoutingEngine,笛卡尔积的库表路由。
实现逻辑上相对复杂,请保持耐心哟,�� 其实目的就是实现连连看的效果:
x
RoutingResult[1] …… x
RoutingResult[n- 1] x
RoutingResult[n]// CartesianRoutingEngine.java
@Override
public CartesianRoutingResult route() {
CartesianRoutingResult result = new CartesianRoutingResult();
for (Entry<String, Set<String>> entry : getDataSourceLogicTablesMap().entrySet()) { // Entry<数据源(库), Set<逻辑表>> entry
// 获得当前数据源(库)的 路由表单元分组
List<Set<String>> actualTableGroups = getActualTableGroups(entry.getKey(), entry.getValue()); // List>
List<Set<TableUnit>> tableUnitGroups = toTableUnitGroups(entry.getKey(), actualTableGroups);
// 笛卡尔积,并合并结果
result.merge(entry.getKey(), getCartesianTableReferences(Sets.cartesianProduct(tableUnitGroups)));
}
log.trace("cartesian tables sharding result: {}", result);
return result;
}
下面,我们一起逐步看看代码实现。
SELECT * FROM t_order o join t_order_item i ON o.order_id = i.order_id
multi_db_multi_table_01
├── t_order_0 ├── t_order_item_01
└── t_order_1 ├── t_order_item_02
multi_db_multi_table_02
├── t_order_0 ├── t_order_item_01
└── t_order_1 ├── t_order_item_02
// 第一步
// CartesianRoutingEngine.java
/**
* 获得同库对应的逻辑表集合
*/
private Map> getDataSourceLogicTablesMap() {
Collection intersectionDataSources = getIntersectionDataSources();
Map> result = new HashMap<>(routingResults.size());
// 获得同库对应的逻辑表集合
for (RoutingResult each : routingResults) {
for (Entry> entry : each.getTableUnits().getDataSourceLogicTablesMap(intersectionDataSources).entrySet()) { // 过滤掉不在数据源(库)交集的逻辑表
if (result.containsKey(entry.getKey())) {
result.get(entry.getKey()).addAll(entry.getValue());
} else {
result.put(entry.getKey(), entry.getValue());
}
}
}
return result;
}
/**
* 获得所有路由结果里的数据源(库)交集
*/
private Collection getIntersectionDataSources() {
Collection result = new HashSet<>();
for (RoutingResult each : routingResults) {
if (result.isEmpty()) {
result.addAll(each.getTableUnits().getDataSourceNames());
}
result.retainAll(each.getTableUnits().getDataSourceNames()); // 交集
}
return result;
}
// 第二步
// CartesianRoutingEngine.java
private List<Set<String>> getActualTableGroups(final String dataSource, final Set<String> logicTables) {
List<Set<String>> result = new ArrayList<>(logicTables.size());
for (RoutingResult each : routingResults) {
result.addAll(each.getTableUnits().getActualTableNameGroups(dataSource, logicTables));
}
return result;
}
private List<Set<TableUnit>> toTableUnitGroups(final String dataSource, final List<Set<String>> actualTableGroups) {
List<Set<TableUnit>> result = new ArrayList<>(actualTableGroups.size());
for (Set<String> each : actualTableGroups) {
result.add(new HashSet<>(Lists.transform(new ArrayList<>(each), new Function<String, TableUnit>() {
@Override
public TableUnit apply(final String input) {
return findTableUnit(dataSource, input);
}
})));
}
return result;
}
// CartesianRoutingEngine.java
private List getCartesianTableReferences(final Set> cartesianTableUnitGroups) {
List result = new ArrayList<>(cartesianTableUnitGroups.size());
for (List each : cartesianTableUnitGroups) {
result.add(new CartesianTableReference(each));
}
return result;
}
// CartesianRoutingResult.java
@Getter
private final List routingDataSources = new ArrayList<>();
void merge(final String dataSource, final Collection routingTableReferences) {
for (CartesianTableReference each : routingTableReferences) {
merge(dataSource, each);
}
}
private void merge(final String dataSource, final CartesianTableReference routingTableReference) {
for (CartesianDataSource each : routingDataSources) {
if (each.getDataSource().equalsIgnoreCase(dataSource)) {
each.getRoutingTableReferences().add(routingTableReference);
return;
}
}
routingDataSources.add(new CartesianDataSource(dataSource, routingTableReference));
}
Sets.cartesianProduct(tableUnitGroups)
返回如图(Guava 工具库真强大):#getCartesianTableReferences()
返回如图:
CartesianTableReference,笛卡尔积表路由组,包含多条 TableUnit,即 TableUnit[0] x
TableUnit[1] …… x
TableUnit[n]。例如图中:t_order_01 x t_order_item_02
,最终转换成 SQL 为 SELECT * FROM t_order_01 o join t_order_item_02 i ON o.order_id = i.order_id
。
#merge()
合并笛卡尔积路由结果。CartesianRoutingResult 包含多个 CartesianDataSource,因此需要将 CartesianTableReference 合并(添加)到对应的 CartesianDataSource。当然,目前在实现时已经是按照数据源(库)生成对应的 CartesianTableReference。// ParsingSQLRouter.java
@Override
public SQLRouteResult route(final String logicSQL, final List
RoutingResult routingResult = route(parameters, sqlStatement);
调用的就是上文分析的 SimpleRoutingEngine、ComplexRoutingEngine、CartesianRoutingEngine 的 #route()
方法。#processGeneratedKey()
、#processLimit()
、#rewrite()
、#generateSQL()
等会放在《SQL 改写》 分享。篇幅有些长,希望能让大家对路由有比较完整的认识。
如果内容有错误,烦请您指正,我会认真修改。
如果表述不清晰,不太理解的,欢迎加我微信(wangwenbin-server)一起探讨。
谢谢你技术这么好,还耐心看完了本文。
强制路由 HintManager 讲的相对略过,可以看如下内容进一步了解:
厚着脸皮,道友,辛苦分享朋友圈可好?!