1. 什么是强制路由
一种通过在外部业务代码中指定路由配置的一种方式,在ShardingSphere中叫做Hint。如果使用Hint指定了强制分片路由,那么SQL将会无视原有的分片逻辑,直接路由至指定的数据节点操作。
2. Hint强制路由使用场景?
- 数据分片操作,如果分片键没有在SQL或数据表中(没有按表中字段分片),而是在业务逻辑代码中;
- 读写分离操作,如果强制在主库进行某些数据操作;
基于Hint的强制主库路由。可以强制路由走主库查询实时数据,避免主从同步数据延迟。
3. Hint使用步骤
3.1 编写分库或分表路由策略,实现HintShardingAlgorithm接口
// 泛型 Long 代表传入的 参数类型为 Long
public class MyHintShardingAlgorithm implements HintShardingAlgorithm<Long> {
@Override
public Collection<String> doSharding(
Collection<String> availableTargetNames,
HintShardingValue<Long> shardingValue) {
// 添加分库或分表路由逻辑
Collection<String> result = new ArrayList<>();
for (String each : availableTargetNames){ //代表:分片目标,对哪些数据库、表分片。如果是对分库路由,表示ds0,ds1;
for (Long value : shardingValue.getValues()){ // 代表:分片值; 可以HintManager设置多个分片值,所以是个集合。
if(each.endsWith(String.valueOf(value % 2))){ // 分库路由,只需要模2,指定是路由到ds0库,还是ds1库
result.add(each);
}
}
}
return result;
}
}
3.2 在配置文件指定分库或分表策略
#datasource
spring.shardingsphere.datasource.names=ds0,ds1
spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://localhost:3306/demo1
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=root
spring.shardingsphere.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.ds1.jdbc-url=jdbc:mysql://localhost:3306/demo2
spring.shardingsphere.datasource.ds1.username=root
spring.shardingsphere.datasource.ds1.password=root
#hint
spring.shardingsphere.sharding.tables.city.database-strategy.hint.algorithm-class-name=com.demo.hint.MyHintShardingAlgorithm
3.3 在业务代码中执行查询前使用HintManager指定执行策略值
@RunWith(SpringRunner.class)
@SpringBootTest(classes = RunBoot.class)
public class TestHintAlgorithm {
@Resource
private CityRepository cityRepository;
@Test
public void test1(){
HintManager hintManager = HintManager.getInstance();
// 只对库路由,则只需要hintManager.setDatabaseShardingValue操作
hintManager.setDatabaseShardingValue(1L); //强制路由到ds${xx%2}
List<City> list = cityRepository.findAll();
list.forEach(city->{
System.out.println(city.getId()+" "+city.getName()+" "+city.getProvince());
});
}
}
4. HintManager源码
HintManager主要使用ThreadLocal管理分片键信息,进行hint强制路由。在代码中向HintManager添加的配置信息只能在当前线程内有效。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.apache.shardingsphere.api.hint;
import com.google.common.base.Preconditions;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import java.util.Collection;
import java.util.Collections;
public final class HintManager implements AutoCloseable {
// 所有的addDatabaseShardingValue、setDatabaseShardingValue只是对当前线程生效
private static final ThreadLocal<HintManager> HINT_MANAGER_HOLDER = new ThreadLocal();
private final Multimap<String, Comparable<?>> databaseShardingValues = HashMultimap.create(); // 数据库的路由值信息
private final Multimap<String, Comparable<?>> tableShardingValues = HashMultimap.create(); // 表的路由值信息
private boolean databaseShardingOnly; // 是否只强制路由数据库
private boolean masterRouteOnly; // shi是否只强制路由主数据库
public static HintManager getInstance() {
Preconditions.checkState(null == HINT_MANAGER_HOLDER.get(), "Hint has previous value, please clear first.");
HintManager result = new HintManager();
HINT_MANAGER_HOLDER.set(result);
return result;
}
// set...与add... 的区别:就是是否只路由 数据库
public void setDatabaseShardingValue(Comparable<?> value) {
this.databaseShardingValues.clear();
this.databaseShardingValues.put("", value);
this.databaseShardingOnly = true; // **
}
public void addDatabaseShardingValue(String logicTable, Comparable<?> value) {
this.databaseShardingValues.put(logicTable, value);
this.databaseShardingOnly = false; // **
}
// 表的路由信息
public void addTableShardingValue(String logicTable, Comparable<?> value) {
this.tableShardingValues.put(logicTable, value);
this.databaseShardingOnly = false;
}
public static Collection<Comparable<?>> getDatabaseShardingValues() {
return getDatabaseShardingValues("");
}
public static Collection<Comparable<?>> getDatabaseShardingValues(String logicTable) {
return (Collection)(null == HINT_MANAGER_HOLDER.get() ? Collections.emptyList() : ((HintManager)HINT_MANAGER_HOLDER.get()).databaseShardingValues.get(logicTable));
}
public static Collection<Comparable<?>> getTableShardingValues(String logicTable) {
return (Collection)(null == HINT_MANAGER_HOLDER.get() ? Collections.emptyList() : ((HintManager)HINT_MANAGER_HOLDER.get()).tableShardingValues.get(logicTable));
}
public static boolean isDatabaseShardingOnly() {
return null != HINT_MANAGER_HOLDER.get() && ((HintManager)HINT_MANAGER_HOLDER.get()).databaseShardingOnly;
}
public void setMasterRouteOnly() {
this.masterRouteOnly = true;
}
public static boolean isMasterRouteOnly() {
return null != HINT_MANAGER_HOLDER.get() && ((HintManager)HINT_MANAGER_HOLDER.get()).masterRouteOnly;
}
public static void clear() {
HINT_MANAGER_HOLDER.remove();
}
public void close() {
clear();
}
private HintManager() {
}
}
注意:在读写分离结构中,为了避免主从同步数据延迟及时获取刚添加或更新的数据,可以采用强制路由走主库查询实时数据,使用hintManager.setMasterRouteOnly设置主库路由即可。