根据解析上下文匹配数据库和表的分片策略,并生成路由路径。 对于携带分片键的SQL,根据分片键的不同可以划分为单片路由(分片键的操作符是等号)、多片路由(分片键的操作符是IN)和范围路由(分片键的操作符是BETWEEN)。 不携带分片键的SQL则采用广播路由。
分片策略通常可以采用由数据库内置或由用户方配置。 数据库内置的方案较为简单,内置的分片策略大致可分为尾数取模、哈希、范围、标签、时间等。 由用户方配置的分片策略则更加灵活,可以根据使用方需求定制复合分片策略。 如果配合数据自动迁移来使用,可以做到无需用户关注分片策略,自动由数据库中间层分片和平衡数据即可,进而做到使分布式数据库具有的弹性伸缩的能力。 在ShardingSphere的线路规划中,弹性伸缩将于4.x开启。
路由引擎的整体结构划分如下图
分片路由
分片路由又可分为直接路由、标准路由和笛卡尔积路由这3种类型。
直接路由
满足直接路由的条件相对苛刻,它需要通过Hint(使用HintAPI直接指定路由至库表)方式分片,并且是只分库不分表的前提下,则可以避免SQL解析和之后的结果归并。 因此它的兼容性最好,可以执行包括子查询、自定义函数等复杂情况的任意SQL。直接路由还可以用于分片键不在SQL中的场景。例如,设置用于数据库分片的键为3:
hintManager.setDatabaseShardingValue(3);
假如路由算法为value % 2,当一个逻辑库t_order对应2个真实库t_order_0和t_order_1时,路由后SQL将在t_order_1上执行。下方是使用API的代码样例:
String sql = "SELECT * FROM t_order";
try (
HintManager hintManager = HintManager.getInstance();
Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
hintManager.setDatabaseShardingValue(3);
try (ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) {
//...
}
}
}
直接路由源码:
@RequiredArgsConstructor
public final class DatabaseHintRoutingEngine implements RoutingEngine {
//所有分库的数据库名称集合,由于直接路由只支持分库,不支持分表,不需要维护分表的集合
private final Collection<String> dataSourceNames;
//直接路由使用Hint路由策略进行分库
private final HintShardingStrategy databaseShardingStrategy;
@Override
public RoutingResult route() {
//获取数据库分片值
Optional<ShardingValue> shardingValue = HintManagerHolder.getDatabaseShardingValue(HintManagerHolder.DB_TABLE_NAME);
// 校验是否存在值
Preconditions.checkState(shardingValue.isPresent());
Collection<String> routingDataSources;
//得到分片结果,Hint分片策略
routingDataSources = databaseShardingStrategy.doSharding(dataSourceNames, Collections.singletonList(shardingValue.get()));
Preconditions.checkState(!routingDataSources.isEmpty(), "no database route info");
RoutingResult result = new RoutingResult();
//遍历分片结果,填充到路由结果中
for (String each : routingDataSources) {
//路由表单元集合,主要存储数据库名和路由表(包含逻辑表和实际表名称)返回这个结果便于在下一步的改写中,指定数据库和实际表的sql
result.getTableUnits().getTableUnits().add(new TableUnit(each));
}
return result;
}
}
标准路由
标准路由是ShardingSphere最为推荐使用的分片方式,它的适用范围是不包含关联查询或仅包含绑定表之间关联查询的SQL。 当分片运算符是等于号时,路由结果将落入单库(表),当分片运算符是BETWEEN或IN时,则路由结果不一定落入唯一的库(表),因此一条逻辑SQL最终可能被拆分为多条用于执行的真实SQL。 举例说明,如果按照order_id的奇数和偶数进行数据分片,一个单表查询的SQL如下:
SELECT * FROM t_order WHERE order_id IN (1, 2);
那么路由的结果应为:
SELECT * FROM t_order_0 WHERE order_id IN (1, 2);
SELECT * FROM t_order_1 WHERE order_id IN (1, 2);
绑定表的关联查询与单表查询复杂度和性能相当。举例说明,如果一个包含绑定表的关联查询的SQL如下:
SELECT * FROM t_order o JOIN t_order_item i ON o.order_id=i.order_id WHERE order_id IN (1, 2);
那么路由的结果应为:
SELECT * FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE order_id IN (1, 2);
SELECT * FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE order_id IN (1, 2);
标准路由源码:
@RequiredArgsConstructor
public final class StandardRoutingEngine implements RoutingEngine {
//分片策略接口,具体实现有外部指定
private final ShardingRule shardingRule;
//逻辑表
private final String logicTableName;
//分片条件
private final ShardingConditions shardingConditions;
@Override
public RoutingResult route() {
return generateRoutingResult(getDataNodes(shardingRule.getTableRuleByLogicTableName(logicTableName)));
}
/**
* 生成路由结果
* DataNode包含如下三个属性
* private static final String DELIMITER = ".";
*
* private final String dataSourceName;
*
* private final String tableName;
*/
private RoutingResult generateRoutingResult(final Collection<DataNode> routedDataNodes) {
RoutingResult result = new RoutingResult();
//与直接路由中相同,最终都是组装路由表单元集合
for (DataNode each : routedDataNodes) {
//创建路由表单元实例,并指定数据库名
TableUnit tableUnit = new TableUnit(each.getDataSourceName());
//赋值路由表 逻辑表和实际表
tableUnit.getRoutingTables().add(new RoutingTable(logicTableName, each.getTableName()));
result.getTableUnits().getTableUnits().add(tableUnit);
}
return result;
}
//获取DataNode节点集合,参数为表规则配置
private Collection<DataNode> getDataNodes(final TableRule tableRule) {
//判断是否是指定表的规则
if (isRoutingByHint(tableRule)) {
return routeByHint(tableRule);
}
//是否根据分片条件路由
if (isRoutingByShardingConditions(tableRule)) {
return routeByShardingConditions(tableRule);
}
//没有路由条件,则使用混合方式
return routeByMixedConditions(tableRule);
}
private boolean isRoutingByHint(final TableRule tableRule) {
//数据库分片策略是Hint分片策略 且表分片策略也是Hint分片策略
return shardingRule.getDatabaseShardingStrategy(tableRule) instanceof HintShardingStrategy && shardingRule.getTableShardingStrategy(tableRule) instanceof HintShardingStrategy;
}
//Hint路由方式
private Collection<DataNode> routeByHint(final TableRule tableRule) {
return route(tableRule, getDatabaseShardingValuesFromHint(), getTableShardingValuesFromHint());
}
private boolean isRoutingByShardingConditions(final TableRule tableRule) {
//数据库和表的分片策略都是不是Hint分片方式
return !(shardingRule.getDatabaseShardingStrategy(tableRule) instanceof HintShardingStrategy || shardingRule.getTableShardingStrategy(tableRule) instanceof HintShardingStrategy);
}
//分片条件路由方式
private Collection<DataNode> routeByShardingConditions(final TableRule tableRule) {
return shardingConditions.getShardingConditions().isEmpty() ? route(tableRule, Collections.<ShardingValue>emptyList(), Collections.<ShardingValue>emptyList())
: routeByShardingConditionsWithCondition(tableRule);
}
//路由条件不为空,根据表分片规则进行路由
private Collection<DataNode> routeByShardingConditionsWithCondition(final TableRule tableRule) {
Collection<DataNode> result = new LinkedList<>();
for (ShardingCondition each : shardingConditions.getShardingConditions()) {
Collection<DataNode> dataNodes = route(tableRule, getShardingValuesFromShardingConditions(shardingRule.getDatabaseShardingStrategy(tableRule).getShardingColumns(), each),
getShardingValuesFromShardingConditions(shardingRule.getTableShardingStrategy(tableRule).getShardingColumns(), each));
reviseShardingConditions(each, dataNodes);
result.addAll(dataNodes);
}
return result;
}
private Collection<DataNode> routeByMixedConditions(final TableRule tableRule) {
return shardingConditions.getShardingConditions().isEmpty() ? routeByMixedConditionsWithHint(tableRule) : routeByMixedConditionsWithCondition(tableRule);
}
private Collection<DataNode> routeByMixedConditionsWithCondition(final TableRule tableRule) {
Collection<DataNode> result = new LinkedList<>();
for (ShardingCondition each : shardingConditions.getShardingConditions()) {
Collection<DataNode> dataNodes = route(tableRule, getDatabaseShardingValues(tableRule, each), getTableShardingValues(tableRule, each));
reviseShardingConditions(each, dataNodes);
result.addAll(dataNodes);
}
return result;
}
private Collection<DataNode> routeByMixedConditionsWithHint(final TableRule tableRule) {
if (shardingRule.getDatabaseShardingStrategy(tableRule) instanceof HintShardingStrategy) {
return route(tableRule, getDatabaseShardingValuesFromHint(), Collections.<ShardingValue>emptyList());
}
return route(tableRule, Collections.<ShardingValue>emptyList(), getTableShardingValuesFromHint());
}
//获取数据库分片值
private List<ShardingValue> getDatabaseShardingValues(final TableRule tableRule, final ShardingCondition shardingCondition) {
ShardingStrategy dataBaseShardingStrategy = shardingRule.getDatabaseShardingStrategy(tableRule);
return isGettingShardingValuesFromHint(dataBaseShardingStrategy)
? getDatabaseShardingValuesFromHint() : getShardingValuesFromShardingConditions(dataBaseShardingStrategy.getShardingColumns(), shardingCondition);
}
private List<ShardingValue> getTableShardingValues(final TableRule tableRule, final ShardingCondition shardingCondition) {
ShardingStrategy tableShardingStrategy = shardingRule.getTableShardingStrategy(tableRule);
return isGettingShardingValuesFromHint(tableShardingStrategy)
? getTableShardingValuesFromHint() : getShardingValuesFromShardingConditions(tableShardingStrategy.getShardingColumns(), shardingCondition);
}
private boolean isGettingShardingValuesFromHint(final ShardingStrategy shardingStrategy) {
return shardingStrategy instanceof HintShardingStrategy;
}
private List<ShardingValue> getDatabaseShardingValuesFromHint() {
Optional<ShardingValue> shardingValueOptional = HintManagerHolder.getDatabaseShardingValue(logicTableName);
return shardingValueOptional.isPresent() ? Collections.singletonList(shardingValueOptional.get()) : Collections.<ShardingValue>emptyList();
}
private List<ShardingValue> getTableShardingValuesFromHint() {
Optional<ShardingValue> shardingValueOptional = HintManagerHolder.getTableShardingValue(logicTableName);
return shardingValueOptional.isPresent() ? Collections.singletonList(shardingValueOptional.get()) : Collections.<ShardingValue>emptyList();
}
private List<ShardingValue> getShardingValuesFromShardingConditions(final Collection<String> shardingColumns, final ShardingCondition shardingCondition) {
List<ShardingValue> result = new ArrayList<>(shardingColumns.size());
for (ShardingValue each : shardingCondition.getShardingValues()) {
Optional<BindingTableRule> bindingTableRule = shardingRule.findBindingTableRule(logicTableName);
if ((logicTableName.equals(each.getLogicTableName()) || bindingTableRule.isPresent() && bindingTableRule.get().hasLogicTable(logicTableName))
&& shardingColumns.contains(each.getColumnName())) {
result.add(each);
}
}
return result;
}
private Collection<DataNode> route(final TableRule tableRule, final List<ShardingValue> databaseShardingValues, final List<ShardingValue> tableShardingValues) {
Collection<String> routedDataSources = routeDataSources(tableRule, databaseShardingValues);
Collection<DataNode> result = new LinkedList<>();
for (String each : routedDataSources) {
result.addAll(routeTables(tableRule, each, tableShardingValues));
}
return result;
}
private Collection<String> routeDataSources(final TableRule tableRule, final List<ShardingValue> databaseShardingValues) {
Collection<String> availableTargetDatabases = tableRule.getActualDatasourceNames();
if (databaseShardingValues.isEmpty()) {
return availableTargetDatabases;
}
Collection<String> result = new LinkedHashSet<>(shardingRule.getDatabaseShardingStrategy(tableRule).doSharding(availableTargetDatabases, databaseShardingValues));
Preconditions.checkState(!result.isEmpty(), "no database route info");
return result;
}
private Collection<DataNode> routeTables(final TableRule tableRule, final String routedDataSource, final List<ShardingValue> tableShardingValues) {
Collection<String> availableTargetTables = tableRule.getActualTableNames(routedDataSource);
Collection<String> routedTables = new LinkedHashSet<>(tableShardingValues.isEmpty() ? availableTargetTables
: shardingRule.getTableShardingStrategy(tableRule).doSharding(availableTargetTables, tableShardingValues));
Preconditions.checkState(!routedTables.isEmpty(), "no table route info");
Collection<DataNode> result = new LinkedList<>();
for (String each : routedTables) {
result.add(new DataNode(routedDataSource, each));
}
return result;
}
private void reviseShardingConditions(final ShardingCondition each, final Collection<DataNode> dataNodes) {
if (each instanceof InsertShardingCondition) {
((InsertShardingCondition) each).getDataNodes().addAll(dataNodes);
}
}
}
笛卡尔路由
笛卡尔路由是最复杂的情况,它无法根据绑定表的关系定位分片规则,因此非绑定表之间的关联查询需要拆解为笛卡尔积组合执行。 如果上个示例中的SQL并未配置绑定表关系,那么路由的结果应为:
SELECT * FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE order_id IN (1, 2);
SELECT * FROM t_order_0 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE order_id IN (1, 2);
SELECT * FROM t_order_1 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE order_id IN (1, 2);
SELECT * FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE order_id IN (1, 2);
笛卡尔路由使用频率很低,就不做具体分析了
广播路由
对于不携带分片键的SQL,则采取广播路由的方式。根据SQL类型又可以划分为全库表路由、全库路由、全实例路由、单播路由和阻断路由这5种类型。
全库表路由
全库表路由用于处理对数据库中与其逻辑表相关的所有真实表的操作,主要包括不带分片键的DQL和DML,以及DDL等。例如:
SELECT * FROM t_order WHERE good_prority IN (1, 10);
则会遍历所有数据库中的所有表,逐一匹配逻辑表和真实表名,能够匹配得上则执行。路由后成为
SELECT * FROM t_order_0 WHERE good_prority IN (1, 10);
SELECT * FROM t_order_1 WHERE good_prority IN (1, 10);
SELECT * FROM t_order_2 WHERE good_prority IN (1, 10);
SELECT * FROM t_order_3 WHERE good_prority IN (1, 10);
全库表路由源码:
@RequiredArgsConstructor
public final class TableBroadcastRoutingEngine implements RoutingEngine {
private final ShardingRule shardingRule;
//sql语句
private final SQLStatement sqlStatement;
@Override
public RoutingResult route() {
RoutingResult result = new RoutingResult();
for (String each : getLogicTableNames()) {
result.getTableUnits().getTableUnits().addAll(getAllTableUnits(each));
}
return result;
}
//湖区哦v所有逻辑表
private Collection<String> getLogicTableNames() {
if (isOperateIndexWithoutTable()) {
//通过分片策略获取逻辑表
return Collections.singletonList(shardingRule.getLogicTableName(getIndexToken().getIndexName()));
}
return sqlStatement.getTables().getTableNames();
}
private boolean isOperateIndexWithoutTable() {
return sqlStatement instanceof DDLStatement && sqlStatement.getTables().isEmpty();
}
//获取Token的索引
private IndexToken getIndexToken() {
List<SQLToken> sqlTokens = sqlStatement.getSQLTokens();
Preconditions.checkState(1 == sqlTokens.size());
return (IndexToken) sqlTokens.get(0);
}
//根据逻辑表获取实际表,并封装为分片表单元实例
private Collection<TableUnit> getAllTableUnits(final String logicTableName) {
Collection<TableUnit> result = new LinkedList<>();
TableRule tableRule = shardingRule.getTableRuleByLogicTableName(logicTableName);
for (DataNode each : tableRule.getActualDataNodes()) {
TableUnit tableUnit = new TableUnit(each.getDataSourceName());
tableUnit.getRoutingTables().add(new RoutingTable(logicTableName, each.getTableName()));
result.add(tableUnit);
}
return result;
}
}
全库路由
全库路由用于处理对数据库的操作,包括用于库设置的SET类型的数据库管理命令,以及TCL这样的事务控制语句。 在这种情况下,会根据逻辑库的名字遍历所有符合名字匹配的真实库,并在真实库中执行该命令,例如:
SET autocommit=0;
在t_order中执行,t_order有2个真实库。则实际会在t_order_0和t_order_1上都执行这个命令。
@RequiredArgsConstructor
public final class DatabaseBroadcastRoutingEngine implements RoutingEngine {
private final ShardingRule shardingRule;
@Override
public RoutingResult route() {
RoutingResult result = new RoutingResult();
for (String each : shardingRule.getShardingDataSourceNames().getDataSourceNames()) {
result.getTableUnits().getTableUnits().add(new TableUnit(each));
}
return result;
}
}
全实例路由
全实例路由用于DCL操作,授权语句针对的是数据库的实例。无论一个实例中包含多少个Schema,每个数据库的实例只执行一次。例如:CREATE USER [email protected] identified BY '123';
这个命令将在所有的真实数据库实例中执行,以确保customer用户可以访问每一个实例。
@RequiredArgsConstructor
public final class InstanceBroadcastRoutingEngine implements RoutingEngine {
private final ShardingRule shardingRule;
private final ShardingDataSourceMetaData shardingDataSourceMetaData;
@Override
public RoutingResult route() {
RoutingResult result = new RoutingResult();
for (String each : shardingRule.getShardingDataSourceNames().getDataSourceNames()) {
if (shardingDataSourceMetaData.getAllInstanceDataSourceNames().contains(each)) {
result.getTableUnits().getTableUnits().add(new TableUnit(each));
}
}
return result;
}
}
单播路由
单播路由用于获取某一真实表信息的场景,它仅需要从任意库中的任意真实表中获取数据即可。例如:
DESCRIBE t_order;
t_order的两个真实表t_order_0,t_order_1的描述结构相同,所以这个命令在任意真实表上选择执行一次。
@RequiredArgsConstructor
public final class UnicastRoutingEngine implements RoutingEngine {
private final ShardingRule shardingRule;
private final Collection<String> logicTables;
@Override
public RoutingResult route() {
RoutingResult result = new RoutingResult();
if (shardingRule.isAllBroadcastTables(logicTables)) {
List<RoutingTable> routingTables = new ArrayList<>(logicTables.size());
for (String each : logicTables) {
routingTables.add(new RoutingTable(each, each));
}
TableUnit tableUnit = new TableUnit(shardingRule.getShardingDataSourceNames().getDataSourceNames().iterator().next());
tableUnit.getRoutingTables().addAll(routingTables);
result.getTableUnits().getTableUnits().add(tableUnit);
} else if (logicTables.isEmpty()) {
result.getTableUnits().getTableUnits().add(new TableUnit(shardingRule.getShardingDataSourceNames().getDataSourceNames().iterator().next()));
} else if (1 == logicTables.size()) {
String logicTableName = logicTables.iterator().next();
DataNode dataNode = shardingRule.findDataNode(logicTableName);
TableUnit tableUnit = new TableUnit(dataNode.getDataSourceName());
tableUnit.getRoutingTables().add(new RoutingTable(logicTableName, dataNode.getTableName()));
result.getTableUnits().getTableUnits().add(tableUnit);
} else {
String dataSourceName = null;
List<RoutingTable> routingTables = new ArrayList<>(logicTables.size());
for (String each : logicTables) {
DataNode dataNode = shardingRule.findDataNode(dataSourceName, each);
routingTables.add(new RoutingTable(each, dataNode.getTableName()));
if (null == dataSourceName) {
dataSourceName = dataNode.getDataSourceName();
}
}
TableUnit tableUnit = new TableUnit(dataSourceName);
tableUnit.getRoutingTables().addAll(routingTables);
result.getTableUnits().getTableUnits().add(tableUnit);
}
return result;
}
}
阻断路由
阻断路由用于屏蔽SQL对数据库的操作,例如:
USE order_db;
这个命令不会在真实数据库中执行,因为ShardingSphere采用的是逻辑Schema的方式,无需将切换数据库Schema的命令发送至数据库中。
public final class IgnoreRoutingEngine implements RoutingEngine {
@Override
public RoutingResult route() {
return new RoutingResult();
}
}