[toc]
主要記錄使用shardingsphere-jdbc 5.2.0進(jìn)行分庫(kù),分表,分庫(kù)加分表都办,讀寫分離烙荷,自定義復(fù)合分片算法等的操作步驟镜会。
概念介紹
基因法
基因法原理 : 對(duì)一個(gè)數(shù)取余2的n次方,那么余數(shù)就是這個(gè)數(shù)的二進(jìn)制的最后n位數(shù)终抽。
- 舉例
十進(jìn)制數(shù)對(duì)10的n次冪取余戳表,余數(shù)是10進(jìn)制數(shù)的最后n位, 比如
11 % 10昼伴,余數(shù) 1
122 % 10匾旭,余數(shù) 2
122 % 100 , 余數(shù) 22
同理,對(duì)一個(gè)數(shù)取余2的n次方圃郊,余數(shù)就是這個(gè)數(shù)的二進(jìn)制的最后n位數(shù)价涝,然后可以再轉(zhuǎn)為10進(jìn)制。
為什么分片數(shù)要是2的n次冪
- 可以將取模算法優(yōu)化成性能更高的位運(yùn)算算法持舆。
如: 11 % 4 等于 11 & (4-1)
- 可以將表在多個(gè)庫(kù)中均勻分布色瘩。
通常分表會(huì)和分庫(kù)一起進(jìn)行,比如需要分成2個(gè)庫(kù)8個(gè)表逸寓,分表和分庫(kù)的數(shù)量都是2的n次冪居兆,可以實(shí)現(xiàn)均勻分布,8個(gè)表均分到2個(gè)庫(kù)中竹伸,每個(gè)庫(kù)4個(gè)表泥栖。
- 第三個(gè)也是最重要的原因,可以減少擴(kuò)容時(shí)遷移的數(shù)據(jù)量佩伤,只需要遷移一半聊倔。
比如 原來(lái)分2個(gè)表,order_0,order_1
- userId % 2 = 0 生巡,對(duì)應(yīng) order_0
- userId % 2 = 1 , 對(duì)應(yīng) order_1
此時(shí)擴(kuò)容到4個(gè)表
- userId % 4 = 0 耙蔑,對(duì)應(yīng) order_0
- userId % 4 = 1 , 對(duì)應(yīng) order_1
- userId % 4 = 2 ,對(duì)應(yīng) order_2
- userId % 4 = 3 , 對(duì)應(yīng) order_3
只需要遷移原來(lái)在 order_0, order_1 中孤荣,且 userId % 4 >=2 數(shù)據(jù)就行甸陌。
- userId % 4 = 2的數(shù)據(jù)须揣, 從 order_0 遷移到 order_2
- userId % 4 = 3的數(shù)據(jù), 從order_1 遷移到 order_3
具體配置和操作
以電商系統(tǒng)中用戶的訂單表為例钱豁。
電商系統(tǒng)中耻卡,常見的業(yè)務(wù)場(chǎng)景是通過(guò)userId查詢用戶的訂單列表,因此使用userId作為分片鍵牲尺,查詢時(shí)可以快速定位到數(shù)據(jù)庫(kù)和表卵酪。
但是也會(huì)有通過(guò)訂單號(hào)進(jìn)行查詢的情況,默認(rèn)情況下谤碳,非分片鍵的查詢溃卡,需要在所有分片上進(jìn)行查詢,然后對(duì)結(jié)果進(jìn)行聚合蜒简,這樣效率非常低瘸羡,日志中sql如下
Actual SQL: ds0 ::: SELECT user_id,order_id,address_id,status FROM t_order_0
WHERE order_id = ? UNION ALL SELECT user_id,order_id,address_id,status FROM t_order_1
WHERE order_id = ? :::
此時(shí)可以考慮新增一個(gè)訂單號(hào)和userId的映射關(guān)系表即索引表。
索引表法
將訂單號(hào)和userId的映射關(guān)系搓茬,單獨(dú)保存到一個(gè)表中犹赖,通過(guò)訂單號(hào)進(jìn)行查詢時(shí),先從索引表中查詢到對(duì)應(yīng)的userId卷仑,然后在查詢條件中加上userId峻村,這樣只需要2步就可以查詢出結(jié)果。
select user_id from order_user_relation where order_id = xxx;
select * from t_order where order_id = xxx and user_id = xxx;
為了提升查詢效率锡凝,可以在加上一層分布式緩存雀哨,將映射關(guān)系保存到redis中。但是這樣會(huì)帶來(lái)一定的問(wèn)題私爷,比如數(shù)據(jù)庫(kù)和緩存的存儲(chǔ)量增大雾棺。
基因法自定義復(fù)合分片算法
為了解決聚合查詢或者索引表的問(wèn)題,業(yè)界一般采用基因法來(lái)將分片鍵的信息衬浑,保存到非分片鍵中捌浩,比如取userId的后4位,拼接到order_id后面工秩。
假設(shè)訂單號(hào)的規(guī)則為 : 時(shí)間戳 + 隨機(jī)數(shù) + 用戶id后四位尸饺。這樣可以從訂單號(hào)的后四位中截取到用戶id的基因,可以定位到具體的分片位置助币,一次查詢就可以查到對(duì)應(yīng)的數(shù)據(jù)浪听。
比如淘寶的訂單號(hào),后6位都是一樣的眉菱,大概率也是用戶id的后6位迹栓。
- 算法實(shí)現(xiàn)
@Override
public Collection<String> doSharding(Collection availableTargetNames, ComplexKeysShardingValue complexKeysShardingValue) {
int count = availableTargetNames.size();
// 判斷分片數(shù)必須是2的整數(shù)次冪
if ((count & (count - 1)) != 0) {
log.warn("分片數(shù)不是2的整數(shù)次冪,當(dāng)前分片數(shù) {}", count);
throw new IllegalArgumentException("分片數(shù)量不是2的整數(shù)次冪俭缓,當(dāng)前數(shù)量:" + count);
}
ArrayList list = new ArrayList(availableTargetNames);
List<String> result = new ArrayList<>();
// String logicName = complexKeysShardingValue.getLogicTableName();
log.debug("availableTargetNames的值 {}", JSON.toJSONString(availableTargetNames));
log.debug("complexKeysShardingValue {}", JSON.toJSONString(complexKeysShardingValue));
// 先判斷userId克伊, 如果有userId酥郭, 則直接計(jì)算
Map<String, Collection<Comparable<?>>> nameAndValueMap = complexKeysShardingValue.getColumnNameAndShardingValuesMap();
Collection<Comparable<?>> userIdValueCollection = nameAndValueMap.get(userIdKey);
if (!CollectionUtils.isEmpty(userIdValueCollection)) {
userIdValueCollection.stream().findFirst().ifPresent(o -> {
Long index = (Long) o % count;
log.debug("以u(píng)serid {} 對(duì) 分片數(shù){} 取余的結(jié)果為 {}", o, count, index);
// result.add(logicName + "_" + index);
result.add(String.valueOf(list.get(index.intValue())));
});
} else {
// 從訂單號(hào)中截取用戶id基因
Collection<Comparable<?>> orderIdCollection = nameAndValueMap.get(orderIdKey);
orderIdCollection.stream().findFirst().ifPresent(comparable -> {
String orderId = String.valueOf(comparable);
String subString = orderId.substring(orderId.length() - userIdSuffixLength);
int index = Integer.parseInt(subString) % count;
log.debug("orderId中基因 {} 對(duì) 分片數(shù){} 取余的結(jié)果為 {}", subString, count, index);
// result.add(logicName + "_" + index);
result.add(String.valueOf(list.get(index)));
});
}
return result;
}
- 配置信息
# 模式配置
spring.shardingsphere.mode.type=Standalone
spring.shardingsphere.mode.repository.type=JDBC
# 數(shù)據(jù)源配置 配置真實(shí)數(shù)據(jù)源
spring.shardingsphere.datasource.names=ds0
spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://localhost:3306/shop_ds_0?serverTimezone=Asia/Shanghai&useSSL=false&useUnicode=true&characterEncoding=UTF-8
spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=root
# 廣播表規(guī)則列表
spring.shardingsphere.rules.sharding.broadcast-tables=t_address
# 標(biāo)準(zhǔn)分片表配置
# 由數(shù)據(jù)源名 + 表名組成,以小數(shù)點(diǎn)分隔愿吹。多個(gè)表以逗號(hào)分隔不从,支持 inline 表達(dá)式。
# 缺省表示使用已知數(shù)據(jù)源與邏輯表名稱生成數(shù)據(jù)節(jié)點(diǎn)犁跪,用于廣播表(即每個(gè)庫(kù)中都需要一個(gè)同樣的表用于關(guān)聯(lián)查詢椿息,多為字典表)或只分庫(kù)不分表且所有庫(kù)的表結(jié)構(gòu)完全一致的情況
spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=ds0.t_order_$->{0..1}
# 復(fù)合分片,自定義策略
spring.shardingsphere.rules.sharding.tables.t_order.table-strategy.complex.sharding-columns=user_id,order_id
# 分片算法名稱
spring.shardingsphere.rules.sharding.tables.t_order.table-strategy.complex.sharding-algorithm-name=t_order_complex_custom_algorithm
# 自定義復(fù)合分片算法
spring.shardingsphere.rules.sharding.sharding-algorithms.t_order_complex_custom_algorithm.type=CLASS_BASED
spring.shardingsphere.rules.sharding.sharding-algorithms.t_order_complex_custom_algorithm.props.strategy=complex
spring.shardingsphere.rules.sharding.sharding-algorithms.t_order_complex_custom_algorithm.props.algorithmClassName=com.xxxx.sharding.config.GeneComplexKeysShardingAlgorithm
參考文章
- 大眾點(diǎn)評(píng)訂單系統(tǒng)分庫(kù)分表實(shí)踐
- 基于Apache ShardingSphere的核心業(yè)務(wù)分庫(kù)分表實(shí)踐
- DIY 3 種分庫(kù)分表分片算法
- 實(shí)戰(zhàn):Springboot3+ShardingSphere5.2.1生產(chǎn)級(jí)分庫(kù)分表實(shí)現(xiàn)
- 聊聊分庫(kù)分表后非Sharding Key查詢的三種方案
- 一些補(bǔ)充的知識(shí)點(diǎn)-MySQL大表分庫(kù)分表基因法
- 看完這一篇坷衍,ShardingSphere-jdbc 實(shí)戰(zhàn)再也不怕了
- SpringBoot 2 種方式快速實(shí)現(xiàn)分庫(kù)分表撵颊,輕松拿捏
- 分布式數(shù)據(jù)庫(kù):如何正確選擇分片鍵?
FAQ整理
- 點(diǎn)評(píng)的文章中惫叛,userid后4位,為什么說(shuō)最大是8192個(gè)表
按照上面的規(guī)范逞刷,分片數(shù)必須是2的n次冪嘉涌,4位數(shù)的最大值是9999,假設(shè)分片數(shù)是 2的14次冪 16384夸浅,4位數(shù)字對(duì)16384取余的結(jié)果范圍是[0, 9999], 實(shí)際計(jì)算出來(lái)需要的分片數(shù)是10000個(gè)仑最,不是2的n次冪,不滿足分片數(shù)規(guī)則帆喇。
所以最大只能取 8192 個(gè)
- 點(diǎn)評(píng)的方式和基因法的區(qū)別警医。
點(diǎn)評(píng)文章中userid和orderid的取模都是用的userid的后四位,所以最大是8192個(gè)坯钦≡せ剩基因法里面,通過(guò)userid計(jì)算分片是按照userid % 2^n婉刀,按照訂單id計(jì)算分片 = orderId.substring(length-4) % 2^n吟温,
是使用訂單號(hào)的后四位進(jìn)行計(jì)算,所以u(píng)serid后四位的最大分片數(shù)是16個(gè)突颊。