Sharding-Jdbc 自定義復(fù)合分片的實(shí)現(xiàn)(分庫(kù)分表)
Sharding-JDBC中的分片策略有兩個(gè)維度,分別是:
- 數(shù)據(jù)源分片策略(DatabaseShardingStrategy)
- 表分片策略(TableShardingStrategy)
其中,數(shù)據(jù)源分片策略表示:數(shù)據(jù)路由到的物理目標(biāo)數(shù)據(jù)源,表分片策略表示數(shù)據(jù)被路由到的目標(biāo)表。
特別的,表分片策略是依賴于數(shù)據(jù)源分片策略的,也就是說(shuō)要先分庫(kù)再分表,當(dāng)然也可以只分表。
Sharding-JDBC的數(shù)據(jù)分片策略
Sharding-JDBC的分片策略包含了分片鍵和分片算法。由于分片算法與業(yè)務(wù)實(shí)現(xiàn)緊密相關(guān),因此Sharding-JDBC沒(méi)有提供內(nèi)置的分片算法,而是通過(guò)分片策略將各種場(chǎng)景提煉出來(lái),提供了高層級(jí)的抽象,通過(guò)提供接口讓開發(fā)者自行實(shí)現(xiàn)分片算法。
以下內(nèi)容引用自官方文檔。官方文檔
首先介紹四種分片算法。
通過(guò)分片算法將數(shù)據(jù)分片,支持通過(guò)=、BETWEEN和IN分片。
分片算法需要應(yīng)用方開發(fā)者自行實(shí)現(xiàn),可實(shí)現(xiàn)的靈活度非常高。
目前提供4種分片算法。由于分片算法和業(yè)務(wù)實(shí)現(xiàn)緊密相關(guān),
因此并未提供內(nèi)置分片算法,而是通過(guò)分片策略將各種場(chǎng)景提煉出來(lái),
提供更高層級(jí)的抽象,并提供接口讓應(yīng)用開發(fā)者自行實(shí)現(xiàn)分片算法。
分片鍵
用于分片的數(shù)據(jù)庫(kù)字段,是將數(shù)據(jù)庫(kù)(表)水平拆分的關(guān)鍵字段。例:將訂單表中的訂單主鍵的尾數(shù)取模分片,則訂單主鍵為分片字段。 SQL中如果無(wú)分片字段,將執(zhí)行全路由,性能較差。 除了對(duì)單分片字段的支持,ShardingSphere也支持根據(jù)多個(gè)字段進(jìn)行分片。
分片算法
通過(guò)分片算法將數(shù)據(jù)分片,支持通過(guò)=
、BETWEEN
和IN
分片。分片算法需要應(yīng)用方開發(fā)者自行實(shí)現(xiàn),可實(shí)現(xiàn)的靈活度非常高。
目前提供4種分片算法。由于分片算法和業(yè)務(wù)實(shí)現(xiàn)緊密相關(guān),因此并未提供內(nèi)置分片算法,而是通過(guò)分片策略將各種場(chǎng)景提煉出來(lái),提供更高層級(jí)的抽象,并提供接口讓應(yīng)用開發(fā)者自行實(shí)現(xiàn)分片算法。
精確分片算法
對(duì)應(yīng)PreciseShardingAlgorithm,用于處理使用單一鍵作為分片鍵的=與IN進(jìn)行分片的場(chǎng)景。需要配合StandardShardingStrategy使用。
范圍分片算法
對(duì)應(yīng)RangeShardingAlgorithm,用于處理使用單一鍵作為分片鍵的BETWEEN AND進(jìn)行分片的場(chǎng)景。需要配合StandardShardingStrategy使用。
復(fù)合分片算法
對(duì)應(yīng)ComplexKeysShardingAlgorithm,用于處理使用多鍵作為分片鍵進(jìn)行分片的場(chǎng)景,包含多個(gè)分片鍵的邏輯較復(fù)雜,需要應(yīng)用開發(fā)者自行處理其中的復(fù)雜度。需要配合ComplexShardingStrategy使用。
Hint分片算法
對(duì)應(yīng)HintShardingAlgorithm,用于處理使用Hint行分片的場(chǎng)景。需要配合HintShardingStrategy使用。
分片策略
包含分片鍵和分片算法,由于分片算法的獨(dú)立性,將其獨(dú)立抽離。真正可用于分片操作的是分片鍵 + 分片算法,也就是分片策略。目前提供5種分片策略。
標(biāo)準(zhǔn)分片策略
對(duì)應(yīng)StandardShardingStrategy。提供對(duì)SQL語(yǔ)句中的=, IN和BETWEEN AND的分片操作支持。StandardShardingStrategy只支持單分片鍵,提供PreciseShardingAlgorithm和RangeShardingAlgorithm兩個(gè)分片算法。PreciseShardingAlgorithm是必選的,用于處理=和IN的分片。RangeShardingAlgorithm是可選的,用于處理BETWEEN AND分片,如果不配置RangeShardingAlgorithm,SQL中的BETWEEN AND將按照全庫(kù)路由處理。
復(fù)合分片策略
對(duì)應(yīng)ComplexShardingStrategy。復(fù)合分片策略。提供對(duì)SQL語(yǔ)句中的=, IN和BETWEEN AND的分片操作支持。ComplexShardingStrategy支持多分片鍵,由于多分片鍵之間的關(guān)系復(fù)雜,因此并未進(jìn)行過(guò)多的封裝,而是直接將分片鍵值組合以及分片操作符透?jìng)髦练制惴?,完全由?yīng)用開發(fā)者實(shí)現(xiàn),提供最大的靈活度。
行表達(dá)式分片策略
對(duì)應(yīng)InlineShardingStrategy。使用Groovy的表達(dá)式,提供對(duì)SQL語(yǔ)句中的=和IN的分片操作支持,只支持單分片鍵。對(duì)于簡(jiǎn)單的分片算法,可以通過(guò)簡(jiǎn)單的配置使用,從而避免繁瑣的Java代碼開發(fā),如:t_user_$->{u_id % 8}
表示t_user表根據(jù)u_id模8,而分成8張表,表名稱為t_user_0
到t_user_7
。
Hint分片策略
對(duì)應(yīng)HintShardingStrategy。通過(guò)Hint而非SQL解析的方式分片的策略。
不分片策略
對(duì)應(yīng)NoneShardingStrategy。不分片的策略。
SQL Hint
對(duì)于分片字段非SQL決定,而由其他外置條件決定的場(chǎng)景,可使用SQL Hint靈活的注入分片字段。例:內(nèi)部系統(tǒng),按照員工登錄主鍵分庫(kù),而數(shù)據(jù)庫(kù)中并無(wú)此字段。SQL Hint支持通過(guò)Java API和SQL注釋(待實(shí)現(xiàn))兩種方式使用。
實(shí)戰(zhàn)–自定義復(fù)合分片策略
由于目的為貼近實(shí)戰(zhàn),因此著重講解如何實(shí)現(xiàn)復(fù)雜分片策略,即實(shí)現(xiàn)ComplexShardingStrategy接口定制生產(chǎn)可用的分片策略。
AdminIdShardingAlgorithm 復(fù)合分片算法代碼如下:
import com.google.common.collect.Range; import io.shardingjdbc.core.api.algorithm.sharding.ListShardingValue; import io.shardingjdbc.core.api.algorithm.sharding.PreciseShardingValue; import io.shardingjdbc.core.api.algorithm.sharding.RangeShardingValue; import io.shardingjdbc.core.api.algorithm.sharding.ShardingValue; import io.shardingjdbc.core.api.algorithm.sharding.complex.ComplexKeysShardingAlgorithm; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import java.util.*; /** */ public class AdminIdShardingAlgorithm implements ComplexKeysShardingAlgorithm { private Logger logger = Logger.getLogger(getClass()); @Override public Collection<String> doSharding(Collection<String> availableTargetNames, Collection<ShardingValue> shardingValues) { Collection<String> routTables = new HashSet<String>(); if (shardingValues != null) { for (ShardingValue shardingValue : shardingValues) { // eq in 條件 if (shardingValue instanceof ListShardingValue) { ListShardingValue listShardingValue = (ListShardingValue) shardingValue; Collection<Comparable> values = listShardingValue.getValues(); if (values != null) { Iterator<Comparable> it = values.iterator(); while (it.hasNext()) { Comparable value = it.next(); String routTable = getRoutTable(shardingValue.getLogicTableName(), value); if (StringUtils.isNotBlank(routTable)) { routTables.add(routTable); } } } // eq 條件 } else if (shardingValue instanceof PreciseShardingValue) { PreciseShardingValue preciseShardingValue = (PreciseShardingValue) shardingValue; Comparable value = preciseShardingValue.getValue(); String routTable = getRoutTable(shardingValue.getLogicTableName(), value); if (StringUtils.isNotBlank(routTable)) { routTables.add(routTable); } // between 條件 } else if (shardingValue instanceof RangeShardingValue) { RangeShardingValue rangeShardingValue = (RangeShardingValue) shardingValue; Range<Comparable> valueRange = rangeShardingValue.getValueRange(); Comparable lowerEnd = valueRange.lowerEndpoint(); Comparable upperEnd = valueRange.upperEndpoint(); Collection<String> tables = getRoutTables(shardingValue.getLogicTableName(), lowerEnd, upperEnd); if (tables != null && tables.size() > 0) { routTables.addAll(tables); } } if (routTables != null && routTables.size() > 0) { return routTables; } } } throw new UnsupportedOperationException(); } private String getRoutTable(String logicTable, Comparable keyValue) { Map<String, List<KeyShardingRange>> keyRangeMap = KeyShardingRangeConfig.getKeyRangeMap(); List<KeyShardingRange> keyShardingRanges = keyRangeMap.get(KeyShardingRangeConfig.SHARDING_ID_KEY); if (keyValue != null && keyShardingRanges != null) { if (keyValue instanceof Integer) { keyValue = Long.valueOf(((Integer) keyValue).intValue()); } for (KeyShardingRange range : keyShardingRanges) { if (keyValue.compareTo(range.getMin()) >= 0 && keyValue.compareTo(range.getMax()) <= 0) { return logicTable + range.getTableKey(); } } } return null; } private Collection<String> getRoutTables(String logicTable, Comparable lowerEnd, Comparable upperEnd) { Map<String, List<KeyShardingRange>> keyRangeMap = KeyShardingRangeConfig.getKeyRangeMap(); List<KeyShardingRange> keyShardingRanges = keyRangeMap.get(KeyShardingRangeConfig.SHARDING_CONTENT_ID_KEY); Set<String> routTables = new HashSet<String>(); if (lowerEnd != null && upperEnd != null && keyShardingRanges != null) { if (lowerEnd instanceof Integer) { lowerEnd = Long.valueOf(((Integer) lowerEnd).intValue()); } if (upperEnd instanceof Integer) { upperEnd = Long.valueOf(((Integer) upperEnd).intValue()); } boolean start = false; for (KeyShardingRange range : keyShardingRanges) { if (lowerEnd.compareTo(range.getMin()) >= 0 && lowerEnd.compareTo(range.getMax()) <= 0) { start = true; } if (start) { routTables.add(logicTable + range.getTableKey()); } if (upperEnd.compareTo(range.getMin()) >= 0 && upperEnd.compareTo(range.getMax()) <= 0) { break; } } } return routTables; } }
范圍 map 如下:
import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * 分片鍵分布配置 */ public class KeyShardingRangeConfig { private static Map<String, List<KeyShardingRange>> keyRangeMap = new LinkedHashMap<String, List<KeyShardingRange>>(); public static final String SHARDING_ORDER_ID_KEY = "id"; public static final String SHARDING_USER_ID_KEY = "adminId"; public static final String SHARDING_DATE_KEY = "createTime"; static { List<KeyShardingRange> idRanges = new ArrayList<KeyShardingRange>(); idRanges.add(new KeyShardingRange(0, "_0", 0L, 4000000L)); idRanges.add(new KeyShardingRange(1, "_1", 4000001L, 8000000L)); idRanges.add(new KeyShardingRange(2, "_2", 8000001L, 12000000L)); idRanges.add(new KeyShardingRange(3, "_3", 12000001L, 16000000L)); idRanges.add(new KeyShardingRange(4, "_4", 16000001L, 2000000L)); keyRangeMap.put(SHARDING_ID_KEY, idRanges); List<KeyShardingRange> contentIdRanges = new ArrayList<KeyShardingRange>(); contentIdRanges.add(new KeyShardingRange(0, "_0", 0L, 4000000L)); contentIdRanges.add(new KeyShardingRange(1, "_1", 4000001L, 8000000L)); contentIdRanges.add(new KeyShardingRange(2, "_2", 8000001L, 12000000L)); contentIdRanges.add(new KeyShardingRange(3, "_3", 12000001L, 16000000L)); contentIdRanges.add(new KeyShardingRange(4, "_4", 16000001L, 2000000L)); keyRangeMap.put(SHARDING_CONTENT_ID_KEY, contentIdRanges); List<KeyShardingRange> timeRanges = new ArrayList<KeyShardingRange>(); timeRanges.add(new KeyShardingRange("_0", 20170701L, 20171231L)); timeRanges.add(new KeyShardingRange("_1", 20180101L, 20180630L)); timeRanges.add(new KeyShardingRange("_2", 20180701L, 20181231L)); timeRanges.add(new KeyShardingRange("_3", 20190101L, 20190630L)); timeRanges.add(new KeyShardingRange("_4", 20190701L, 20191231L)); keyRangeMap.put(SHARDING_DATE_KEY, timeRanges); } public static Map<String, List<KeyShardingRange>> getKeyRangeMap() { return keyRangeMap; } }
核心邏輯解析
梳理一下邏輯,首先介紹一下該方法的入?yún)?/p>
參數(shù)名 解釋
availableTargetNames 有效的物理數(shù)據(jù)源,即配置文件中的 t_order_0,t_order_1,t_order_2,t_order_3
shardingValues 分片屬性,如:{“columnName”:”order_id”,”logicTableName”:”t_order”,”values”:[“UD020003011903261545436593200002”]} ,包含:分片列名,邏輯表名,當(dāng)前列的具體分片值
該方法返回值為
參數(shù)名 解釋
Collection<String> 分片結(jié)果,可以是目標(biāo)數(shù)據(jù)源,也可以是目標(biāo)數(shù)據(jù)表,此處為數(shù)據(jù)源
接著回來(lái)看業(yè)務(wù)邏輯,偽代碼如下
首先打印了一下數(shù)據(jù)源集合 availableTargetNames 以及 分片屬性 shardingValues的值,執(zhí)行測(cè)試用例后,日志輸出為:
availableTargetNames:["t_order_0","t_order_1","t_order_2","t_order_3"], shardingValues:[{"columnName":"user_id","logicTableName":"t_order","values":["UD020003011903261545436593200002"]}, {"columnName":"order_id","logicTableName":"t_order","values":["OD000000011903261545475143200001"]}]
從日志可以看出,我們可以在該路由方法中取到配置時(shí)的物理數(shù)據(jù)源列表,以及在運(yùn)行時(shí)獲取本次執(zhí)行時(shí)的路由屬性及其值
完整的邏輯流程如下:
- 定義一個(gè)集合用于放置最終匹配好的路由數(shù)據(jù)源,接著對(duì)shardingValues進(jìn)行遍歷,目的為至少命中一個(gè)路由鍵
- 遍歷shardingValues循環(huán)體中,打印了當(dāng)前循環(huán)的shardingValue,即實(shí)際的分片鍵的數(shù)值,如:訂單號(hào)、用戶id等。通過(guò)getIndex方法,獲取該分片鍵值中包含的物理數(shù)據(jù)源索引
- 接著遍歷數(shù)據(jù)源列表availableTargetNames,截取當(dāng)前循環(huán)對(duì)應(yīng)availableTargetName的索引值,(eg: ds0則取0,ds1則取1…以此類推)將該配置的物理數(shù)據(jù)源索引與 第2步 中解析到的數(shù)據(jù)源路由索引進(jìn)行比較,兩者相等則表名我們期望將該數(shù)據(jù)路由到該匹配到的數(shù)據(jù)源。
- 執(zhí)行這個(gè)過(guò)程,直到匹配到一個(gè)路由鍵則停止循環(huán),之所以這么做是因?yàn)槲覀兪菑?fù)合分片,至少要匹配到一個(gè)路由規(guī)則,才能停止循環(huán),最終將路由到的物理數(shù)據(jù)源(ds0/ds1/ds2/ds3)通過(guò)add方法添加到事先定義好的集合中并返回給框架。
- 邏輯結(jié)束。
小結(jié)
本文中,基本完成了Sharding-JDBC中復(fù)合分片路由算法的自定義實(shí)現(xiàn),并經(jīng)過(guò)測(cè)試驗(yàn)證符合預(yù)期,該實(shí)現(xiàn)方案在生產(chǎn)上已經(jīng)經(jīng)歷過(guò)考驗(yàn)。定義分片路由策略的核心還是要熟悉ComplexKeysShardingAlgorithm,對(duì)如何解析 doSharding(CollectionavailableTargetNames, CollectionshardingValues)的參數(shù)有明確的認(rèn)識(shí),最簡(jiǎn)單的方法就是實(shí)際打印一下參數(shù),相信會(huì)讓你更加直觀的感受到作者優(yōu)良的接口設(shè)計(jì)能力,站在巨人的肩膀上我們能看到更遠(yuǎn)。
到此這篇關(guān)于Sharding-Jdbc 自定義復(fù)合分片的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Sharding-Jdbc 自定義復(fù)合分片內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot 2.0 整合sharding-jdbc中間件實(shí)現(xiàn)數(shù)據(jù)分庫(kù)分表
- Spring Boot 集成 Sharding-JDBC + Mybatis-Plus 實(shí)現(xiàn)分庫(kù)分表功能
- Java使用Sharding-JDBC分庫(kù)分表進(jìn)行操作
- SpringBoot整合sharding-jdbc實(shí)現(xiàn)分庫(kù)分表與讀寫分離的示例
- SpringBoot整合sharding-jdbc實(shí)現(xiàn)自定義分庫(kù)分表的實(shí)踐
- 利用Sharding-Jdbc進(jìn)行分庫(kù)分表的操作代碼
- SpringBoot+MybatisPlus+Mysql+Sharding-JDBC分庫(kù)分表
- Mybatis-Plus集成Sharding-JDBC與Flyway實(shí)現(xiàn)多租戶分庫(kù)分表實(shí)戰(zhàn)
- SpringBoot+MybatisPlus實(shí)現(xiàn)sharding-jdbc分庫(kù)分表的示例代碼
相關(guān)文章
Java多線程并發(fā)synchronized?關(guān)鍵字
這篇文章主要介紹了Java多線程并發(fā)synchronized?關(guān)鍵字,Java?在虛擬機(jī)層面提供了?synchronized?關(guān)鍵字供開發(fā)者快速實(shí)現(xiàn)互斥同步的重量級(jí)鎖來(lái)保障線程安全。2022-06-06java處理異常的機(jī)制關(guān)鍵字throw和throws使用解析
這篇文章主要介紹了java處理異常的機(jī)制關(guān)鍵字throw和throws使用解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09Java SMM框架關(guān)聯(lián)關(guān)系映射示例講解
SSM框架是spring MVC ,spring和mybatis框架的整合,是標(biāo)準(zhǔn)的MVC模式,將整個(gè)系統(tǒng)劃分為表現(xiàn)層,controller層,service層,DAO層四層,使用spring MVC負(fù)責(zé)請(qǐng)求的轉(zhuǎn)發(fā)和視圖管理,spring實(shí)現(xiàn)業(yè)務(wù)對(duì)象管理,mybatis作為數(shù)據(jù)對(duì)象的持久化引擎2022-08-08Java中jakarta.validation數(shù)據(jù)校驗(yàn)幾個(gè)主要依賴包講解
在Java開發(fā)中,BeanValidationAPI提供了一套標(biāo)準(zhǔn)的數(shù)據(jù)驗(yàn)證機(jī)制,尤其是通過(guò)JakartaBeanValidation(原HibernateValidator)實(shí)現(xiàn),文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-09-09java按照模板導(dǎo)出pdf或word文件詳細(xì)代碼
有時(shí)候業(yè)務(wù)中我們需要使用pdf模板生成一份pdf文件,下面這篇文章主要給大家介紹了關(guān)于java按照模板導(dǎo)出pdf或word文件的相關(guān)資料,文中給出了詳細(xì)的代碼示例,需要的朋友可以參考下2023-11-11詳談Java中Object類中的方法以及finalize函數(shù)作用
下面小編就為大家?guī)?lái)一篇詳談Java中Object類中的方法以及finalize函數(shù)作用。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-04-04使用SpringBoot發(fā)送郵箱驗(yàn)證碼的簡(jiǎn)單實(shí)現(xiàn)
這篇文章主要介紹了使用SpringBoot發(fā)送郵箱驗(yàn)證碼的簡(jiǎn)單實(shí)現(xiàn),咱們今天來(lái)講使用QQ郵箱來(lái)發(fā)送和接收驗(yàn)證碼,首先來(lái)介紹一下它在SpringBoot項(xiàng)目中的具體應(yīng)用,需要的朋友可以參考下2023-04-04