Springboot 整合ShardingSphere实现分库分表
简介
分库分表是一种将数据拆分成数据库或表中的策略,例如按照时间的月份创建新的表,将数据隔离开,用于提高查询效率,我们可以使用 Apache 提供的服务 ShardingSphere,用于简化数据库分片、读写分离、分布式事务等复杂场景的管理。
使用步骤一:引入相关依赖
Apache ShardingSphere分为两种模块一种是JDBC一种是proxy,应用于不同的场景和语言,在Java中,我们选择JDBC版本即可,他仅支持Java
<!-- 分库分表 -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.2.0</version>
</dependency>
步骤二:进行相关配置
我们在yaml中可以对shardingSphere进行相对应的配置
spring:
# 使用 shardingSphere 空间图片分表
shardingsphere:
datasource:
names: zzx_picture # 数据库名称
zzx_picture: # 数据源名称
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://${picture.mysql.host}:${picture.mysql.port}/${picture.mysql.database}
username: ${picture.mysql.username}
password: ${picture.mysql.password}
rules:
sharding:
tables:
picture: # 配置 picture 表的分片规则
actual-data-nodes: zzx_picture.picture # 动态分表
table-strategy:
standard:
sharding-column: spaceId # 分表字段
sharding-algorithm-name: picture_sharding_algorithm # 使用自定义分片算法
sharding-algorithms:
picture_sharding_algorithm:
type: CLASS_BASED
props:
strategy: standard # 标准分片
algorithmClassName: com.zzx.zzxpicturebackend.manager.sharding.PictureShardingAlgorithm # 自定义分片算法
props:
sql-show: true # 显示分片后的实际 SQL 语句,用于调试
mybatis-plus:
configuration:
map-underscore-to-camel-case: false # MyBatis 下划线转驼峰
# 仅在开发环境开启日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
# 启用 SQL 运行器
enable-sql-runner: true
db-config:
logic-delete-field: is_delete # 全局逻辑删除
logic-delete-value: 1 # 逻辑删除值(默认为1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为0)
mapper-locations: classpath:/mapper/**.xml
我们在分片规则 actual-data-nodes配置中只写了基本数据表,因为我们业务需求是创建动态表,所以这里以动态分表为例
这里的配置需要改成自己的数据库的相关配置和MybatisPlus的相关配置
这里的配置需要注意的是:
actual-data-nodes 一般指定的你分表的范围,比如 ds0.table_${0..9999},也可以直接写成ds0.table1,ds0.table2
我们还需要指定分表字段,比如spaceId
需要配置指定一下分片算法类,这个分片算法类主要是需要实现StandardShardingAlgorithm接口,编写doSharding方法,泛型为你分表字段的类型,比如日期就是Data
步骤三:书写相关类
我们需要写两个类,一个类为自己实现分表算法类,一个为动态分表管理相关类
分表算法类:
package com.zzx.zzxpicturebackend.manager.sharding;
import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.RangeShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Properties;
/**
* 图片分片算法
*/
public class PictureShardingAlgorithm implements StandardShardingAlgorithm<Long> {
/**
*
* @param collection 所有分表名 是一个固定值,由yaml中 actual-data-nodes 指定
* @param preciseShardingValue 分片键值
* @return 分表名
*/
@Override
public String doSharding(Collection<String> collection, PreciseShardingValue<Long> preciseShardingValue) {
Long spaceId = preciseShardingValue.getValue();
String logicTableName = preciseShardingValue.getLogicTableName();
// spaceId 为 null 表示查询所有图片
if (spaceId == null || spaceId == 0) {
return logicTableName;
}
// 根据 spaceId 动态生成分表名
String realTableName = "picture_" + spaceId;
if (collection.contains(realTableName)) {
// 找到匹配的目标表
return realTableName;
}else{
// 未找到匹配的目标表,返回逻辑表
return logicTableName;
}
}
@Override
public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Long> rangeShardingValue) {
return new ArrayList<>();
}
@Override
public Properties getProps() {
return null;
}
@Override
public void init(Properties properties) {
}
}
如果只实现分表算法类,还不能完成动态分表,
因为 doSharding方法中的 collection 字段是在yaml中 actual-data-nodes直接确定的,所以我们需要自己维护分表列表,并且更新在到 ShardingSphere 中 actual-data-nodes配置中
我们可以写一个动态分表管理类,用于首次加载项目是需要更新分表配置,当项目读取完yaml配置后,然后再把读取到的配置给修改了
最后在这个管理类中创建动态分表的方法,通过拼接SQL 的方式创建出和 picture 表结构一样的分表
因为框架不支持动态维护分表,所以我们需要自己写一个分表管理器,自己维护分表,并且在项目加载完配置文件后动态更新到加载后的配置中
package com.zzx.zzxpicturebackend.manager.sharding;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.toolkit.SqlRunner;
import com.zzx.zzxpicturebackend.constant.ShardingSphereConstant;
import com.zzx.zzxpicturebackend.exception.BusinessException;
import com.zzx.zzxpicturebackend.exception.ErrorCode;
import com.zzx.zzxpicturebackend.model.enums.SpaceLevelEnum;
import com.zzx.zzxpicturebackend.model.enums.SpaceTypeEnum;
import com.zzx.zzxpicturebackend.model.po.Space;
import com.zzx.zzxpicturebackend.service.SpaceService;
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.driver.jdbc.core.connection.ShardingSphereConnection;
import org.apache.shardingsphere.infra.config.rule.RuleConfiguration;
import org.apache.shardingsphere.infra.metadata.database.rule.ShardingSphereRuleMetaData;
import org.apache.shardingsphere.mode.manager.ContextManager;
import org.apache.shardingsphere.sharding.api.config.ShardingRuleConfiguration;
import org.apache.shardingsphere.sharding.api.config.rule.ShardingTableRuleConfiguration;
import org.apache.shardingsphere.sharding.route.strategy.ShardingStrategy;
import org.apache.shardingsphere.sharding.rule.ShardingRule;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 动态分表管理
* 首次加载项目时需要更新分表配置
*/
@Component
@Slf4j
public class DynamicShardingManager {
@Resource
private DataSource dataSource;
@Resource
private SpaceService spaceService;
/**
* 初始化动态分表
*/
@PostConstruct // 当这个bean 创建成功之后,会执行这个方法
public void initialize() {
log.info("初始化动态分表配置");
updateShardingTableNodes();
}
/**
* 更新 ShardingSphere 的 actual-data-nodes 动态表名配置
*/
private void updateShardingTableNodes() {
// 现获取所有的表名
Set<String> tableNames = fetchAllTableNames();
// zzx_picture.picture_53613631563,zzx_picture.picture_436434847934,zzx_picture.picture_48348934873934,
String newActualDataNodes = tableNames.stream()
.map(tableName -> ShardingSphereConstant.MY_DATABASES_NAME + "." + tableName) // 将表名转换为逻辑表名
.collect(Collectors.joining(","));
log.info("动态分表 actual-data-nodes 配置为:{}", newActualDataNodes);
ContextManager contextManager = getContextManager();
// 获取 ShardingSphere 的规则元数据
ShardingSphereRuleMetaData ruleMetaData = contextManager.getMetaDataContexts()
.getMetaData()
.getDatabases()
.get(ShardingSphereConstant.DATABASES_NAME)
.getRuleMetaData();
Optional<ShardingRule> shardingRule = ruleMetaData.findSingleRule(ShardingRule.class);
// 如果存在,则更新
if (shardingRule.isPresent()) {
ShardingRuleConfiguration ruleConfig = (ShardingRuleConfiguration) shardingRule.get().getConfiguration();
List<ShardingTableRuleConfiguration> updateRules = ruleConfig.getTables()
.stream()
.map(oldTableRule -> {
if (ShardingSphereConstant.LOGIC_TABLE_NAME.equals(oldTableRule.getLogicTable())) {
// 设置新的表配置
ShardingTableRuleConfiguration newTableRuleConfig = new ShardingTableRuleConfiguration(ShardingSphereConstant.LOGIC_TABLE_NAME, newActualDataNodes);
newTableRuleConfig.setDatabaseShardingStrategy(oldTableRule.getDatabaseShardingStrategy());
newTableRuleConfig.setTableShardingStrategy(oldTableRule.getTableShardingStrategy());
newTableRuleConfig.setKeyGenerateStrategy(oldTableRule.getKeyGenerateStrategy());
newTableRuleConfig.setAuditStrategy(oldTableRule.getAuditStrategy());
return newTableRuleConfig;
}
return oldTableRule;
})
.collect(Collectors.toList());
// 更新配置
ruleConfig.setTables(updateRules);
// 更新规则
contextManager.alterRuleConfiguration(ShardingSphereConstant.DATABASES_NAME, Collections.singleton(ruleConfig));
// 重新加载数据库
contextManager.reloadDatabase(ShardingSphereConstant.DATABASES_NAME);
log.info("动态分表配置更新成功");
} else {
log.error("未找到 ShardingSphere 的分片规则配置,动态分表更新失败");
}
}
/**
* 动态创建一张分表
*
* @param space 团队空间
*/
public void createSpacePictureTable(Space space) {
// 仅为旗舰版团队空间创建分表
if (space.getSpaceType().equals(SpaceTypeEnum.TEAM.getValue()) && space.getSpaceLevel() == SpaceLevelEnum.FLAGSHIP.getValue()) {
Long spaceId = space.getId();
String tableName = ShardingSphereConstant.LOGIC_TABLE_NAME + "_" + spaceId;
// 创建新表
String createTableSql = "CREATE TABLE " + tableName + " LIKE " + ShardingSphereConstant.LOGIC_TABLE_NAME;
try {
SqlRunner.db().update(createTableSql);
// 更新分表
updateShardingTableNodes();
} catch (Exception e) {
log.error("创建分表失败,空间 id = {}", spaceId);
e.printStackTrace();
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "创建失败,空间 id = " + spaceId);
}
}
}
/**
* 获取 ShardingSphere 上下文管理器
*/
private ContextManager getContextManager() {
try (ShardingSphereConnection connection = dataSource.getConnection().unwrap(ShardingSphereConnection.class)) {
return connection.getContextManager();
} catch (SQLException e) {
throw new RuntimeException("获取 ShardingSphere 上下文管理器失败", e);
}
}
/**
* 获取所有的表名,包括初始表 picture 和 分表 picture_{spaceId}
*
* @return 所有的表名
*/
private Set<String> fetchAllTableNames() {
// 查询对旗舰版的团队空间分表
LambdaQueryWrapper<Space> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Space::getSpaceType, SpaceTypeEnum.TEAM.getValue());
// 为了方便测试,目前只对团队空间进行动态分表,实际上线可取消注释
queryWrapper.eq(Space::getSpaceLevel, SpaceLevelEnum.FLAGSHIP.getValue());
Set<Long> spaceIds = spaceService.list(queryWrapper).stream()
.map(Space::getId)
.collect(Collectors.toSet());
// 获取所有的表名
Set<String> tableNames = spaceIds.stream()
.map(spaceId -> ShardingSphereConstant.LOGIC_TABLE_NAME + "_" + spaceId)
.collect(Collectors.toSet());
// 添加初始表
tableNames.add(ShardingSphereConstant.LOGIC_TABLE_NAME);
return tableNames;
}
}
为了我们的业务便利性,可以在这个管理类中编写创建动态分表的方法,通过拼接SQL 的方式创建出和 picture 表结构一样的分表,在相关的业务代码中执行该方法
至此我们的业务就完成了,在重新梳理一下,我们在yaml进行了相关的配置,然后自己管理创建分表的方法,然后在项目启动时或者执行相关业务通过更新配置文件,实现了动态分表的操作
相关的参考文档:
https://blog.csdn.net/weixin_60523038/article/details/145650882
更多推荐


所有评论(0)