ShardingSphere使用ThreadLocal管理分片键值进行Hint强制路由。可以通过编程的方式向HintManager中添加分片值,该分片值仅在当前线程内生效。 Hint方式主要使用场景:
分片字段不存在SQL中、数据库表结构中,而存在于外部业务逻辑。
强制在主库进行某些数据操作。
简单来说,强制路由可以让我们显式指定某个库或某个表来执行我们的数据库操作
在Sharding-JDBC中,强制路由是依靠HintManager实现的, HintManager 是一种上下文,可以用于存储一些特殊的路由信息,以便在路由规则无法满足需求时进行强制路由和强制分库分表
public final class HintManager implements AutoCloseable {
//基于ThreadLocal存储HintManager实例
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;
…
}
获取HintManager
HintManager hintManager = HintManager.getInstance();
要使用HintManager进行强制路由分片,那么需要使用Hint分片算法,Hint分片算法需要我们实现org.apache.shardingsphere.api.sharding.hint.HintShardingAlgorithm接口。ShardingSphere在进行Routing时,如果发现LogicTable的TableRule采用了 Hint的分片算法,将会从HintManager中获取分片值进行路由操作。
/**
* 自定义强制路由类
*/
public class MyHintShardingAlgorithm implements HintShardingAlgorithm<Long> {
/**
* 定义强制路由策略
* collection: 分片目标:表示当前可用的数据源或数据表名称,对哪些数据库 表进行分片,比如做分库路由
* hintShardingValue: 分片值, 分片键的值
*/
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames, HintShardingValue<Long> shardingValue) {
Collection<String> result = new ArrayList<>();
// 遍历数据源
for (String actualDb : availableTargetNames) {
// hintShardingValue: 代表分片值, 指的是使用者对分片键赋的值
Collection<Long> values = shardingValue.getValues();
for (Long value : values) {
//直接路由到与我们传入的路由值相等的数据源上
if(actualDb.endsWith(String.valueOf(value))){
result.add(actualDb);
}
}
}
return result;
}
}
配置
# 对t_course表进行强制路由
# 强制路由库设置
spring.shardingsphere.sharding.tables.t_course.database-strategy.hint.algorithm-class-name=com.example.demo.shardingsphere.MyHintShardingAlgorithm
# 强制路由表设置
spring.shardingsphere.sharding.tables.t_course.table-strategy.hint.algorithm-class-name=com.example.demo.shardingsphere.MyHintShardingAlgorithm
添加分片键值
使用hintManager.addDatabaseShardingValue来添加数据源分片键值。
使用hintManager.addTableShardingValue来添加表分片键值。
分库不分表情况下,强制路由至某一个分库时,可使用hintManager.setDatabaseShardingValue方式添加分片。
清除分片键值
分片键值保存在ThreadLocal中,所以需要在操作结束时调用hintManager.close()来清除ThreadLocal中的内容。
close方法实际调用的是clear方法,而clear方法调用的是ThreadLocal的remove方法
/**
* Clear threadlocal for hint manager.
*/
public static void clear() {
HINT_MANAGER_HOLDER.remove();
}
@Override
public void close() {
HintManager.clear();
}
下面是一个简单的示例
//使用hintManager对数据库和表进行分片
String sql = "SELECT * FROM t_order";
try (HintManager hintManager = HintManager.getInstance();
Connection conn = dataSource.getConnection();
PreparedStatement preparedStatement = conn.prepareStatement(sql)) {
hintManager.addDatabaseShardingValue("t_order", 1);
hintManager.addTableShardingValue("t_order", 2);
try (ResultSet rs = preparedStatement.executeQuery()) {
while (rs.next()) {
// ...
}
}
}
//使用hintmanager对数据库进行分片,不使用分片表,只路由到一个数据库
String sql = "SELECT * FROM t_order";
try (HintManager hintManager = HintManager.getInstance();
Connection conn = dataSource.getConnection();
PreparedStatement preparedStatement = conn.prepareStatement(sql)) {
hintManager.setDatabaseShardingValue(3);
try (ResultSet rs = preparedStatement.executeQuery()) {
while (rs.next()) {
// ...
}
}
}
可以看到,单独路由某个库,使用setDatabaseShardingValue设置值即可,如果需要路由库和表,则需要设置addDatabaseShardingValue和addTableShardingValue两个值
在读写分离的情况,有些时候,我们想要直接访问主库,来保证数据的实时性,这就可以使用我们的强制主库路由
设置主库路由
使用hintManager.setMasterRouteOnly方法设置主库路由,通过调用该方法,可以指定只路由到主库
示例
String sql = "SELECT * FROM t_order";
try (
HintManager hintManager = HintManager.getInstance();
Connection conn = dataSource.getConnection();
PreparedStatement preparedStatement = conn.prepareStatement(sql)) {
hintManager.setMasterRouteOnly();
try (ResultSet rs = preparedStatement.executeQuery()) {
while (rs.next()) {
// ...
}
}
}
只需要调用 hintManager.setMasterRouteOnly()方法即可,setMasterRouteOnly()会将只进行主库路由的标识设置为 true,这样在后续的数据路由过程中,将忽略从库的存在,只路由到主库。
public void setMasterRouteOnly() {
masterRouteOnly = true;
}
使用之后,调用close或clear方法清除ThreadLocal变量