1. 什么是強制路由
一種通過在外部業(yè)務(wù)代碼中指定路由配置的一種方式笋颤,在ShardingSphere中叫做Hint祸憋。如果使用Hint指定了強制分片路由虐急,那么SQL將會無視原有的分片邏輯比庄,直接路由至指定的數(shù)據(jù)節(jié)點操作唉工。
2. Hint強制路由使用場景研乒?
- 數(shù)據(jù)分片操作,如果分片鍵沒有在SQL或數(shù)據(jù)表中(沒有按表中字段分片)淋硝,而是在業(yè)務(wù)邏輯代碼中雹熬;
- 讀寫分離操作,如果強制在主庫進行某些數(shù)據(jù)操作谣膳;
基于Hint的強制主庫路由竿报。可以強制路由走主庫查詢實時數(shù)據(jù)继谚,避免主從同步數(shù)據(jù)延遲烈菌。
3. Hint使用步驟
3.1 編寫分庫或分表路由策略,實現(xiàn)HintShardingAlgorithm接口
// 泛型 Long 代表傳入的 參數(shù)類型為 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){ //代表:分片目標(biāo)花履,對哪些數(shù)據(jù)庫芽世、表分片。如果是對分庫路由诡壁,表示ds0济瓢,ds1;
for (Long value : shardingValue.getValues()){ // 代表:分片值; 可以HintManager設(shè)置多個分片值妹卿,所以是個集合旺矾。
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 在業(yè)務(wù)代碼中執(zhí)行查詢前使用HintManager指定執(zhí)行策略值
@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添加的配置信息只能在當(dāng)前線程內(nèi)有效。
//
// 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只是對當(dāng)前線程生效
private static final ThreadLocal<HintManager> HINT_MANAGER_HOLDER = new ThreadLocal();
private final Multimap<String, Comparable<?>> databaseShardingValues = HashMultimap.create(); // 數(shù)據(jù)庫的路由值信息
private final Multimap<String, Comparable<?>> tableShardingValues = HashMultimap.create(); // 表的路由值信息
private boolean databaseShardingOnly; // 是否只強制路由數(shù)據(jù)庫
private boolean masterRouteOnly; // shi是否只強制路由主數(shù)據(jù)庫
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... 的區(qū)別:就是是否只路由 數(shù)據(jù)庫
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() {
}
}
注意:在讀寫分離結(jié)構(gòu)中,為了避免主從同步數(shù)據(jù)延遲及時獲取剛添加或更新的數(shù)據(jù)魁索,可以采用強制路由走主庫查詢實時數(shù)據(jù)融撞,使用hintManager.setMasterRouteOnly設(shè)置主庫路由即可。