1. Sharding-JDBC 分片策略
分片操作是分片鍵 + 分片算法,也就是分片策略。目前Sharding-JDBC 支持多種分片策略:
- 標(biāo)準(zhǔn)分片策略
對應(yīng)StandardShardingStrategy。提供對SQL語句中的=, IN和BETWEEN AND的分片操作支持。 - 復(fù)合分片策略
對應(yīng)ComplexShardingStrategy。復(fù)合分片策略。提供對SQL語句中的=, IN和BETWEEN AND的分片操作支持。 - 行表達(dá)式分片策略
對應(yīng)InlineShardingStrategy。使用Groovy的表達(dá)式,提供對SQL語句中的=和IN的分片操作支持,只支持單分片鍵。 - Hint分片策略
對應(yīng)HintShardingStrategy。通過Hint而非SQL解析的方式分片的策略。
具體的請參考官方文檔:sharding分片策略
2. Sharding-JDBC 分片算法
分片算法需要應(yīng)用方開發(fā)者自行實(shí)現(xiàn),可實(shí)現(xiàn)的靈活度非常高。
目前 Sharding-JDBC提供4種分片算法。由于分片算法和業(yè)務(wù)實(shí)現(xiàn)緊密相關(guān),因此并未提供內(nèi)置分片算法,而是通過分片策略將各種場景提煉出來,提供更高層級的抽象,并提供接口讓應(yīng)用開發(fā)者自行實(shí)現(xiàn)分片算法。
-
精確分片算法
對應(yīng)PreciseShardingAlgorithm,用于處理使用單一鍵作為分片鍵的=與IN進(jìn)行分片的場景。需要配合StandardShardingStrategy使用。 -
范圍分片算法
對應(yīng)RangeShardingAlgorithm,用于處理使用單一鍵作為分片鍵的BETWEEN AND進(jìn)行分片的場景。需要配合StandardShardingStrategy使用。 -
復(fù)合分片算法
對應(yīng)ComplexKeysShardingAlgorithm,用于處理使用多鍵作為分片鍵進(jìn)行分片的場景,包含多個(gè)分片鍵的邏輯較復(fù)雜,需要應(yīng)用開發(fā)者自行處理其中的復(fù)雜度。需要配合ComplexShardingStrategy使用。 -
Hint分片算法
對應(yīng)HintShardingAlgorithm,用于處理使用Hint行分片的場景。需要配合HintShardingStrategy使用。
3. 自定義一致性哈希算法實(shí)踐
什么是一致性哈希算法 請看這篇博客:文章來源:http://www.zghlxwxcb.cn/news/detail-433934.html
- 首先實(shí)現(xiàn)ShardingJDBC提供的接口:
- PreciseShardingAlgorithm:精確分片算法,如果使用 == 來判定分片的情況,需要實(shí)現(xiàn)這個(gè)接口。
- RangeShardingAlgorithm:范圍分片,如果有使用范圍查找的話,需要使用這個(gè)進(jìn)行分片策略。
/**
* 自定義哈希算法 + 虛擬節(jié)點(diǎn)實(shí)現(xiàn)數(shù)據(jù)分片
* 使用FNV1_32_HASH算法計(jì)算key的Hash值
* 也可以使用 MurmurHash3 或者別的加密方式
* @author manji
* @Date 2023/5/4
*/
public class ConsistenceHashAlgorithm implements RangeShardingAlgorithm<Long>, PreciseShardingAlgorithm<Long> {
/**
* 范圍查找時(shí)需要用到改分片算法,這里暫不完善了
* @param collection
* @param rangeShardingValue
* @return
*/
@Override
public Collection<String> doSharding(Collection collection, RangeShardingValue rangeShardingValue) {
System.out.println(collection);
System.out.println(rangeShardingValue);
return collection;
}
/**
* @param collection collection 配置文件中解析到的所有分片節(jié)點(diǎn)
* @param preciseShardingValue 解析到的sql值
* @return
*/
@Override
public String doSharding(Collection collection, PreciseShardingValue preciseShardingValue) {
System.out.println(collection);
InitTableNodesToHashLoop initTableNodesToHashLoop = SpringUtils.getBean(InitTableNodesToHashLoop.class);
if (CollectionUtils.isEmpty(collection)) {
return preciseShardingValue.getLogicTableName();
}
//這里主要為了兼容當(dāng)聯(lián)表查詢時(shí),如果兩個(gè)表非關(guān)聯(lián)表則
//當(dāng)對副表分表時(shí)shardingValue這里傳遞進(jìn)來的依然是主表的名稱,
//但availableTargetNames中確是副表名稱,所有這里要從availableTargetNames中匹配真實(shí)表
ArrayList<String> availableTargetNameList = new ArrayList<>(collection);
String logicTableName = availableTargetNameList.get(0).replaceAll("[^(a-zA-Z_)]", "");
SortedMap<Long, String> tableHashNode =
initTableNodesToHashLoop .getTableVirtualNodes().get(logicTableName);
ConsistenceHashUtil consistentHashAlgorithm = new ConsistenceHashUtil(tableHashNode,
collection);
return consistentHashAlgorithm.getTableNode(String.valueOf(preciseShardingValue.getValue()));
}
}
- 初始化hash環(huán)
注意:@Lazy 不添加的話會報(bào)錯(cuò)ShardingDataSource 找不到,因?yàn)樵诩虞d該類時(shí),ShardingDataSource 還未放入容器中,所以獲取不到,所以使用@Lazy 注解延后該類的加載。
/**
* 初始化hash環(huán)
* @author manji
* @Date 2023/5/4
*/
@Slf4j
@Component
@Lazy
public class InitTableNodesToHashLoop {
@Resource
private ShardingDataSource shardingDataSource;
@Getter
private HashMap<String, SortedMap<Long, String>> tableVirtualNodes = new HashMap<>();
@PostConstruct
public void init() {
try {
ShardingRule rule = shardingDataSource.getShardingContext().getShardingRule();
Collection<TableRule> tableRules = rule.getTableRules();
ConsistenceHashUtil consistenceHashUtil = new ConsistenceHashUtil();
for (TableRule tableRule : tableRules) {
String logicTable = tableRule.getLogicTable();
tableVirtualNodes.put(logicTable,
consistenceHashUtil.initNodesToHashLoop(
tableRule.getActualDataNodes()
.stream()
.map(DataNode::getTableName)
.collect(Collectors.toList()))
);
}
} catch (Exception e) {
log.error("分表節(jié)點(diǎn)初始化失敗 {}", e);
}
}
}
- 一致性hash算法的核心代碼
/**
* 一致性哈希算法工具類
* @author manji
* @Date 2023/5/4
*/
public class ConsistenceHashUtil {
//存儲所有節(jié)點(diǎn),按照hash值排序的
@Getter
private SortedMap<Long, String> virtualNodes = new TreeMap<>();
// 設(shè)置虛擬節(jié)點(diǎn)的個(gè)數(shù)
private static final int VIRTUAL_NODES = 3;
public ConsistenceHashUtil() {
}
public ConsistenceHashUtil(SortedMap<Long, String> virtualTableNodes, Collection<String> tableNodes) {
if (Objects.isNull(virtualTableNodes)) {
virtualTableNodes = initNodesToHashLoop(tableNodes);
}
this.virtualNodes = virtualTableNodes;
}
public SortedMap<Long, String> initNodesToHashLoop(Collection<String> tableNodes) {
SortedMap<Long, String> virtualTableNodes = new TreeMap<>();
for (String node : tableNodes) {
for (int i = 0; i < VIRTUAL_NODES; i++) {
String s = String.valueOf(i);
String virtualNodeName = node + "-manji" + s;
long hash = getHash(virtualNodeName);
virtualTableNodes.put(hash, virtualNodeName);
}
}
return virtualTableNodes;
}
/**
* 通過計(jì)算key的hash
* 計(jì)算映射的表節(jié)點(diǎn)
*
* @param key
* @return
*/
public String getTableNode(String key) {
String virtualNode = getVirtualTableNode(key);
//虛擬節(jié)點(diǎn)名稱截取后獲取真實(shí)節(jié)點(diǎn)
if (!StringUtils.isEmpty(virtualNode)) {
return virtualNode.substring(0, virtualNode.indexOf("-"));
}
return null;
}
/**
* 獲取虛擬節(jié)點(diǎn)
* @param key
* @return
*/
public String getVirtualTableNode(String key) {
long hash = getHash(key);
// 得到大于該Hash值的所有Map
SortedMap<Long, String> subMap = virtualNodes.tailMap(hash);
String virtualNode;
if (subMap.isEmpty()) {
//如果沒有比該key的hash值大的,則從第一個(gè)node開始
Long i = virtualNodes.firstKey();
//返回對應(yīng)的服務(wù)器
virtualNode = virtualNodes.get(i);
} else {
//第一個(gè)Key就是順時(shí)針過去離node最近的那個(gè)結(jié)點(diǎn)
Long i = subMap.firstKey();
//返回對應(yīng)的服務(wù)器
virtualNode = subMap.get(i);
}
return virtualNode;
}
/**
* 使用FNV1_32_HASH算法計(jì)算key的Hash值
* 也可以使用 MurmurHash3 或者別的加密方式
* @param key
* @return
*/
public long getHash(String key) {
// return MurmurHash3.murmurhash3_x86_32(key);
final int p = 16777619;
int hash = (int) 2166136261L;
for (int i = 0; i < key.length(); i++)
hash = (hash ^ key.charAt(i)) * p;
hash += hash << 13;
hash ^= hash >> 7;
hash += hash << 3;
hash ^= hash >> 17;
hash += hash << 5;
// 如果算出來的值為負(fù)數(shù)則取其絕對值
if (hash < 0)
hash = Math.abs(hash);
return hash;
}
}
- 配置Sharding-JDBC的分片策略
spring.main.allow-bean-definition-overriding = true
mybatis.configuration.map-underscore-to-camel-case = true
#數(shù)據(jù)源
spring.shardingsphere.datasource.names = m1
#數(shù)據(jù)源1
spring.shardingsphere.datasource.m1.type = com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.driver-class-name = com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m1.url = jdbc:mysql://localhost:3306/shardingdemo?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
spring.shardingsphere.datasource.m1.username = root
spring.shardingsphere.datasource.m1.password = 12345678
spring.shardingsphere.sharding.default-data-source-name=m1
# 指定user表的數(shù)據(jù)分布情況
spring.shardingsphere.sharding.tables.user.actual-data-nodes = m1.user_$->{0..2}
# 指定user表的分片策略,分片策略包括分片鍵和分片算法
spring.shardingsphere.sharding.tables.user.table-strategy.standard.sharding-column=id
spring.shardingsphere.sharding.tables.user.table-strategy.standard.precise-algorithm-class-name=com.manji.shardingdemo.consistencehasg.ConsistenceHashAlgorithm
# 打開sql輸出日志
spring.shardingsphere.props.sql.show = true
代碼demo地址:sharding-一致性哈希算法文章來源地址http://www.zghlxwxcb.cn/news/detail-433934.html
到了這里,關(guān)于Sharding-JDBC 自定義一致性哈希算法 + 虛擬節(jié)點(diǎn) 實(shí)現(xiàn)數(shù)據(jù)庫分片策略的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!