preface
Shardingsphere-jdbc sharding parsing engine above introduces the parsing engine in the sharding process, focusing on the core component of the parsing engine ANTLR; This paper continues to introduce the routing engine in the sharding process. The routing engine can be said to be the core module of the whole sharding process. The user-defined sharding algorithm is executed in the routing engine.
Open the log
To view routing logs clearly and in detail, enable the SQL_SHOW function:
-
Import the log4J-related JAR and the log4j.xml configuration file
-
Set the SQL_SHOW attribute to enable:
Properties prop = new Properties(); prop.put(ConfigurationPropertyKey.SQL_SHOW.getKey(), true); Copy the code
Route decorator
The routing engine has a RouteDecorator wrapped around it. The reason for this is that in addition to our algorithm-based routing engine, Shardingsphere-JDBC also provides read-write separation, which is actually routing to some extent. And the two routing modes can be superimposed; So a layer of abstraction is provided, and the implementation classes include:
- MasterSlaveRouteDecorator: reading and writing separate routing decorator;
- ShardingRouteDecorator: a ShardingRouteDecorator containing various routing engines;
Decorators can be stacked, so the priority function OrderAware is provided, and each decorator has a corresponding rule, roughly as follows:
A decorator – RouteDecorator | Configuration – a Configuration | Rules – BaseRule | Priority Order |
---|---|---|---|
MasterSlaveRouteDecorator | MasterSlaveRuleConfiguration | MasterSlaveRule | 10 |
ShardingRouteDecorator | ShardingRuleConfiguration | ShardingRule | 0 |
According to the priority first perform ShardingRouteDecorator, can know the routing result to perform MasterSlaveRouteDecorator; Part of the startup class code is shown in DataNodeRouter as follows:
private final Map<BaseRule, RouteDecorator> decorators = new LinkedHashMap<>();
private RouteContext executeRoute(final String sql, final List<Object> parameters, final boolean useCache) {
RouteContext result = createRouteContext(sql, parameters, useCache);
for (Entry<BaseRule, RouteDecorator> entry : decorators.entrySet()) {
result = entry.getValue().decorate(result, metaData, entry.getKey(), properties);
}
return result;
}
Copy the code
Decorators decide whether to start decorators based on the user’s configuration, as shown in the table above. Here are the two decorators in order of priority;
Fragment route decorator
ShardingRule; ShardingRule; ShardingRule; ShardingRule; ShardingRule; With these two core parameters, fragment routing can be roughly divided into the following steps:
- Get sharding conditions
ShardingConditions
- Get the specific sharding engine
ShardingRouteEngine
- Perform route processing to obtain the route result
Before introducing each step in detail, first introduce the following core parameters RouteContext and ShardingRule;
Core parameter
Focus on the two core parameters RouteContext and ShardingRule;
RouteContext
Routing context parameters, including the following parameters:
public final class RouteContext {
private final SQLStatementContext sqlStatementContext;
private final List<Object> parameters;
private final RouteResult routeResult;
}
Copy the code
- SqlStatementContext: obtained by the parsing engine
SQLStatement
; - The parameters:
PreparedStatement
To perform an INSERT operationsetXxx
Instead of?
; - RouteResult: stores the routeResult after the route.
ShardingRule
Fragmentation rules, the main parameters as follows, this actually and ShardingRuleConfiguration the same, just to do a packaging;
public class ShardingRule implements BaseRule {
private final ShardingRuleConfiguration ruleConfiguration;
private final ShardingDataSourceNames shardingDataSourceNames;
private final Collection<TableRule> tableRules;
private final Collection<BindingTableRule> bindingTableRules;
private final Collection<String> broadcastTables;
private final ShardingStrategy defaultDatabaseShardingStrategy;
private final ShardingStrategy defaultTableShardingStrategy;
private final ShardingKeyGenerator defaultShardingKeyGenerator;
private final Collection<MasterSlaveRule> masterSlaveRules;
private final EncryptRule encryptRule;
Copy the code
- RuleConfiguration: Configuration of a routing rule
ShardingRule
The original file of; - ShardingDataSourceNames: data source name of the sharding.
- TableRules: table routing rules that correspond to user-configured rules
TableRuleConfiguration
; - BindingTableRules: binding table configuration, sharding rules of the same primary table and child tables;
- BroadcastTables: broadcastTables that exist in all fragmented data sources
- DefaultDatabaseShardingStrategy: the default library shard strategy;
- DefaultTableShardingStrategy: the default table shard strategy;
- DefaultShardingKeyGenerator: the primary key generation strategy;
- MasterSlaveRules: master and slave rule configuration, used to achieve read and write separation, can configure a master table multiple slave tables;
- EncryptRule: Encryption rule configuration that encrypts sensitive data.
Get sharding conditions
Before obtaining the specific routing engine and performing routing operations, we need to obtain sharding conditions. Common sharding conditions are mainly after Insert statements and Where statements. Part of the source code to obtain sharding conditions is as follows:
private ShardingConditions getShardingConditions(final List<Object> parameters,
final SQLStatementContext sqlStatementContext, final SchemaMetaData schemaMetaData, final ShardingRule shardingRule) {
if (sqlStatementContext.getSqlStatement() instanceof DMLStatement) {
if (sqlStatementContext instanceof InsertStatementContext) {
return new ShardingConditions(newInsertClauseShardingConditionEngine(shardingRule).createShardingConditions((InsertStatementContext) sqlStatementContext, parameters)); }return new ShardingConditions(new WhereClauseShardingConditionEngine(shardingRule, schemaMetaData).createShardingConditions(sqlStatementContext, parameters));
}
return new ShardingConditions(Collections.emptyList());
}
Copy the code
The SQL statement type is DMLStatement, which is the most common SQL statement type. Next check whether it is an Insert statement and use a different fragment conditional generation engine:
- InsertClauseShardingConditionEngine: handle Insert statement shard conditions;
- WhereClauseShardingConditionEngine: processing the Insert statement Where clause shard conditions;
InsertClauseShardingConditionEngine
Insert statements contain sharding conditions in two main places:
-
TableRules of ShardingRule configure sharding keys for sharding columns, as shown in the following table:
insert into t_order (user_id,order_id) values(? ,?)Copy the code
This step checks whether user_id and order_id are configured with the component slice key in a ShardingRule.
-
If the primary key generation policy is configured, it is also necessary to check whether tableRules of ShardingRule is configured with related fields as sharding keys.
tableRuleConfig.setKeyGeneratorConfig(new KeyGeneratorConfiguration("SNOWFLAKE"."id")); Copy the code
The generated result is ShardingConditions, which contains multiple ShardingConditions:
public final class ShardingConditions {
private final List<ShardingCondition> conditions;
}
public class ShardingCondition {
private final List<RouteValue> routeValues = new LinkedList<>();
}
Copy the code
The RouteValue implementation here includes ListRouteValue, RangeRouteValue, AlwaysFalseRouteValue:
- ListRouteValue: It can be understood that the shard key corresponds to a specific value, which can be a single or multiple value.
- RangeRouteValue: The shard key corresponds to an interval value;
- AlwaysFalseRouteValue: indicates the route value that always fails.
WhereClauseShardingConditionEngine
A common Select statement can contain multiple conditions after the Where statement. Each condition also needs to check whether tableRules of ShardingRule are configured with related fields as sharding keys. Slightly different, Where conditions need to be merged, such as:
String sql = "select user_id,order_id from t_order where order_id = 101 and order_id = 101";
String sql = "select user_id,order_id from t_order where order_id = 101 and order_id in(101)";
Copy the code
If order_id = 101, merge order_id = 101, merge order_id = 101
String sql = "select user_id,order_id from t_order where order_id = 101 and order_id = 102";
Copy the code
Returns an AlwaysFalseRouteValue indicating that this condition cannot be true;
Getting the Routing Engine
Shardingsphere-jdbc provides 10 routing engines based on the SQLStatement.
The flow chart
Flow chart is roughly as shown above, specific see ShardingRouteEngineFactory can; Each routing engine is described in detail below;
The routing engine
ShardingDatabaseBroadcastRoutingEngine
Full-library routing engine: Used to handle operations on the database, including database management commands of type SET for library Settings and transaction control statements such as TCL. In this case, the command is executed in the real library through all the real libraries that match the name of the logical library.
1. Belong to DALStatement
At the database access layer, common commands include: set, reset, show databases;
show databases;
Copy the code
The above SQL will be the whole library routing, routing SQL is as follows:
Actual SQL: ds0 ::: show databases;
Actual SQL: ds1 ::: show databases;
Copy the code
2. Logical tables belong to broadcast tables
insert into t_config (k,v) values(? ,?)Copy the code
T_config configudes a broadcast table and inserts data into all libraries. Of course, you need to configure the broadcast table:
Collection<String> broadcastTables = new LinkedList<>();
broadcastTables.add("t_config");
shardingRuleConfig.setBroadcastTables(broadcastTables);
Copy the code
The route log is as follows:
Actual SQL: ds0 ::: insert into t_config (k,v) values (?, ?) ::: [aa1, 1111]
Actual SQL: ds1 ::: insert into t_config (k,v) values (?, ?) ::: [aa1, 1111]
Copy the code
3. Belong to TCLStatement
Transaction control language, including setting savepoints, rollback, etc.
SET autocommit=0
Copy the code
The route log is as follows:
Actual SQL: ds0 ::: SET autocommit=0;
Actual SQL: ds1 ::: SET autocommit=0;
Copy the code
ShardingTableBroadcastRoutingEngine
Full-library table routing is used to process the operation of all real tables related to its logical tables in the database, including DQL and DML without sharding keys, as well as DDL, etc.
1. Belong to DDLStatement
Database definition language, including creating, modifying, deleting tables, etc.
ALTER TABLE t_order MODIFY COLUMN user_id BIGINT(50) NOT NULL;
Copy the code
The log is as follows:
Actual SQL: ds0 ::: ALTER TABLE t_order0 MODIFY COLUMN user_id BIGINT(50) NOT NULL;
Actual SQL: ds0 ::: ALTER TABLE t_order1 MODIFY COLUMN user_id BIGINT(50) NOT NULL;
Actual SQL: ds1 ::: ALTER TABLE t_order0 MODIFY COLUMN user_id BIGINT(50) NOT NULL;
Actual SQL: ds1 ::: ALTER TABLE t_order1 MODIFY COLUMN user_id BIGINT(50) NOT NULL;
Copy the code
2. Belong to DCLStatement
Database control language, including authorization, role control, etc. Grant, deny, etc
grant select on ds.t_order to root@The '%'
Copy the code
Grant select privileges to user root. Select * from user root.
Actual SQL: ds0 ::: grant select on t_order0 to root@The '%'
Actual SQL: ds0 ::: grant select on t_order1 to root@The '%'
Actual SQL: ds1 ::: grant select on t_order0 to root@The '%'
Actual SQL: ds1 ::: grant select on t_order1 to root@The '%'
Copy the code
ShardingIgnoreRoutingEngine
Blocking routes shield SQL operations on the database.
1.DALStatement
The use command in DALStatement is used to block routes.
use ds0
Copy the code
ShardingDefaultDatabaseRoutingEngine
Default database route, you need to configure the default data source name, including the following situations:
1. Belong to DALStatement
show create table t_order1
Copy the code
Note: TableRuleConfiguration is a real table. You need to configure the default data source name.
shardingRuleConfig.setDefaultDataSourceName("ds0");
Copy the code
The log is as follows:
Actual SQL: ds0 ::: show create table t_order1
Copy the code
2. Logical tables belong to default data sources
select user_id,order_id from t_order0 where user_id = 102
Copy the code
Note: TableRuleConfiguration is a real table and cannot be configured as a broadcast table.
Actual SQL: ds0 ::: select user_id,order_id from t_order0 where user_id = 102
Copy the code
3. Belong to DMLStatement
select 2+2
Copy the code
This DMLStatement does not have a table name and needs to specify a default data source name.
Actual SQL: ds0 ::: select 2+2
Copy the code
ShardingUnicastRoutingEngine
Unicast routing is used to obtain the information of a real table. It only needs to obtain data from any real table in any library.
1. Belong to DALStatement
desc t_order
Copy the code
The log is as follows:
Actual SQL: ds1 ::: desc t_order0
Copy the code
2. Logical tables belong to broadcast tables
Broadcast table A table that exists in a sharded data source, usually a dictionary table:
select * from t_config
Copy the code
To configure the broadcast table:
Collection<String> broadcastTables = new LinkedList<>();
broadcastTables.add("t_config");
shardingRuleConfig.setBroadcastTables(broadcastTables);
Copy the code
The log is as follows:
Actual SQL: ds0 ::: select * from t_config
Copy the code
3. Belong to DMLStatement
Belong to DMLStatement for AlwaysFalseShardingCondition shard conditions at the same time, or does not specify the name of the table, or no configuration table rules;
select user_id,order_id from t_order where order_id = 101 and order_id = 102
Copy the code
Behind the where the specified condition can lead to fragmentation of AlwaysFalseShardingCondition
Actual SQL: ds1 ::: select user_id,order_id from t_order0 where order_id = 101 and order_id = 102
Copy the code
ShardingDataSourceGroupBroadcastRoutingEngine
Data source group broadcast, randomly select a data source from the data source group;
1. Belong to DALStatement
SHOW STATUS
Copy the code
DALStatement subclasses include use, set, reset, and show databases. Almost all use this engine;
Actual SQL: ds1 ::: SHOW STATUS
Copy the code
ShardingMasterInstanceBroadcastRoutingEngine
Full-instance routing is used for DCL operations, and authorization statements are used for database instances. No matter how many schemas are contained in an instance, each database instance is executed only once;
1. Belong to DCLStatement
CREATE USER customer@ 127. 0. 01. identified BY '123'
Copy the code
Note: the primary instance of Mysql will be checked between instances, so it cannot have the same hostname and the same port. The local test is performed on the same Mysql library, and the hostname configuration is inconsistent, such as localhost and 127.0.0.1.
ShardingStandardRoutingEngine
Standard routing is the most common sharding method. After filtering by the preceding routing engines, the remaining SQLStatement will be used by the remaining two engines.
1. Query a single table
select user_id,order_id from t_order where order_id = 101
Copy the code
The above uses the general configuration, and the two data sources are DS0 and DS1 respectively. User_id as the database key, order_id as the table key;
Actual SQL: ds0 ::: select user_id,order_id from t_order1 where order_id = 101
Actual SQL: ds1 ::: select user_id,order_id from t_order1 where order_id = 101
Copy the code
101 The physical table T_order1 was located through the fragmentation algorithm, but the database could not be located, so the two libraries were respectively executed.
2. Associated query
select a.user_id,a.order_id from t_order a left join t_order_item b ON a.order_id=b.order_id where a.order_id = 101
Copy the code
In associative query, the standard route is used only when the binding relationship is configured.
Collection<String> bindingTables = new LinkedList<>();
bindingTables.add("t_order,t_order_item");
shardingRuleConfig.setBindingTableGroups(bindingTables);
Copy the code
Two tables are configured as binding tables. Associated query has the same complexity and performance as single-table query, and Cartesian routing is not implemented.
Actual SQL: ds0 ::: select a.user_id,a.order_id from t_order1 a left join t_order1 b ON a.order_id=b.order_id where a.order_id = 101
Actual SQL: ds1 ::: select a.user_id,a.order_id from t_order1 a left join t_order1 b ON a.order_id=b.order_id where a.order_id = 101
Copy the code
ShardingComplexRoutingEngine
Cartesian routing is the most complex case, because it cannot locate sharding rules according to the relationship between bound tables, so the associated query between unbound tables needs to be disassembled into Cartesian product combination.
1. Associated query
If no binding relationship is configured in the above SQL, cartesian routing will be performed. The routing log is as follows:
Actual SQL: ds0 ::: select a.user_id,a.order_id from t_order1 a left join t_order_item0 b ON a.order_id=b.order_id where a.order_id = 101
Actual SQL: ds0 ::: select a.user_id,a.order_id from t_order1 a left join t_order_item1 b ON a.order_id=b.order_id where a.order_id = 101
Actual SQL: ds1 ::: select a.user_id,a.order_id from t_order1 a left join t_order_item0 b ON a.order_id=b.order_id where a.order_id = 101
Actual SQL: ds1 ::: select a.user_id,a.order_id from t_order1 a left join t_order_item1 b ON a.order_id=b.order_id where a.order_id = 101
Copy the code
Perform routing processing
After the preceding process, the routing engine corresponding to the SQLStatement has been obtained. You only need to run the routing engine to obtain the routing result.
public interface ShardingRouteEngine {
RouteResult route(ShardingRule shardingRule);
}
Copy the code
The ShardingRule rule is passed in and the result RouteResult is returned. The following uses standard routes as an example to analyze how route processing is performed. GetDataNodes ShardingStandardRoutingEngine core method is shown below:
private Collection<DataNode> getDataNodes(final ShardingRule shardingRule, final TableRule tableRule) {
if (isRoutingByHint(shardingRule, tableRule)) {
return routeByHint(shardingRule, tableRule);
}
if (isRoutingByShardingConditions(shardingRule, tableRule)) {
return routeByShardingConditions(shardingRule, tableRule);
}
return routeByMixedConditions(shardingRule, tableRule);
}
Copy the code
There are three routing modes: Hint routing, fragment conditional routing, and mixed conditional routing. The following are introduced separately;
Hint routing
Determine whether to use forced routing:
private boolean isRoutingByHint(final ShardingRule shardingRule, final TableRule tableRule) {
return shardingRule.getDatabaseShardingStrategy(tableRule) instanceof HintShardingStrategy && shardingRule.getTableShardingStrategy(tableRule) instanceof HintShardingStrategy;
}
Copy the code
Need to library and table are HintShardingStrategy routing strategy, the only need to configure TableRuleConfiguration configuration database and table strategies are respectively for HintShardingStrategyConfiguration can;
private Collection<DataNode> route0(final ShardingRule shardingRule, final TableRule tableRule, final List<RouteValue> databaseShardingValues, final List<RouteValue> tableShardingValues) {
Collection<String> routedDataSources = routeDataSources(shardingRule, tableRule, databaseShardingValues);
Collection<DataNode> result = new LinkedList<>();
for (String each : routedDataSources) {
result.addAll(routeTables(shardingRule, tableRule, each, tableShardingValues));
}
return result;
}
Copy the code
The next step is to execute the routing library and routing table separately. Both the routing library and the routing table require two core parameters: the currently available target library or table, and the current shard value.
- Currently available target libraries or tables: this is the initialized library and table, such as target libraries ds0, ds1, target tables t_order0, T_order1;
- Current shard value: The current hint is passed
HintManager
The configured fragment value;
In fact, the other two routing methods are similar, but the way to obtain the fragment value is different. With these two values, ShardingAlgorithm defined by ourselves will be called. In this way, the library table after routing will be returned and the results will be saved to DataNode:
public final class DataNode {
private final String dataSourceName;
private final String tableName;
}
Copy the code
A real library corresponds to a real table; Finally, encapsulate the DataNode to the RouteResult.
Fragment conditional routing
Also, first determine whether to use fragment conditional routing:
private boolean isRoutingByShardingConditions(final ShardingRule shardingRule, final TableRule tableRule) {
return! (shardingRule.getDatabaseShardingStrategy(tableRule)instanceof HintShardingStrategy || shardingRule.getTableShardingStrategy(tableRule) instanceof HintShardingStrategy);
}
Copy the code
Sharding conditional routing is implemented only when the library and table routing strategies are not HintShardingStrategy.
private Collection<DataNode> routeByShardingConditions(final ShardingRule shardingRule, final TableRule tableRule) {
return shardingConditions.getConditions().isEmpty()
? route0(shardingRule, tableRule, Collections.emptyList(), Collections.emptyList()) : routeByShardingConditionsWithCondition(shardingRule, tableRule);
}
Copy the code
Then it will judge whether there are ShardingConditions. The above chapter will specifically introduce ShardingConditions. If there is no ShardingConditions, then the full library table will be routed. If there are ShardingConditions, then the library table sharding value will be retrieved from ShardingConditions.
Hybrid conditional routing
The mixed mode is that neither the library nor the table routing policies are all HintShardingStrategy. Either the table uses forced routing or the library uses forced routing.
private Collection<DataNode> routeByMixedConditions(final ShardingRule shardingRule, final TableRule tableRule) {
return shardingConditions.getConditions().isEmpty() ? routeByMixedConditionsWithHint(shardingRule, tableRule) : routeByMixedConditionsWithCondition(shardingRule, tableRule);
}
Copy the code
It will judge whether there is ShardingConditions. If there is no indication of library or table routing, one uses HintShardingStrategy and the other does not. Otherwise, Hint and Condition are mixed; Hint (); Hint (); Hint ();
private List<RouteValue> getDatabaseShardingValues(final ShardingRule shardingRule, final TableRule tableRule, final ShardingCondition shardingCondition) {
ShardingStrategy dataBaseShardingStrategy = shardingRule.getDatabaseShardingStrategy(tableRule);
return isGettingShardingValuesFromHint(dataBaseShardingStrategy)
? getDatabaseShardingValuesFromHint() : getShardingValuesFromShardingConditions(shardingRule, dataBaseShardingStrategy.getShardingColumns(), shardingCondition);
}
Copy the code
If the Hint fragment value is available, use the Hint value, otherwise get it from Condition;
Read/write split route decorator
Through fragmentation routing decorators on the processing, according to the priority, if the configuration the separation will perform, speaking, reading and writing, speaking, reading and writing are separated decorator MasterSlaveRouteDecorator; The general process is as follows:
Read/write Separation Configuration
List<String> slaveDataSourceNames0 = new ArrayList<String>();
slaveDataSourceNames0.add("ds01");
MasterSlaveRuleConfiguration masterSlaveRuleConfiguration0 = new MasterSlaveRuleConfiguration("ds0"."ds0",
slaveDataSourceNames0);
shardingRuleConfig.getMasterSlaveRuleConfigs().add(masterSlaveRuleConfiguration0);
List<String> slaveDataSourceNames1 = new ArrayList<String>();
slaveDataSourceNames1.add("ds11");
MasterSlaveRuleConfiguration masterSlaveRuleConfiguration1 = new MasterSlaveRuleConfiguration("ds1"."ds1",
slaveDataSourceNames1);
shardingRuleConfig.getMasterSlaveRuleConfigs().add(masterSlaveRuleConfiguration1);
Copy the code
A read/write separation policy must be configured for ds0 standby library ds01 and DS1 standby library DS11.
Library name matching
The library name of RouteUnit generated by fragmented routes matches the name configured in MasterSlaveRule.
Read and write routing
public String route(final SQLStatement sqlStatement) {
if (isMasterRoute(sqlStatement)) {
MasterVisitedManager.setMasterVisited();
return masterSlaveRule.getMasterDataSourceName();
}
return masterSlaveRule.getLoadBalanceAlgorithm().getDataSource(
masterSlaveRule.getName(), masterSlaveRule.getMasterDataSourceName(), new ArrayList<>(masterSlaveRule.getSlaveDataSourceNames()));
}
Copy the code
SQLStatement (); SQLStatement ();
private boolean isMasterRoute(final SQLStatement sqlStatement) {
returncontainsLockSegment(sqlStatement) || ! (sqlStatementinstanceof SelectStatement) || MasterVisitedManager.isMasterVisited() || HintManager.isMasterRouteOnly();
}
private boolean containsLockSegment(final SQLStatement sqlStatement) {
return sqlStatement instanceof SelectStatement && ((SelectStatement) sqlStatement).getLock().isPresent();
}
Copy the code
SelectStatement
Contains locks- non
SelectStatement
- Configure the
MasterVisitedManager
, internally managed using ThreadLocal - Configure the
HintManager
, internally managed using ThreadLocal
In the preceding cases, the master table is used. In other cases, the standby database is used. If multiple standby databases exist, load balancing is performed.
Alternative routing library
After read/write split route processing, the library name that needs to be replaced is obtained.
conclusion
This article mainly introduces shardingSphere-JDBC sharding routing engine. The key and difficulty is that different types of SQLStatement use different routing engines. There are many routing engines. The routing engine will fetch the routing result and now we’re going to rewrite the SQL, rewrite it into a real library table, so that the database can execute it.
reference
Shardingsphere.apache.org/document/cu…
Thank you for attention
You can pay attention to the wechat public account “Roll back the code”, read the first time, the article continues to update; Focus on Java source code, architecture, algorithms, and interviews.