shardingsphere分庫(kù)分表

[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

  1. userId % 2 = 0 生巡,對(duì)應(yīng) order_0
  2. userId % 2 = 1 , 對(duì)應(yīng) order_1

此時(shí)擴(kuò)容到4個(gè)表

  1. userId % 4 = 0 耙蔑,對(duì)應(yīng) order_0
  2. userId % 4 = 1 , 對(duì)應(yīng) order_1
  3. userId % 4 = 2 ,對(duì)應(yīng) order_2
  4. userId % 4 = 3 , 對(duì)應(yīng) order_3

只需要遷移原來(lái)在 order_0, order_1 中孤荣,且 userId % 4 >=2 數(shù)據(jù)就行甸陌。

  1. userId % 4 = 2的數(shù)據(jù)须揣, 從 order_0 遷移到 order_2
  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

參考文章

  1. 大眾點(diǎn)評(píng)訂單系統(tǒng)分庫(kù)分表實(shí)踐
  2. 基于Apache ShardingSphere的核心業(yè)務(wù)分庫(kù)分表實(shí)踐
  3. DIY 3 種分庫(kù)分表分片算法
  4. 實(shí)戰(zhàn):Springboot3+ShardingSphere5.2.1生產(chǎn)級(jí)分庫(kù)分表實(shí)現(xiàn)
  5. 聊聊分庫(kù)分表后非Sharding Key查詢的三種方案
  6. 一些補(bǔ)充的知識(shí)點(diǎn)-MySQL大表分庫(kù)分表基因法
  7. 看完這一篇坷衍,ShardingSphere-jdbc 實(shí)戰(zhàn)再也不怕了
  8. SpringBoot 2 種方式快速實(shí)現(xiàn)分庫(kù)分表撵颊,輕松拿捏
  9. 分布式數(shù)據(jù)庫(kù):如何正確選擇分片鍵?

FAQ整理

  1. 點(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è)

  1. 點(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è)突颊。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末鲁豪,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子律秃,更是在濱河造成了極大的恐慌爬橡,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,816評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件棒动,死亡現(xiàn)場(chǎng)離奇詭異糙申,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)船惨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門郭宝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)辞槐,“玉大人,你說(shuō)我怎么就攤上這事粘室¢剩” “怎么了?”我有些...
    開封第一講書人閱讀 158,300評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵衔统,是天一觀的道長(zhǎng)鹿榜。 經(jīng)常有香客問(wèn)我,道長(zhǎng)锦爵,這世上最難降的妖魔是什么舱殿? 我笑而不...
    開封第一講書人閱讀 56,780評(píng)論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮险掀,結(jié)果婚禮上沪袭,老公的妹妹穿的比我還像新娘。我一直安慰自己樟氢,他們只是感情好冈绊,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著埠啃,像睡著了一般死宣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上碴开,一...
    開封第一講書人閱讀 50,084評(píng)論 1 291
  • 那天毅该,我揣著相機(jī)與錄音,去河邊找鬼潦牛。 笑死眶掌,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的巴碗。 我是一名探鬼主播畏线,決...
    沈念sama閱讀 39,151評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼良价!你這毒婦竟也來(lái)了寝殴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,912評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤明垢,失蹤者是張志新(化名)和其女友劉穎蚣常,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體痊银,經(jīng)...
    沈念sama閱讀 44,355評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡抵蚊,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贞绳。...
    茶點(diǎn)故事閱讀 38,809評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡谷醉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出冈闭,到底是詐尸還是另有隱情俱尼,我是刑警寧澤,帶...
    沈念sama閱讀 34,504評(píng)論 4 334
  • 正文 年R本政府宣布萎攒,位于F島的核電站遇八,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏耍休。R本人自食惡果不足惜刃永,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望羊精。 院中可真熱鬧斯够,春花似錦、人聲如沸喧锦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)裸违。三九已至,卻和暖如春本昏,著一層夾襖步出監(jiān)牢的瞬間供汛,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工涌穆, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留怔昨,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,628評(píng)論 2 362
  • 正文 我出身青樓宿稀,卻偏偏與公主長(zhǎng)得像趁舀,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子祝沸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容