强制路由详解与实战
1 强制路由介绍
https://shardingsphere.apache.org/document/4.1.0/cn/manual/sharding-jdbc/usage/hint/
在一些应用场景中,分片条件并不存在于SQL,而存在于外部业务逻辑。因此需要提供一种通过在外部业务代码中指定路由配置的一种方式,在ShardingSphere中叫做Hint。如果使用Hint指定了强制分片路由,那么SQL将会无视原有的分片逻辑,直接路由至指定的数据节点操作。
Hint使用场景:
- 数据分片操作,如果分片键没有在SQL或数据表中,而是在业务逻辑代码中
- 读写分离操作,如果强制在主库进行某些数据操作
2 强制路由的使用
基于 Hint 进行强制路由的设计和开发过程需要遵循一定的约定,同时,ShardingSphere 也提供了专门的 HintManager 来简化强制路由的开发过程.
2.1 环境准备
-
在
shardingjdbc0和shardingjdbc1中创建 t_course表.sqlCREATE TABLE `t_course` ( `cid` bigint(20) NOT NULL, `user_id` bigint(20) DEFAULT NULL, `corder_no` bigint(20) DEFAULT NULL, `cname` varchar(50) DEFAULT NULL, `brief` varchar(50) DEFAULT NULL, `price` double DEFAULT NULL, `status` int(11) DEFAULT NULL, PRIMARY KEY (`cid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8


2.2 代码编写
java
@TableName("t_course")
@Data
@ToString
public class Course implements Serializable {
@TableId(type = IdType.ASSIGN_ID)
private Long cid;
private Long userId;
private Long corderNo;
private String cname;
private String brief;
private double price;
private int status;
}
CourseMapper
java
@Repository
public interface CourseMapper extends BaseMapper<Course> {
}
自定义MyHintShardingAlgorithm类
在该类中编写分库或分表路由策略,实现HintShardingAlgorithm接口,重写doSharding方法
java
// 泛型Long表示传入的参数是Long类型
public class MyHintShardingAlgorithm implements HintShardingAlgorithm<Long> {
public MyHintShardingAlgorithm() {
System.out.println("MyHintShardingAlgorithm 被创建了!");
}
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames,
HintShardingValue<Long> shardingValue) {
Collection<String> result = new ArrayList<>();
for (String target : availableTargetNames) {
for (Long value : shardingValue.getValues()) {
if (target.endsWith(String.valueOf(value % 2))) {
result.add(target);
}
}
}
return result;
}
@Override
public void init() {
}
@Override
public String getType() {
// CLASS_BASED 模式下不会用到此 type,但实现返回值无妨
return "MY_HINT";
}
}
参数解析:
| 参数 | 含义 |
|---|---|
| availableTargetNames | 当前所有可用的数据节点名称,例如:["db0", "db1"]和 ["t_course_0", "t_course_1"] |
| shardingValue | 通过 hintManager.addDatabaseShardingValue(...) 或 addTableShardingValue(...) 传入的值 |
| Collection<String> | 实际要路由到的目标 |
2.3 配置文件
application.properties
properties
# 启用 debug 日志
logging.level.org.apache.shardingsphere=DEBUG
# 应用名称
spring.application.name=shardingsphere-jdbc-table
# 打印 SQL
spring.shardingsphere.props.sql-show=true
# 定义多个数据源
spring.shardingsphere.datasource.names=db0,db1
# 数据源1
spring.shardingsphere.datasource.db0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.db0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.db0.jdbc-url=jdbc:mysql://192.168.116.128:3306/shardingjdbc0?characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
spring.shardingsphere.datasource.db0.username=root
spring.shardingsphere.datasource.db0.password=123456
# 数据源2
spring.shardingsphere.datasource.db1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.db1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.db1.jdbc-url=jdbc:mysql://192.168.116.129:3306/shardingjdbc1?characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
spring.shardingsphere.datasource.db1.username=root
spring.shardingsphere.datasource.db1.password=123456
# 默认数据源(建议保留)
spring.shardingsphere.rules.sharding.default-data-source-name=db0
# t_course 表实际数据节点
spring.shardingsphere.rules.sharding.tables.t_course.actual-data-nodes=db${0..1}.t_course_${0..1}
# 数据库分片策略 - Hint
spring.shardingsphere.rules.sharding.tables.t_course.database-strategy.hint.sharding-algorithm-name=myHint
# 表分片策略 - Hint
spring.shardingsphere.rules.sharding.tables.t_course.table-strategy.hint.sharding-algorithm-name=myHint
# 分片算法定义 - MY_HINT
spring.shardingsphere.rules.sharding.sharding-algorithms.myHint.type=CLASS_BASED
spring.shardingsphere.rules.sharding.sharding-algorithms.myHint.props.strategy=HINT
spring.shardingsphere.rules.sharding.sharding-algorithms.myHint.props.algorithmClassName=com.zhp.hint.MyHintShardingAlgorithm
2.4 强制路由到库到表测试
通过设置addDatabaseShardingValue 决定路由到哪个数据库,addTableShardingValue决定路由到哪张表
java
@Test
public void testHintInsert(){
HintManager hintManager = HintManager.getInstance();
hintManager.addDatabaseShardingValue("db", 1L);
hintManager.addTableShardingValue("t_course", 0L);
for (int i = 1; i < 9; i++) {
Course course = new Course();
course.setCid(Long.parseLong(String.valueOf(i)));
course.setUserId(1001L+i);
course.setCname("Java经典面试题讲解");
course.setBrief("课程涵盖目前最容易被问到的10000道Java面试题");
course.setPrice(100.0);
course.setStatus(1);
courseMapper.insert(course);
}
}

2.5 强制路由到库到表查询测试
java
//测试查询
@Test
public void testHintSelectTable() {
HintManager hintManager = HintManager.getInstance();
//强制路由到db1数据库
hintManager.addDatabaseShardingValue("db", 1L);
//强制路由到t_course_1表
hintManager.addTableShardingValue("t_course",0L);
List<Course> courses = courseMapper.selectList(null);
courses.forEach(System.out::println);
}

2.6 强制路由走主库查询测试
在读写分离结构中,为了避免主从同步数据延迟及时获取刚添加或更新的数据,可以采用强制路由走主库查询实时数据,使用hintManager.setMasterRouteOnly设置主库路由即可。
- 配置文件
sql
# 应用名称
spring.application.name=sharding-jdbc-hint01
# 定义多个数据源
spring.shardingsphere.datasource.names = m1,s1
#数据源1
spring.shardingsphere.datasource.db0.type = com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.db0.driver-class-name = com.mysql.jdbc.Driver
spring.shardingsphere.datasource.db0.url = jdbc:mysql://192.168.116.128:3306/shardingjdbc0?characterEncoding=UTF-8&useSSL=false
spring.shardingsphere.datasource.db0.username = root
spring.shardingsphere.datasource.db0.password = 123456
#数据源2
spring.shardingsphere.datasource.db1.type = com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.db1.driver-class-name = com.mysql.jdbc.Driver
spring.shardingsphere.datasource.db1.url = jdbc:mysql://192.168.116.129:3306/shardingjdbc1?characterEncoding=UTF-8&useSSL=false
spring.shardingsphere.datasource.db1.username = root
spring.shardingsphere.datasource.db1.password = 123456
#主库与从库的信息
spring.shardingsphere.sharding.master-slave-rules.ms1.master-data-source-name=m1
spring.shardingsphere.sharding.master-slave-rules.ms1.slave-data-source-names=s1
#配置数据节点
spring.shardingsphere.sharding.tables.products.actual-data-nodes = ms1.products
# 打印SQl
spring.shardingsphere.props.sql-show=true
- 测试
java
//强制路由走主库
@Test
public void testHintReadTableToMaster() {
HintManager hintManager = HintManager.getInstance();
hintManager.setMasterRouteOnly();
List<Products> products = productsMapper.selectList(null);
products.forEach(System.out::println);
}
2.7 SQL执行流程剖析
ShardingSphere 3个产品的数据分片功能主要流程是完全一致的,如下图所示。

-
SQL解析
SQL解析分为词法解析和语法解析。 先通过词法解析器将SQL拆分为一个个不可再分的单词。再使用语法解析器对SQL进行理解,并最终提炼出解析上下文。
Sharding-JDBC采用不同的解析器对SQL进行解析,解析器类型如下:
- MySQL解析器
- Oracle解析器
- SQLServer解析器
- PostgreSQL解析器
- 默认SQL解析器
-
查询优化
负责合并和优化分片条件,如OR等。
-
SQL路由
根据解析上下文匹配用户配置的分片策略,并生成路由路径。目前支持分片路由和广播路由。
-
SQL改写
将SQL改写为在真实数据库中可以正确执行的语句。SQL改写分为正确性改写和优化改写。
-
SQL执行
通过多线程执行器异步执行SQL。
-
结果归并
将多个执行结果集归并以便于通过统一的JDBC接口输出。结果归并包括流式归并、内存归并和使用装饰者模式的追加归并这几种方式。