一、環境準備 1.資料庫 創建2個庫2個表: xdclass_shop_order_0 product_order_0 product_order_1 ad_config product_order_item_0 product_order_item_1 xdclass_shop_order_1 p ...
一、環境準備
1.資料庫
創建2個庫2個表:
- xdclass_shop_order_0
- product_order_0
- product_order_1
- ad_config
- product_order_item_0
- product_order_item_1
- xdclass_shop_order_1
- product_order_0
- product_order_1
- ad_config
- product_order_item_0
- product_order_item_1
資料庫腳本:
CREATE TABLE `product_order_0` (
`id` bigint NOT NULL AUTO_INCREMENT,
`out_trade_no` varchar(64) DEFAULT NULL COMMENT '訂單唯一標識',
`state` varchar(11) DEFAULT NULL COMMENT 'NEW 未支付訂單,PAY已經支付訂單,CANCEL超時取消訂單',
`create_time` datetime DEFAULT NULL COMMENT '訂單生成時間',
`pay_amount` decimal(16,2) DEFAULT NULL COMMENT '訂單實際支付價格',
`nickname` varchar(64) DEFAULT NULL COMMENT '昵稱',
`user_id` bigint DEFAULT NULL COMMENT '用戶id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
CREATE TABLE `ad_config` (
`id` bigint unsigned NOT NULL COMMENT '主鍵id',
`config_key` varchar(1024) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '配置key',
`config_value` varchar(1024) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '配置value',
`type` varchar(128) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '類型',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
CREATE TABLE `product_order_item_0` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`product_order_id` bigint DEFAULT NULL COMMENT '訂單號',
`product_id` bigint DEFAULT NULL COMMENT '產品id',
`product_name` varchar(128) DEFAULT NULL COMMENT '商品名稱',
`buy_num` int DEFAULT NULL COMMENT '購買數量',
`user_id` bigint DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
2.代碼工程
1.工程創建
- 創建Maven工程,添加相關Maven依賴,
<properties>
<java.version>1.8</java.version>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<spring.boot.version>2.5.5</spring.boot.version>
<mybatisplus.boot.starter.version>3.4.0</mybatisplus.boot.starter.version>
<lombok.version>1.18.16</lombok.version>
<sharding-jdbc.version>4.1.1</sharding-jdbc.version>
<junit.version>4.12</junit.version>
<druid.version>1.1.16</druid.version>
<!--跳過單元測試-->
<skipTests>true</skipTests>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring.boot.version}</version>
<scope>test</scope>
</dependency>
<!--mybatis plus和springboot整合-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatisplus.boot.starter.version}</version>
</dependency>
<!-- mysql資料庫 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<!--<scope>provided</scope>-->
</dependency>
<!-- shardingshpere-jdbc-->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>${sharding-jdbc.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<configuration>
<fork>true</fork>
<addResources>true</addResources>
</configuration>
</plugin>
</plugins>
</build>
- 添加資料庫配置文件,根據配置文件可知,配置了兩個資料庫ds0,ds1;
spring.application.name=yb-sharding-jdbc
server.port=8080
logging.level.root=INFO
# 列印執行的資料庫以及語句
spring.shardingsphere.props.sql.show=true
# 數據源 ds0 ds1
spring.shardingsphere.datasource.names=ds0,ds1
# 第一個資料庫
spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://localhost:3306/ybe_shop_order0?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=*****
# 第二個資料庫
spring.shardingsphere.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.ds1.jdbc-url=jdbc:mysql://localhost:3306/ybe_shop_order1?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.shardingsphere.datasource.ds1.username=root
spring.shardingsphere.datasource.ds1.password=*****
2.廣播表介紹和配置實戰
- 指所有的分片數據源中都存在的表,表結構和表中的數據在每個資料庫中均完全一致
- 適用於數據量不大且需要與海量數據的表進行關聯查詢的場景
- 例如:字典表、配置表
- 添加AdConfigDO實體類和添加ProductOrderDOMapper類
//資料庫實體類
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("ad_config")
public class AdConfigDO {
private Long id;
private String configKey;
private String configValue;
private String type;
}
//資料庫實體配置類
public interface AdConfigMapper extends BaseMapper<AdConfigDO> {
}
- 設置ad_config為廣播表,如果需要配置多個用 逗號 (,) 分開;設置id為生成演算法為雪花演算法。配置文件中添加如下代碼,
#配置廣播表
spring.shardingsphere.sharding.broadcast-tables=ad_config
spring.shardingsphere.sharding.tables.ad_config.key-generator.column=id
spring.shardingsphere.sharding.tables.ad_config.key-generator.type=SNOWFLAKE
- 添加測試方法
@Test
public void testSaveAdConfig(){
AdConfigDO adConfigDO = new AdConfigDO();
adConfigDO.setConfigKey("banner");
adConfigDO.setConfigValue("ybe.com");
adConfigDO.setType("ad");
adConfigMapper.insert(adConfigDO);
}
- 執行結果,兩個資料庫的表都進行了更新。如下圖
3.行表達式分片策略 InlineShardingStrategy
- 只支持【單分片鍵】使用Groovy的表達式,提供對SQL語句中的 =和IN 的分片操作支持
- 可以通過簡單的配置使用,無需自定義分片演算法,從而避免繁瑣的Java代碼開發
- 添加ProductOrderDO實體類和添加ProductOrderDOMapper類
//資料庫實體類
@Data
@TableName("product_order")
@EqualsAndHashCode(callSuper = false)
public class ProductOrderDO {
// 不設置Mybatis-plus的主鍵規則,由sharding-jdbc 設置
private Long id;
private String outTradeNo;
private String state;
private Date createTime;
private Double payAmount;
private String nickname;
private Long userId;
}
//資料庫實體配置類
public interface ProductOrderMapper extends BaseMapper<ProductOrderDO> {
}
- 配置文件添加如下代碼,
# 指定product_order表的數據分佈情況,配置數據節點,行表達式標識符使用 ${...} 或 $->{...},但前者與 Spring 本身的文件占位符衝突,所以在 Spring 環境中建議使用 $->{...}
spring.shardingsphere.sharding.tables.product_order.actual-data-nodes=ds$->{0..1}.product_order_$->{0..1}
#id生成策略
spring.shardingsphere.sharding.tables.product_order.key-generator.column=id
spring.shardingsphere.sharding.tables.product_order.key-generator.type=SNOWFLAKE
#work_id 的設置
spring.shardingsphere.sharding.tables.product_order.key-generator.props.worker.id=1
#配置分庫規則
spring.shardingsphere.sharding.tables.product_order.database-strategy.inline.sharding-column=user_id
spring.shardingsphere.sharding.tables.product_order.database-strategy.inline.algorithm-expression=ds$->{user_id % 2}
#配置分表規則
#指定product_order表的分片策略,分片策略包括【分片鍵和分片演算法】
spring.shardingsphere.sharding.tables.product_order.table-strategy.inline.sharding-column=id
spring.shardingsphere.sharding.tables.product_order.table-strategy.inline.algorithm-expression=product_order_$->{id % 2}
由配置文件可知,
設置了product_order為邏輯表,設置了它的真實數據節點為ds$->{0..1}.product_order_$->{0..1},使用了表達式$->{...},它表示實際的物理表為:ds0.product_order_0,ds0.product_order_1,ds1.product_order_0,ds1.product_order_1,總共對應了2個庫的2個物理表。
設置了product_order表的id計算方式為雪花演算法;
設置了product_order表的分庫規則,分庫規則為 user_id % 2;也就是說會根據user_id % 2的結果確定是ds0庫還是ds1庫。
設置了product_order表的分表規則,分表規則為 id % 2;也就是說會根據id % 2的結果確定是product_order_0表還是product_order_1表。
- 添加測試方法
@Test
public void testSaveProductOrder(){
Random random = new Random();
for (int i = 0 ;i < 10 ; i++){
// id是由配置的雪花演算法生成
ProductOrderDO productOrderDO = new ProductOrderDO();
productOrderDO.setCreateTime(new Date());
productOrderDO.setNickname("ybe:"+i);
productOrderDO.setOutTradeNo(UUID.randomUUID().toString().substring(0,32));
productOrderDO.setPayAmount(100.00);
productOrderDO.setState("PAY");
// 隨機生成UserId
productOrderDO.setUserId(Long.valueOf(random.nextInt(50)));
productOrderMapper.insert(productOrderDO);
}
}
- 執行結果根據不同的user_id 和 id ,生成的表記錄插入到了不同的庫和表,如下圖可以看到數據分散在了兩個不同的資料庫,以及不同的表中。
4.標準分片策略StandardShardingStrategy
- 只支持【單分片鍵】,提供PreciseShardingAlgorithm和RangeShardingAlgorithm兩個分片演算法
- PreciseShardingAlgorithm 精準分片 是必選的,用於處理=和IN的分片
- RangeShardingAlgorithm 範圍分片 是可選的,用於處理BETWEEN AND分片
- 如果不配置RangeShardingAlgorithm,如果SQL中用了BETWEEN AND語法,則將按照全庫路由處理,性能下降
- 添加分表配置類CustomTablePreciseShardingAlgorithm
public class CustomTablePreciseShardingAlgorithm implements PreciseShardingAlgorithm<Long> {
/**
* @param collection 數據源集合
* 在分庫時值為所有分片庫的集合 databaseNames
* 分表時為對應分片庫中所有分片表的集合 tablesNames
* @param preciseShardingValue 分片屬性,包括
* logicTableName 為邏輯表,
* columnName 分片健(欄位),
* value 為從 SQL 中解析出的分片健的值
* @return
*/
@Override
public String doSharding(Collection<String> collection, PreciseShardingValue<Long> preciseShardingValue) {
//迴圈遍歷 數據源,根據演算法
for (String databaseName : collection) {
String value = preciseShardingValue.getValue() % collection.size() + "";
//value是0,則進入0庫表,1則進入1庫表
if (databaseName.endsWith(value)) {
return databaseName;
}
}
throw new IllegalArgumentException();
}
}
- 添加分庫配置類CustomDBPreciseShardingAlgorithm
/**
* @param collection 數據源集合
* 在分庫時值為所有分片庫的集合 databaseNames
* 分表時為對應分片庫中所有分片表的集合 tablesNames
* @param preciseShardingValue 分片屬性,包括
* logicTableName 為邏輯表,
* columnName 分片健(欄位),
* value 為從 SQL 中解析出的分片健的值
* @return
*/
@Override
public String doSharding(Collection<String> collection, PreciseShardingValue<Long> preciseShardingValue) {
for (String databaseName : collection) {
String value = preciseShardingValue.getValue() % collection.size() + "";
//value是0,則進入0庫表,1則進入1庫表
if (databaseName.endsWith(value)) {
return databaseName;
}
}
throw new IllegalArgumentException();
}
- 添加分表範圍配置類CustomRangeShardingAlgorithm
public class CustomRangeShardingAlgorithm implements RangeShardingAlgorithm<Long> {
/**
* @param collection 數據源集合
* 在分庫時值為所有分片庫的集合 databaseNames
* 分表時為對應分片庫中所有分片表的集合 tablesNames
* @param rangeShardingValue 分片屬性,包括
* logicTableName 為邏輯表,
* columnName 分片健(欄位),
* value 為從 SQL 中解析出的分片健的值
* @return
*/
@Override
public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Long> rangeShardingValue) {
Set<String> result = new LinkedHashSet<>();
// between 起始值
Long lower = rangeShardingValue.getValueRange().lowerEndpoint();
// between 結束值
Long upper = rangeShardingValue.getValueRange().upperEndpoint();
// 迴圈範圍計算分庫邏輯
for (long i = lower; i <= upper; i++) {
for (String databaseName : collection) {
if (databaseName.endsWith(i % collection.size() + "")) {
result.add(databaseName);
}
}
}
return result;
}
}
- 配置文件添加圖下代碼,
# 分庫分片演算法
spring.shardingsphere.sharding.tables.product_order.database-strategy.standard.sharding-column=user_id
spring.shardingsphere.sharding.tables.product_order.database-strategy.standard.precise-algorithm-class-name=com.ybe.algorithm.CustomDBPreciseShardingAlgorithm
#精準水平分表下,增加一個範圍分片
spring.shardingsphere.sharding.tables.product_order.table-strategy.standard.range-algorithm-class-name=com.ybe.algorithm.CustomRangeShardingAlgorithm
# 分表分片健
spring.shardingsphere.sharding.tables.product_order.table-strategy.standard.sharding-column=id
spring.shardingsphere.sharding.tables.product_order.table-strategy.standard.precise-algorithm-class-name=com.ybe.algorithm.CustomTablePreciseShardingAlgorithm
- 添加測試方法
@Test
public void testRand(){
Random random = new Random();
for (int i = 0 ;i < 10 ; i++){
ProductOrderDO productOrderDO = new ProductOrderDO();
productOrderDO.setCreateTime(new Date());
productOrderDO.setNickname("ybe:"+i);
productOrderDO.setOutTradeNo(UUID.randomUUID().toString().substring(0,32));
productOrderDO.setPayAmount(100.00);
productOrderDO.setState("PAY");
productOrderDO.setUserId(Long.valueOf(random.nextInt(50)));
productOrderMapper.insert(productOrderDO);
}
productOrderMapper.selectList(new QueryWrapper<ProductOrderDO>().between("id",1L,1L));
}
- 執行結果:1.會根據配置的分庫、分表規則進行插入不同的資料庫和表;2.範圍(between)查詢的時候會根據id的範圍值查詢映射的物理表集合。
5.複合分片演算法ComplexShardingStrategy
- 提供對SQL語句中的=, IN和BETWEEN AND的分片操作,支持【多分片鍵】
- 由於多分片鍵之間的關係複雜,Sharding-JDBC並未做過多的封裝
- 而是直接將分片鍵值組合以及分片操作符交於演算法介面,全部由應用開發者實現,提供最大的靈活度
- 添加分表配置類CustomComplexKeysShardingAlgorithm
public class CustomComplexKeysShardingAlgorithm implements ComplexKeysShardingAlgorithm<Long> {
@Override
public Collection<String> doSharding(Collection<String> collection, ComplexKeysShardingValue<Long> complexKeysShardingValue) {
// 得到每個分片健對應的值
Collection<Long> orderIdValues = this.getShardingValue(complexKeysShardingValue, "id");
Collection<Long> userIdValues = this.getShardingValue(complexKeysShardingValue, "user_id");
List<String> shardingSuffix = new ArrayList<>();
// 對兩個分片健取模的方式
for (Long userId : userIdValues) {
for (Long orderId : orderIdValues) {
String suffix = userId % 2 + "_" + orderId % 2;
for (String databaseName : collection) {
if (databaseName.endsWith(suffix)) {
shardingSuffix.add(databaseName);
}
}
}
}
return shardingSuffix;
}
/**
* shardingValue 分片屬性,包括
* logicTableName 為邏輯表,
* columnNameAndShardingValuesMap 存儲多個分片健 包括key-value
* key:分片key,id和user_id
* value:分片value,66和99
*
* @return shardingValues 集合
*/
private Collection<Long> getShardingValue(ComplexKeysShardingValue<Long> shardingValues, final String key) {
Collection<Long> valueSet = new ArrayList<>();
Map<String, Collection<Long>> columnNameAndShardingValuesMap = shardingValues.getColumnNameAndShardingValuesMap();
if (columnNameAndShardingValuesMap.containsKey(key)) {
valueSet.addAll(columnNameAndShardingValuesMap.get(key));
}
return valueSet;
}
- 配置文件添加如下代碼,多個列的類型必須一樣。
# 複合分片演算法,order_id,user_id 同時作為分片健
spring.shardingsphere.sharding.tables.product_order.table-strategy.complex.sharding-columns=user_id,id
spring.shardingsphere.sharding.tables.product_order.table-strategy.complex.algorithm-class-name=com.ybe.algorithm.CustomComplexKeysShardingAlgorithm
- 添加測試方法
@Test
public void testComplex(){
productOrderMapper.selectList(new QueryWrapper<ProductOrderDO>().eq("id",66L).eq("user_id",99L));
}
- 執行結果:1.會根據配置的複合分片演算法去查找相關的物理表。
6.Hint分片演算法HintShardingStrategy
- 這種分片策略無需配置文件進行配置分片健,分片健值也不再從 SQL中解析,外部手動指定分片健或分片庫,讓 SQL在指定的分庫、分表中執行
- 通過Hint代碼指定的方式而非SQL解析的方式分片的策略
- Hint策略會繞過SQL解析的,對於這些比較複雜的需要分片的查詢,Hint分片策略性能可能會更好
- 可以指定sql去某個庫某個表進行執行
- 添加分表配置類CustomTableHintShardingAlgorithm
public class CustomTableHintShardingAlgorithm implements HintShardingAlgorithm<Long> {
@Override
public Collection<String> doSharding(Collection<String> collection, HintShardingValue<Long> hintShardingValue) {
Collection<String> result = new ArrayList<>();
for (String tableName : collection) {
for (Long shardingValue : hintShardingValue.getValues()) {
if (tableName.endsWith(String.valueOf(shardingValue % collection.size()))) {
result.add(tableName);
}
}
}
return result;
}
}
- 添加分庫配置類CustomDBHintShardingAlgorithm
public class CustomDBHintShardingAlgorithm implements HintShardingAlgorithm<Long>
{
@Override
public Collection<String> doSharding(Collection<String> collection, HintShardingValue<Long> hintShardingValue) {
Collection<String> result = new ArrayList<>();
for (String dbName : collection) {
for (Long shardingValue : hintShardingValue.getValues()) {
if (dbName.endsWith(String.valueOf(shardingValue % collection.size()))) {
result.add(dbName);
}
}
}
return result;
}
}
- 配置文件添加如下代碼
# Hint分片演算法
spring.shardingsphere.sharding.tables.product_order.table-strategy.hint.algorithm-class-name=com.ybe.algorithm.CustomTableHintShardingAlgorithm
spring.shardingsphere.sharding.tables.product_order.database-strategy.hint.algorithm-class-name=com.ybe.algorithm.CustomDBHintShardingAlgorithm
- 添加測試方法
@Test
public void testHint(){
// 清除掉歷史的規則
HintManager.clear();
//Hint分片策略必須要使用 HintManager工具類
HintManager hintManager = HintManager.getInstance();
// 設置庫的分片健,value用於庫分片取模,
hintManager.addDatabaseShardingValue("product_order",4L);
// 設置表的分片健,value用於表分片取模,
hintManager.addTableShardingValue("product_order", 5L);
//對應的value只做查詢,不做sql解析
productOrderMapper.selectList(new QueryWrapper<ProductOrderDO>().eq("id", 66L));
}
- 執行結果:1.不會解析Sql中的分片鍵,會把hintManager配置的值作為分片鍵,在CustomTableHintShardingAlgorithm分片演算法的中使用。
7.綁定表介紹和配置實戰
- 指分片規則一致的主表和子表
- 比如product_order表和product_order_item表,均按照order_id分片,則此兩張表互為綁定表關係
- 綁定表之間的多表關聯查詢不會出現笛卡爾積關聯,關聯查詢效率將大大提升
- 添加ProductOrderItemDO實體類和添加ProductOrderDOMapper類
//資料庫實體類
@Data
@TableName("product_order_item")
@EqualsAndHashCode(callSuper = false)
public class ProductOrderItemDO {
private Long id;
private Long productOrderId;
private Long productId;
private String productName;
private Integer buyNum;
private Long userId;
}
//資料庫實體配置類
public interface ProductOrderItemMapper extends BaseMapper<ProductOrderItemDO> {
}
- 添加配置文件,設置product_order_item的分表邏輯,設置product_order和product_order_item為廣播表,如果需要配置多個需要配置多行,binding-tables是個數組。
spring.shardingsphere.sharding.tables.product_order_item.actual-data-nodes=ds$->{0..1}.product_order_item_$->{0..1}
# 指定product_order表的分片策略,分片策略包括【分片鍵和分片演算法】
spring.shardingsphere.sharding.tables.product_order_item.table-strategy.inline.sharding-column=product_order_id
spring.shardingsphere.sharding.tables.product_order_item.table-strategy.inline.algorithm-expression=product_order_item_$->{product_order_id % 2}
#配置綁定表
spring.shardingsphere.sharding.binding-tables[0]=product_order,product_order_item
- 添加測試方法
@Test
public void testBinding(){
List<Object> objects = productOrderMapper.listProductOrderDetail();
System.out.println(objects);
}
-
執行結果:
- 添加綁定表配置之前,可以看到查詢的sql語句,主表和子表是笛卡爾積的關聯關係。如下圖,
- 添加綁定表配置之後,可以看到查詢的sql語句,主表和子表是一一對應的。如下圖,