Sharding-jdbc 数据分片动态注册数据源的另类实现


 背景

 官方给出注册中心默认的实现方式是引入zookeeper, 用户也可以利用SPI的方式,继承RegisterCenter接口实现自定义的注册中心。因为在项目中不想引入zookeeper 这个三方组件,于是尝试以下两种方式。

1、自定义注册中心LocalRegisterCenter 实现RegisterCenter接口


@Slf4j
public class LocalRegistryCenter implements RegistryCenter {
}

  配置SPI:

在/META-INF/services/org.apache.shardingsphere.orchestration.reg.api.RegistryCenter里配置 com.**.**.provider.db.sharding.spi.LocalRegistryCenter

  在db-sharding(数据源分片)时,根据分片键值获取待注册的数据源连接信息, 使用 LocalRegisterCenter.warch 方法中的DataChangedEventListener  事件机制(内部原理基于event-bus机制),将新数据源注册到ShardingDataSource 的数据源映射map中,这样就实现了新数据源的动态注册功能,后面的查询就能路由到正确的数据源上了。看上去是不是很完美。

但是 ,实际验证的时候,发现新租户的第一次查库会失败,提示表不存在,是在默认数据库里执行的,但是后面的查库请求能正常执行。这是为什么呢? 

后来分析得知: 因为注册数据源,是在db-sharding 的时候进行的,这个时候查询请求已经在ShardingDataSource里获取它对应的数据源,但是没找见,就取的是默认的数据源,待第二次查库,因为第一次在db-sharding 的时候已经注册该数据源了,于是就拿到了正确的数据源执行sql。后来尝试在db-sharding 里动态注册数据源变成同步的,也就是待注册完后再继续执行后面的table-sharding 逻辑,问题依旧存在。于是就有了第二个思路。

2、 利用mybatis的Interceptor 机制实现数据源的动态注册

  Spring 中注册的数据源bean 是 ShardingDataSource, 所以在mybatis 的拦截器中实现动态判断,新租户对应的数据源是否已注册,如果未找到,就注册。直接贴代码

     //判断本次查询的数据源是否已注册.....此处代码省略 
     //新租户,动态刷新数据源
            DatasourceProperties dsProperty = DatasourceTemplate.getDatasourceConfig(queryParam.getSource(), dbFieldValue);
            String hikariRegisterString = DatasourceRegister.hikariRegisterString(queryParam.getSource(), dbFieldValue, dsProperty);
            Map addedDataSourceConfigurations = Maps.transformValues((Map) YamlEngine.unmarshal(hikariRegisterString)
                    , (Function<YamlDataSourceConfiguration, DataSourceConfiguration>) input -> new DataSourceConfigurationYamlSwapper().swap(input));

            synchronized (ShardingMybatisInterceptor.class){
                //再次判断已经注册过,就不需要再注册了
                if(dataSources.getDataSourceMap().containsKey(NameUtil.getShardingDataSourceName(queryParam.getSource(), dbFieldValue))){
                    QueryContextHolder.remove();
                    return invocation.proceed();
                }
                //相同的数据源存在就复用已经存在的
                Map reuseDataSource = getReuseDataSource(addedDataSourceConfigurations, dataSources.getDataSourceMap());
                Map addedDataSource = DataSourceConverter.getDataSourceMap(addedDataSourceConfigurations);
                dataSources.getDataSourceMap().putAll(reuseDataSource.isEmpty() ? addedDataSource : reuseDataSource);

                //部分表,不需要刷新分片规则
                if(StringUtils.isEmpty(shardingMapping.getTableField())){
                    refreashTableRule(dataSources);
                }else{
                    refreashShardingRule(dataSources);
                }

                dataSources.getDataSourceMap().entrySet().stream().forEach(e -> {
                    log.info("interceptor,source={}, datasource key={}, hashcode={}", queryParam.getSource(), e.getKey(), e.getValue().hashCode());
                });

                shardingDataSourceWrapper.getDataSourceConfigurations().clear();
                shardingDataSourceWrapper.getDataSourceConfigurations().putAll(DataSourceConverter.getDataSourceConfigurationMap(dataSources.getDataSourceMap()));
            }

    /**
     *
     * @param shardingDataSource
     */
    private void refreashShardingRule(ShardingDataSource shardingDataSource) {
        ShardingRule oldRule = shardingDataSource.getRuntimeContext().getRule();
        ShardingRuleConfiguration oldShardingRuleConfiguration = oldRule.getRuleConfiguration();
        Collection<TableRuleConfiguration> oldTableRuleConfigsBak = Lists.newArrayList(oldShardingRuleConfiguration.getTableRuleConfigs());

        //存在分表的情况下,才会重新生成 TableRuleConfiguration 中的ActualDataNodes
        List<TableRuleConfiguration> latestTableRuleConfigs = Lists.newArrayList();
        if (!CollectionUtils.isEmpty(oldTableRuleConfigsBak)) {
            //每个TableRuleConfiguration都要遍历,因为他们共享同一个数据源分片规则
            oldTableRuleConfigsBak.stream().forEach(oldConfig -> {
                //datasource  key = datasource-tenant1  -> datasource-tenant1.order_tenant1
                List<String> latestActualDataNodes = getActualNodeNames(shardingDataSource.getDataSourceMap(), oldConfig.getLogicTable());
                //刷新actualDataNodes
                String actualDataNodes = Joiner.on(",").join(latestActualDataNodes);
                TableRuleConfiguration renewTableRuleConfig = new TableRuleConfiguration(oldConfig.getLogicTable(), actualDataNodes);
                BeanUtils.copyProperties(oldConfig, renewTableRuleConfig);
                latestTableRuleConfigs.add(renewTableRuleConfig);
            });
        }
        oldShardingRuleConfiguration.getTableRuleConfigs().clear();
        oldShardingRuleConfiguration.getTableRuleConfigs().addAll(latestTableRuleConfigs);

        refreashTableRule(shardingDataSource);
    }


    /**
     * 刷新 table 的路由规则,否则执行sql找不到新注册的数据源
     * @param shardingDataSource
     */
    private void refreashTableRule(ShardingDataSource shardingDataSource) {
        ShardingRule oldRule = shardingDataSource.getRuntimeContext().getRule();
        Collection<TableRule> tableRules = oldRule.getTableRules();
        tableRules.clear();
        tableRules.addAll(createTableRules(oldRule.getRuleConfiguration(), shardingDataSource.getDataSourceMap().keySet()));
    }


//如果存在分表规则,需要更新分表规则
    private Collection<TableRule> createTableRules(final ShardingRuleConfiguration shardingRuleConfig, Collection<String> dataSourceNames) {
        Collection<TableRuleConfiguration> tableRuleConfigurations = shardingRuleConfig.getTableRuleConfigs();
        Collection<TableRule> result = new ArrayList<>(tableRuleConfigurations.size());
        ShardingDataSourceNames shardingDataSourceNames = new ShardingDataSourceNames(shardingRuleConfig, dataSourceNames);
        for (TableRuleConfiguration each : tableRuleConfigurations) {
            result.add(new TableRule(each, shardingDataSourceNames, null));
        }
        return result;
    }



    private List<String> getActualNodeNames(Map<String, DataSource> newDataSources, String logicTable) {
        //datasource-tenant1 -> datasource-tenant1.logicTable+tenant1
        return newDataSources.keySet().stream()
                .filter(e -> !e.toLowerCase().contains(ShardingConstants.DEFAULT_DATASOURCE_PREFIX))
                .map(e -> e.concat(".").concat(logicTable).concat(e.split("-")[1])).collect(Collectors.toList());
    }

另外需要考虑多租户同一数据源的情况,即在创建数据源的时候,同一个db 只能创建一个datasource,避免浪费过多的db 连接数。

如果有需要的同学,可以在下面留言

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页