Nacos 实现分库分表配置动态加载和问题
·
一、Nacos 配置示例
spring:
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
file-extension: yaml
shared-configs:
- dataId: db-router-config.yaml
group: DEFAULT_GROUP
refresh: true
二、Maven 依赖
<!-- 业务项目的 pom.xml -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2022.0.0.0</version>
</dependency>
三、核心代码实现
1 Nacos 配置监听与动态加载
@Component
public class NacosConfigListener {
@Autowired
private DynamicDataSource dynamicDataSource;
@Autowired
private DBRouterConfig dbRouterConfig;
@NacosConfigListener(dataId = "db-router-config.yaml", groupId = "DEFAULT_GROUP")
public void onConfigUpdate(String newConfig) throws Exception {
// 1. 解析新配置
Map<String, Object> configMap = parseYamlConfig(newConfig);
// 2. 更新数据源
refreshDataSources(configMap);
// 3. 更新路由规则
refreshRouterConfig(configMap);
}
private Map<String, Object> parseYamlConfig(String yaml) {
Yaml yamlParser = new Yaml();
return yamlParser.load(yaml);
}
private void refreshDataSources(Map<String, Object> config) {
Map<String, Object> dsConfig = (Map<String, Object>) config.get("mini-db-router.jdbc.datasource");
// 重建数据源 Map
Map<Object, DataSource> newDataSources = new HashMap<>();
List<String> dsList = Arrays.asList(((String) dsConfig.get("list")).split(","));
dsList.forEach(dsName -> {
Map<String, Object> props = (Map<String, Object>) dsConfig.get(dsName);
DataSource ds = DataSourceBuilder.create()
.url((String) props.get("url"))
.username((String) props.get("username"))
.password((String) props.get("password"))
.build();
newDataSources.put(dsName, ds);
});
// 原子切换数据源
dynamicDataSource.setTargetDataSources(newDataSources);
dynamicDataSource.afterPropertiesSet();
}
private void refreshRouterConfig(Map<String, Object> config) {
Map<String, Object> routerConfig = (Map<String, Object>) config.get("mini-db-router.jdbc.datasource");
dbRouterConfig.setDbCount((Integer) routerConfig.get("dbCount"));
dbRouterConfig.setTbCount((Integer) routerConfig.get("tbCount"));
dbRouterConfig.setRouterKey((String) routerConfig.get("routerKey"));
}
}
2. 动态数据源扩展
需将组件中的 DynamicDataSource 改为可动态更新:
public class DynamicDataSource extends AbstractRoutingDataSource {
// 添加此方法允许外部更新数据源
public synchronized void setTargetDataSources(Map<Object, DataSource> targetDataSources) {
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet(); // 必须调用此方法刷新
}
}
3 配置 Bean 作用域调整
修改组件中的 DBRouterConfig 为原型作用域,允许动态更新:
@Configuration
public class DataSourceAutoConfig {
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // 改为原型模式
public DBRouterConfig dbRouterConfig() {
return new DBRouterConfig(...);
}
}
四:当通过 Nacos 动态增加分库数量(例如从 2 库扩到 3 库)时,哈希取模的路由规则会发生变化,导致以下问题
- 新数据路由错误:按新规则写入的数据可能分配到新库(如 db2),而历史数据按旧规则分布在 db0 和 db1
- 历史数据不可见:查询时根据新规则计算的路由结果可能指向空库,导致无法检索到历史数据。
解决方案
需通过 数据迁移 + 双路由兼容 实现平滑扩容,确保新旧数据均能被正确访问。以下是详细步骤:
- 迁移工具链
使用 DataX 或 Spark 等工具,根据新路由规则将历史数据迁移到新库:
# DataX 任务配置示例(job.json)
{
"job": {
"content": [{
"reader": {
"name": "mysqlreader",
"parameter": {
"username": "root",
"password": "123456",
"connection": [{
"table": ["user"],
"jdbcUrl": ["jdbc:mysql://db0:3306/db_old"]
}]
}
},
"writer": {
"name": "mysqlwriter",
"parameter": {
"username": "root",
"password": "123456",
"connection": [{
"jdbcUrl": "jdbc:mysql://db0:3306/db_new",
"table": ["user"]
}],
"preSql": ["TRUNCATE TABLE user"] // 根据新规则写入目标表
}
}
}]
}
}
- 迁移逻辑步骤:
全量迁移:将历史数据按新路由规则(如 userId % 3)重新分布到新库。
增量同步:迁移期间通过 Binlog 监听 同步增量数据,确保数据一致性.
3 双路由兼容(查询阶段)
3.1 版本化路由规则
//在路由策略中引入 版本标识,动态区分新旧规则:
public class DBRouterStrategyHashCode implements IDBRouterStrategy {
private String version; // 路由规则版本(如 v1、v2)
public void doRouter(String dbKeyAttr) {
if ("v1".equals(version)) {
// 旧规则:userId % 2
int dbIdx = userId.hashCode() % 2;
} else if ("v2".equals(version)) {
// 新规则:userId % 3
int dbIdx = userId.hashCode() % 3;
}
// 设置到 ThreadLocal...
}
}
3.2 查询时多版本路由
//在查询接口中,根据数据时间范围或版本标识,决定使用新旧规则:
public User getUser(String userId) {
// 1. 先按新规则查询(v2)
User user = queryByVersion(userId, "v2");
if (user == null) {
// 2. 未找到则按旧规则查询(v1)
user = queryByVersion(userId, "v1");
}
return user;
}
3 Nacos 配置模板
mini-db-router:
rules:
userTable:
version: v2 # 新规则版本
dbCount: 3 # 新分库数量
historyDbCount: 2 # 旧分库数量(用于兼容查询)

更多推荐



所有评论(0)