分库分表自定义策略¶
首先捋一下整体的分库分表的思路:
针对link_group分组的数据,根据用户的account_no字段进行分库,前面有提到过用户的account_no字段是一串数字,所以根据其对2取余得到的结果进行分库
针对short_link短链的数据,根据短链码code进行分库分表,在进行短链码生成的时候可以在其前后分别添加一位,前面一位用于标识库位,后面一位用于标识表位。
至于分库分表的逻辑则是在DataSourceConfig数据源配置中进行配置
首先创建两个数据源配置
private DataSource createDataSource0() {
=======
# 集成sharding-jdbc
这里集成sharding-jdbc主要是为了做分库分表,记录一下集成的过程和坑
这里用到的版本号是4.1.1,配置内容可以直接参考官网:https://shardingsphere.apache.org/document/4.1.1/cn/quick-start/。因为他不同版本间的配置格式差距还挺大的,建议还是直接使用新版本的。
```java
<sharding-jdbc.version>4.1.1</sharding-jdbc.version>
在最初的时候使用官网推荐的进行yml文件配置,启动时遇到了一个问题,就是无法找到DataSourceConfig配置,试了好久还是没能解决,索性直接手写DataSourceConfig了,其实直接那下面的配置文件用就直接可以通过sharding-jdbc分库分表了,就是多数据源的时候需要创建多个createDataSource().
package org.example.config;
import org.apache.shardingsphere.api.config.sharding.KeyGeneratorConfiguration;
import org.apache.shardingsphere.api.config.sharding.ShardingRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.TableRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.strategy.InlineShardingStrategyConfiguration;
import org.apache.shardingsphere.shardingjdbc.api.ShardingDataSourceFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
@Configuration
public class DataSourceConfig {
@Value("${datasourceConfig.url1}")
private String dbUrl1;
@Value("${datasourceConfig.url2}")
private String dbUrl2;
@Value("${datasourceConfig.username}")
private String dbUser;
@Value("${datasourceConfig.password}")
private String dbPassword;
private final SnowFlakeWordIdConfig snowFlakeWordIdConfig;
@Autowired
public DataSourceConfig(SnowFlakeWordIdConfig workerIdConfig) {
this.snowFlakeWordIdConfig = workerIdConfig;
}
@Bean
public DataSource dataSource() throws SQLException {
// 1. 配置数据源
Map<String, DataSource> dataSourceMap = new HashMap<>();
// 通过put,可以配置多个数据源
dataSourceMap.put("ds0", createDataSource0());
dataSourceMap.put("ds1", createDataSource1());
// 2. 配置分片规则
ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
shardingRuleConfig.getTableRuleConfigs().add(getTableRuleConfiguration());
// 添加多个表的分片规则,可以配置多个方法
// shardingRuleConfig.getTableRuleConfigs().add(getTableRuleConfiguration1());
// shardingRuleConfig.getTableRuleConfigs().add(getTableRuleConfiguration2());
Properties properties = new Properties();
// 自定义方法生成workId
properties.setProperty("worker.id", snowFlakeWordIdConfig.getWorkerId()); // 设置工作节点ID
// 配置主键生成规则
KeyGeneratorConfiguration keyGeneratorConfiguration = new KeyGeneratorConfiguration(
"SNOWFLAKE", // 内置雪花算法类型生成id
"id", // 主键字段名
properties // 配置属性
);
// 直接设置配置对象,而非生成器实例
shardingRuleConfig.setDefaultKeyGeneratorConfig(keyGeneratorConfiguration);
// 3. 创建 ShardingSphere 数据源
return ShardingDataSourceFactory.createDataSource(
dataSourceMap,
shardingRuleConfig,
new Properties()
);
}
private TableRuleConfiguration getTableRuleConfiguration() {
// 使用正确的分表表达式
TableRuleConfiguration result = new TableRuleConfiguration(
"link_group",
"ds$->{0..1}.link_group"
);
// 设置分库策略,根据account_no字段进行分库
result.setDatabaseShardingStrategyConfig(
new InlineShardingStrategyConfiguration("account_no", "ds$->{account_no % 2}")
);
return result;
}
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
private DataSource createDataSource0() {
>>>>>>> origin/main
com.zaxxer.hikari.HikariDataSource ds = new com.zaxxer.hikari.HikariDataSource();
ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
ds.setJdbcUrl(dbUrl1);
ds.setUsername(dbUser);
ds.setPassword(dbPassword);
// 添加连接池优化配置 [8](@ref)
ds.setMinimumIdle(5); // 最小空闲连接数
ds.setMaximumPoolSize(20); // 最大连接数
ds.setIdleTimeout(30000); // 空闲连接超时时间(毫秒)
ds.setMaxLifetime(1800000); // 连接最大生命周期(毫秒)
ds.setConnectionTimeout(30000); // 连接获取超时时间(毫秒)
ds.setLeakDetectionThreshold(60000); // 连接泄漏检测阈值(毫秒)
ds.setConnectionTestQuery("SELECT 1"); // 连接验证查询
return ds;
}
private DataSource createDataSource1() {
com.zaxxer.hikari.HikariDataSource ds = new com.zaxxer.hikari.HikariDataSource();
ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
ds.setJdbcUrl(dbUrl2);
ds.setUsername(dbUser);
ds.setPassword(dbPassword);
// 添加连接池优化配置 [8](@ref)
ds.setMinimumIdle(5); // 最小空闲连接数
ds.setMaximumPoolSize(20); // 最大连接数
ds.setIdleTimeout(30000); // 空闲连接超时时间(毫秒)
ds.setMaxLifetime(1800000); // 连接最大生命周期(毫秒)
ds.setConnectionTimeout(30000); // 连接获取超时时间(毫秒)
ds.setLeakDetectionThreshold(60000); // 连接泄漏检测阈值(毫秒)
ds.setConnectionTestQuery("SELECT 1"); // 连接验证查询
return ds;
}
<<<<<<< HEAD
整体的分库分表逻辑配置
// 1. 配置数据源
Map<String, DataSource> dataSourceMap = new HashMap<>();
// 通过put,可以配置多个数据源
dataSourceMap.put("ds0", createDataSource0());
dataSourceMap.put("ds1", createDataSource1());
// 2. 配置分片规则
ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
shardingRuleConfig.getTableRuleConfigs().add(getLinkGroupRuleConfiguration());
shardingRuleConfig.getTableRuleConfigs().add(getShortLinkRuleConfiguration());
// 添加多个表的分片规则,可以配置多个方法
// shardingRuleConfig.getTableRuleConfigs().add(getTableRuleConfiguration2());
Properties properties = new Properties();
// 自定义方法生成workId
properties.setProperty("worker.id", snowFlakeWordIdConfig.getWorkerId()); // 设置工作节点ID
// 配置主键生成规则
KeyGeneratorConfiguration keyGeneratorConfiguration = new KeyGeneratorConfiguration(
"SNOWFLAKE", // 内置雪花算法类型生成id
"id", // 主键字段名
properties // 配置属性
);
// 直接设置配置对象,而非生成器实例
shardingRuleConfig.setDefaultKeyGeneratorConfig(keyGeneratorConfiguration);
// 3. 创建 ShardingSphere 数据源
return ShardingDataSourceFactory.createDataSource(
dataSourceMap,
shardingRuleConfig,
new Properties()
);
针对LinkGroup和ShortLink的分库分表规则
private TableRuleConfiguration getLinkGroupRuleConfiguration() {
// 使用正确的分表表达式
TableRuleConfiguration result = new TableRuleConfiguration(
"link_group",
"ds$->{0..1}.link_group"
);
// 设置分库策略,根据account_no字段进行分库
result.setDatabaseShardingStrategyConfig(
new InlineShardingStrategyConfiguration("account_no", "ds$->{account_no % 2}")
);
return result;
}
private TableRuleConfiguration getShortLinkRuleConfiguration() {
StandardShardingStrategyConfiguration databaseShardingStrategy =
new StandardShardingStrategyConfiguration(
"code",
new CustomDBPreciseShardingAlgorithm() // 实例化您的自定义分库算法
);
StandardShardingStrategyConfiguration tableShardingStrategy =
new StandardShardingStrategyConfiguration(
"code",
new CustomTablePreciseShardingAlgorithm() // 实例化您的自定义分库算法
);
// 关键修复:使用正确的分表表达式
TableRuleConfiguration result = new TableRuleConfiguration(
"short_link",
"ds$->{0..1}.short_link_$->{0..1}"
);
// 设置分库策略,根据短链码code字段进行分库
result.setDatabaseShardingStrategyConfig(
databaseShardingStrategy
);
// 设置分表策略,根据短链码code字段进行分表
result.setTableShardingStrategyConfig(
tableShardingStrategy
);
return result;
}
// ShortLink的自定义分片规则
// 分库规则
public class CustomDBPreciseShardingAlgorithm implements PreciseShardingAlgorithm<String> {
/**
*
* @param collection 数据源集合
* 在分库时值为所有分片库的集合 databaseNames
* 分表时为对应分库中所有分片表的集合 tablesNames
* 分库场景:值为所有分片库的名称集合(如 ["ds0", "ds1", "ds2"])。
* 分表场景:值为当前分片库中所有分片表的名称集合(如 ["t_order_0", "t_order_1"])。
* @param preciseShardingValue 分片的属性
* logicTableName(逻辑表名,如 t_order)
* columnName(分片键字段名,如 short_code)
* value(分片键的具体值,如 "A123456")
* @return
*/
@Override
public String doSharding(Collection<String> collection, PreciseShardingValue<String> preciseShardingValue) {
// 获取短链码第一位,即库位
String codePrefix = preciseShardingValue.getValue().substring(0,1);
for (String targetName:collection) {
//获取库名的最后一位
String targetNameSuffix = targetName.substring(targetName.length()-1);
if (codePrefix.equals(targetNameSuffix)){
return targetName;
}
}
return null;
}
}
// 分表规则
public class CustomTablePreciseShardingAlgorithm implements PreciseShardingAlgorithm<String> {
/**
*
* @param availableTargetNames 数据源集合
* 在分库时值为所有分片库的集合 databaseNames
* 分表时为对应分库中所有分片表的集合 tablesNames
* 分库场景:值为所有分片库的名称集合(如 ["ds0", "ds1", "ds2"])。
* 分表场景:值为当前分片库中所有分片表的名称集合(如 ["t_order_0", "t_order_1"])。
* @param shardingValue 分片的属性
* logicTableName(逻辑表名,如 t_order)
* columnName(分片键字段名,如 short_code)
* value(分片键的具体值,如 "A123456")
* @return
*/
@Override
public String doSharding(Collection<String> availableTargetNames,
PreciseShardingValue<String> shardingValue) {
// 1. 获取分片键值(短链码)
String code = shardingValue.getValue();
// 2. 提取表后缀(如取最后一位)
String tableSuffix = code.substring(code.length() - 1); // 示例:code="ABC123" → "3"
// 3. 构建目标表名基础(逻辑表名_后缀)
String targetBaseName = shardingValue.getLogicTableName() + "_" + tableSuffix;
// 4. 遍历所有可用物理表名,匹配后缀
for (String physicalTable : availableTargetNames) {
// physicalTable 格式:ds0.short_link_1
if (physicalTable.endsWith(targetBaseName)) { // 后缀匹配
return physicalTable; // 返回完整物理表名
}
}
throw new IllegalStateException("无法路由到分片表:" + code);
}
}
在进行分库分表后,会有几个问题;一是库表扩容后,数据分布不均匀的问题、二是分库分表后进行数据查询时如果进行全库表查询的话,会很耗费性能
针对问题一:最直接的方法是增加新建库表的权重比,可以通过负载均衡端实现,也可自定义算法实现。我这边是使用的第二个方法
在进行库表名拼接的时候是随机返回标识,0/1,根据返回的标识进行库表名的拼接。增加权重比的话,直接在list中新增对应的0/1,增加对应表的权重比
private static final List<String> DBPrefixList = new ArrayList<>();
static {
DBPrefixList.add("0");
DBPrefixList.add("1");
}
private static Random random = new Random();
public static String getRandomPrefix(){
int index = random.nextInt(DBPrefixList.size());
return DBPrefixList.get(index);
}
private static final List<String> TablePrefixList = new ArrayList<>();
static {
TablePrefixList.add("0");
TablePrefixList.add("1");
}
private static Random random = new Random();
public static String getRandomPrefix(){
int index = random.nextInt(TablePrefixList.size());
return TablePrefixList.get(index);
}
针对问题二:可以引入nosql,比如es,对数据全量冗余存储。在进行查询时,不通过数据库,直接在es中进行查询。也可进行双向冗余,比如订单系统,针对商家和客户两个维度将订单在两个地方都存储一份。在查询时,既可根据客户主键查询订单,也可通过商户逐渐查询订单,通过空间换取查询时的高效性能。
======= } ```
origin/main