一、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 库)时,哈希取模的路由规则会发生变化,导致以下问题

  1. 新数据路由错误:按新规则写入的数据可能分配到新库(如 db2),而历史数据按旧规则分布在 db0 和 db1
  2. 历史数据不可见:查询时根据新规则计算的路由结果可能指向空库,导致无法检索到历史数据。

解决方案
需通过 数据迁移 + 双路由兼容 实现平滑扩容,确保新旧数据均能被正确访问。以下是详细步骤:

  1. 迁移工具链
    使用 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"]  // 根据新规则写入目标表
        }
      }
    }]
  }
}

  1. 迁移逻辑步骤:
    全量迁移:将历史数据按新路由规则(如 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  # 旧分库数量(用于兼容查询)

在这里插入图片描述

更多推荐