【源码】Sharding-JDBC源码分析之ShardingSphereDataSource的创建原理

Sharding-JDBC系列

1、Sharding-JDBC分库分表的基本使用

2、Sharding-JDBC分库分表之SpringBoot分片策略

3、Sharding-JDBC分库分表之SpringBoot主从配置

4、SpringBoot集成Sharding-JDBC-5.3.0分库分表

5、SpringBoot集成Sharding-JDBC-5.3.0实现按月动态建表分表

6、【源码】Sharding-JDBC源码分析之JDBC

7、【源码】Sharding-JDBC源码分析之SPI机制

8、【源码】Sharding-JDBC源码分析之Yaml分片配置文件解析原理

9、【源码】Sharding-JDBC源码分析之Yaml分片配置原理(一)

10、【源码】Sharding-JDBC源码分析之Yaml分片配置原理(二)

11、【源码】Sharding-JDBC源码分析之Yaml分片配置转换原理

12、【源码】Sharding-JDBC源码分析之ShardingSphereDataSource的创建原理

前言

【源码】Sharding-JDBC源码分析之JDBC-CSDN博客中分析了ShardingSphere框架中自定义ShardingSphereDataSource实现了JDBC中的javax.sql.DataSource,本文从源码角度分析ShardingSphereDataSource的实现。

ShardingSphereDataSource创建准备回顾

ShardingSphere框架创建ShardingSphereDataSource之前,主要执行如下:

1)解析分片配置yaml文件,解析后的对象为YamlRootConfiguration;

2)从YamlRootConfiguration中获取配置的dataSources,根据配置的dataSourceClassName,使用反射机制,创建对应的dataSource,并进行赋值。保存在Map<String, DataSource>中;

3)从YamlRootConfiguration中获取配置的rules、mode,通过对应的Swapper转换器,分别转换为Collection<RuleConfiguration>和ModeConfiguration;

以上代码的实现详见《Sharding-JDBC系列》的前几篇源码介绍。源码入口:

package org.apache.shardingsphere.driver.api.yaml;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class YamlShardingSphereDataSourceFactory {
	/**
     * 创建ShardingSphereDataSource
     */
    private static DataSource createDataSource(final Map<String, DataSource> dataSourceMap, final YamlRootConfiguration rootConfig) throws SQLException {
        // mode配置转换
        ModeConfiguration modeConfig = null == rootConfig.getMode() ? null : new YamlModeConfigurationSwapper().swapToObject(rootConfig.getMode());
        // rules配置转换
        Collection<RuleConfiguration> ruleConfigs = SWAPPER_ENGINE.swapToRuleConfigurations(rootConfig.getRules());
        // 创建ShardingSphereDataSource
        return ShardingSphereDataSourceFactory.createDataSource(rootConfig.getDatabaseName(), modeConfig, dataSourceMap, ruleConfigs, rootConfig.getProps());
    }
}

最后调用ShardingSphereDataSourceFactory的createDataSource()方法,创建DataSource。源码如下:

package org.apache.shardingsphere.driver.api;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class ShardingSphereDataSourceFactory {
  
    /**
     * 创建DataSource,返回ShardingSphereDataSource对象
     */
    public static DataSource createDataSource(final String databaseName, final ModeConfiguration modeConfig,
                                              final Map<String, DataSource> dataSourceMap, final Collection<RuleConfiguration> configs, final Properties props) throws SQLException {
        return new ShardingSphereDataSource(getDatabaseName(databaseName), modeConfig, dataSourceMap, null == configs ? new LinkedList<>() : configs, props);
    }
    
    /**
     * 获取数据库名称,默认为DefaultDatabase.LOGIC_NAME,为字符串"logic_db"
     */
    private static String getDatabaseName(final String databaseName) {
        return Strings.isNullOrEmpty(databaseName) ? DefaultDatabase.LOGIC_NAME : databaseName;
    }
}

ShardingSphereDataSource

ShardingSphereDataSource的源码如下:

package org.apache.shardingsphere.driver.jdbc.core.datasource;

/**
 * ShardingSphere的数据源
 */
public final class ShardingSphereDataSource extends AbstractDataSourceAdapter implements AutoCloseable {
    
	// 数据库名称
    private final String databaseName;

    // 上下文管理,包括元数据上下文、实例上下文、执行引擎
    private final ContextManager contextManager;

    // JDBC上下文。从DataSource中获取一个Connection,获取数据库信息封装成CachedDatabaseMetaData。如url、username、最大连接数、是否支持分组等
    private final JDBCContext jdbcContext;
    
    public ShardingSphereDataSource(final String databaseName, final ModeConfiguration modeConfig) throws SQLException {
        this.databaseName = databaseName;
        contextManager = createContextManager(databaseName, modeConfig, new HashMap<>(), new LinkedList<>(), new Properties());
        jdbcContext = new JDBCContext(contextManager.getDataSourceMap(databaseName));
    }

    /**
     *
     * @param databaseName 默认为logic_db
     * @param modeConfig 模式,单例或集群部署
     * @param dataSourceMap 数据源Map,key为定义的逻辑数据源名称、value为HikariDataSource等
     * @param ruleConfigs 规则配置
     * @param props props配置
     */
    public ShardingSphereDataSource(final String databaseName, final ModeConfiguration modeConfig, final Map<String, DataSource> dataSourceMap,
                                    final Collection<RuleConfiguration> ruleConfigs, final Properties props) throws SQLException {
        this.databaseName = databaseName;
        contextManager = createContextManager(databaseName, modeConfig, dataSourceMap, ruleConfigs, null == props ? new Properties() : props);
        jdbcContext = new JDBCContext(contextManager.getDataSourceMap(databaseName));
    }

    /**
     * 创建上下文
     */
    private ContextManager createContextManager(final String databaseName, final ModeConfiguration modeConfig, final Map<String, DataSource> dataSourceMap,
                                                final Collection<RuleConfiguration> ruleConfigs, final Properties props) throws SQLException {
        // 返回JDBCInstanceMetaData对象。JDBC实例元数据。记录id、ip和版本
        InstanceMetaData instanceMetaData = TypedSPIRegistry.getRegisteredService(InstanceMetaDataBuilder.class, "JDBC").build(-1);
        // 全局的规则配置
        Collection<RuleConfiguration> globalRuleConfigs = ruleConfigs.stream().filter(each -> each instanceof GlobalRuleConfiguration).collect(Collectors.toList());
        // 数据源规则配置信息,从ruleConfigs中拷贝一份
        Collection<RuleConfiguration> databaseRuleConfigs = new LinkedList<>(ruleConfigs);
        databaseRuleConfigs.removeAll(globalRuleConfigs);
        // 记录 ContextManagerBuilder的参数信息
        ContextManagerBuilderParameter param = new ContextManagerBuilderParameter(modeConfig, Collections.singletonMap(databaseName,
                new DataSourceProvidedDatabaseConfiguration(dataSourceMap, databaseRuleConfigs)), globalRuleConfigs, props, Collections.emptyList(), instanceMetaData, false);
        // 根据配置的mode,使用SPI,创建ContextManagerBuilder
        ContextManagerBuilder contextManagerBuilder = null == modeConfig
                // 通过类,从SPI(基于Java的SPI机制)中获取对应实现类,且执行init()方法
                ? RequiredSPIRegistry.getRegisteredService(ContextManagerBuilder.class)
                // 通过类,从SPI(基于Java的SPI机制)中获取对应实现类,且类型名称为配置的mode。如集群为Cluster,单机为Standalone
                : TypedSPIRegistry.getRegisteredService(ContextManagerBuilder.class, modeConfig.getType());
        return contextManagerBuilder.build(param);
    }

    /**
     * 默认返回一个ShardingSphereConnection对象
     * @return
     */
    @Override
    public Connection getConnection() {
        return DriverStateContext.getConnection(databaseName, contextManager, jdbcContext);
    }
    
    @Override
    public Connection getConnection(final String username, final String password) {
        return getConnection();
    }
    
    /**
     * 关闭dataSource
     */
    public void close(final Collection<String> dataSourceNames) throws Exception {
        Map<String, DataSource> dataSourceMap = contextManager.getDataSourceMap(databaseName);
        for (String each : dataSourceNames) {
            close(dataSourceMap.get(each));
        }
        contextManager.close();
    }
    
    private void close(final DataSource dataSource) throws Exception {
        if (dataSource instanceof AutoCloseable) {
            ((AutoCloseable) dataSource).close();
        }
    }
    
    @Override
    public void close() throws Exception {
        close(contextManager.getDataSourceMap(databaseName).keySet());
    }
    
    @Override
    public int getLoginTimeout() throws SQLException {
        Map<String, DataSource> dataSourceMap = contextManager.getDataSourceMap(databaseName);
        return dataSourceMap.isEmpty() ? 0 : dataSourceMap.values().iterator().next().getLoginTimeout();
    }
    
    @Override
    public void setLoginTimeout(final int seconds) throws SQLException {
        for (DataSource each : contextManager.getDataSourceMap(databaseName).values()) {
            each.setLoginTimeout(seconds);
        }
    }
}

3.1 构造方法

在ShardingSphereDataSource的构造方法主要执行如下:

3.1.1 保存databaseName

保存传入的databaseName,默认为DefaultDatabase.LOGIC_NAME,即logic_db。

3.1.2 创建ContextManager

调用createContextManager()创建一个ContextManager对象。

1)通过SPI获得类型为JDBC的InstanceMetaDataBuilder,即JDBCInstanceMetaDataBuilder,执行build()方法,获取一个InstanceMetaData对象。该对象记录id、ip和ShardingSphere的版本;

2)从rules规则中的Collection<RuleConfiguration>过滤出GlobalRuleConfiguration,即全局的rule规则,如SQL解析、事务等规则;

3)移除全局规则,余下的规则为数据源的配置;

4)创建DataSourceProvidedDatabaseConfiguration,保存数据源相关的配置信息;

5)创建ContextManagerBuilderParameter对象,记录 ContextManagerBuilder的参数信息;

6)根据配置的mode,使用SPI,创建ContextManagerBuilder;

如果没有配置mode,则执行RequiredSPIRegistry.getRegisteredService(ContextManagerBuilder.class)。因为StandaloneContextManagerBuilder的isDefault()为true,所以默认返回StandaloneContextManagerBuilder对象;

7)执行ContextManagerBuilder的build()创建ContextManager对象;

在build()方法中,主要执行如下:

a)根据配置的mode中的provider,持久化存储配置信息。在对应的provider中创建一张repository表,而后将配置信息,根据数据源、规则、属性等信息持久化到repository表中;

b)根据配置的数据源信息,创建ShardingSphereDatabase对象。该对象记录了配置的数据源的元数据信息。如数据源中创建的表、视图、表中的列、索引、约束等;

c)创建InstanceContext对象,该对象保存了当前系统的计算节点、集群节点、mode配置、锁上下文等信息;

d)根据配置的数据源信息,创建MetaDataContexts对象。该对象保存上面a和b的信息;

e)创建ContextManager对象。该对象保存了c和d的信息;

3.1.3 创建JDBCContext

创建一个JDBCContext对象,该对象从DataSource中获取一个Connection,获取数据库的DatabaseMetaData对象,将对象的信息获取出来,存放在CachedDatabaseMetaData中。如url、username、最大连接数、是否支持分组等。

JDBCContext的源码如下:

package org.apache.shardingsphere.driver.jdbc.context;

/**
 * JDBC上下文。从DataSource中获取一个Connection,获取数据库信息封装成CachedDatabaseMetaData。如url、username、最大连接数、是否支持分组等
 */
@Getter
public final class JDBCContext {

    // 存放Database的配置信息。如url、username、最大连接数、是否支持分组等
    private volatile CachedDatabaseMetaData cachedDatabaseMetaData;
    
    public JDBCContext(final Map<String, DataSource> dataSources) throws SQLException {
        cachedDatabaseMetaData = createCachedDatabaseMetaData(dataSources).orElse(null);
    }

    @Subscribe
    public synchronized void refreshCachedDatabaseMetaData(final DataSourceChangedEvent event) throws SQLException {
        cachedDatabaseMetaData = createCachedDatabaseMetaData(DataSourcePoolCreator.create(event.getDataSourcePropertiesMap())).orElse(null);
    }

    /**
     * 从Datasource中获取一个Connection对象,只获取Connection的元数据,封装成CachedDatabaseMetaData
     */
    private Optional<CachedDatabaseMetaData> createCachedDatabaseMetaData(final Map<String, DataSource> dataSources) throws SQLException {
        if (dataSources.isEmpty()) {
            return Optional.empty();
        }
        try (Connection connection = dataSources.values().iterator().next().getConnection()) {
            return Optional.of(new CachedDatabaseMetaData(connection.getMetaData()));
        }
    }
}

3.2 getConnection()

该方法执行DriverStateContext的getConnection()用于返回一个 java.sql.Connection 连接对象。

DriverStateContext的源码如下:

package org.apache.shardingsphere.driver.state;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class DriverStateContext {

    /**
     * 通过SPI,获取DriverState状态的实现类(锁、熔断限流、OK三种状态)对应的DataSource,执行getConnection()获取连接
     */
    public static Connection getConnection(final String databaseName, final ContextManager contextManager, final JDBCContext jdbcContext) {
        return TypedSPIRegistry.getRegisteredService(
                DriverState.class, contextManager.getInstanceContext().getInstance().getState().getCurrentState().name()).getConnection(databaseName, contextManager, jdbcContext);
    }
}

getConnection()方法执行如下:

1)根据当前实例的状态,通过SPI,获取DriverState的实现类;

a)实例的状态有三种,在StateType枚举类中定义,分别为OK、LOCK、CIRCUIT_BREAK,即正常状态、锁状态和断路器状态(流量治理,限流、熔断),默认为OK;

b)三种实例状态对应三个DriverState实现类,分别为OKDriverState、LockDriverState和CircuitBreakDriverState;

2)获取具体的DriverState实现类,执行DriverState.getConnection()获取一个 java.sql.Connection 连接对象,不同的DriverState实现处理方式不同;

3.2.1 OKDriverState

OKDriverState的源码如下:

package org.apache.shardingsphere.driver.state.ok;

public final class OKDriverState implements DriverState {

    /**
     * 返回一个ShardingSphereConnection对象
     */
    @Override
    public Connection getConnection(final String databaseName, final ContextManager contextManager, final JDBCContext jdbcContext) {
        return new ShardingSphereConnection(databaseName, contextManager, jdbcContext);
    }
    
    @Override
    public String getType() {
        return "OK";
    }
}

在getConnection()方法中,返回一个ShardingSphereConnection对象。

3.2.2 LockDriverState

LockDriverState的源码如下:

package org.apache.shardingsphere.driver.state.lock;

public final class LockDriverState implements DriverState {
    
    @Override
    public Connection getConnection(final String databaseName, final ContextManager contextManager, final JDBCContext jdbcContext) {
        throw new UnsupportedSQLOperationException("LockDriverState");
    }
    
    @Override
    public String getType() {
        return "LOCK";
    }
}

暂时不支持LockDriverState。

3.2.3 CircuitBreakDriverState

CircuitBreakDriverState的源码如下:

package org.apache.shardingsphere.driver.state.circuit;

public final class CircuitBreakDriverState implements DriverState {
    
	/**
     * 从CircuitBreakerDataSource对象中的getConnection()方法返回一个Connection连接
     */
    @Override
    public Connection getConnection(final String databaseName, final ContextManager contextManager, final JDBCContext jdbcContext) {
        return new CircuitBreakerDataSource().getConnection();
    }
    
    @Override
    public String getType() {
        return "CIRCUIT_BREAK";
    }
}

在getConnection()方法中,新创建一个CircuitBreakerDataSource,从CircuitBreakerDataSource的getConnection()方法中返回一个Connection连接。

CircuitBreakerDataSource同ShardingSphereDataSource一样继承AbstractDataSourceAdapter,在getConnection()方法中返回CircuitBreakerConnection对象。此时系统处于熔断状态,不支持数据库的操作,所以CircuitBreakerConnection是一个空的Connection对象,创建的CircuitBreakerPreparedStatement不执行任何操作。

小结

限于篇幅,本篇先分享到这里,以下做一个小结:

1)程序启动时,执行ShardingSphereDriver的connect()方法;

2)解析分片配置yaml文件,解析后的对象为YamlRootConfiguration;

3)从YamlRootConfiguration中获取配置的dataSources,根据配置的dataSourceClassName,使用反射机制,创建对应的dataSource,并进行赋值。保存在Map<String, DataSource>中;

4)从YamlRootConfiguration中获取配置的rules、mode,通过对应的Swapper转换器,分别转换为Collection<RuleConfiguration>和ModeConfiguration;

5)根据配置的分片信息,创建ShardingSphereDataSource对象。该对象的两个核心方法为:

5.1)构造方法。在构造方法中,创建ContextManager、JDBCContext;

ContextManager存储了配置的数据源的元数据信息(如分片信息、数据源中的表、视图、表的列、索引等元数据)、实例上下文(如维护当前系统的计算节点、集群节点、元数据模型、元数据存储仓库、锁,及相关数据的维护,并提供计算节点状态修改);

JDBCContext从DataSource中获取一个Connection,获取数据库的DatabaseMetaData对象,将对象的信息获取出来,存放在CachedDatabaseMetaData中。如url、username、最大连接数、是否支持分组等;

5.2)getConnection()方法。该方法根据当前实例的StateType,从对应的DriverState中获取一个Connection连接对象;

a)实例的状态有三种,在StateType枚举类中定义,分别为OK、LOCK、CIRCUIT_BREAK,即正常状态、锁状态和断路器状态(流量治理,限流、熔断),默认为OK;

b)三种实例状态对应三个DriverState实现类,分别为OKDriverState、LockDriverState和CircuitBreakDriverState;

c)三个DriverState实现类的getConnection()方法分别返回ShardingSphereConnection、报异常、CircuitBreakConnection,其中CircuitBreakConnection为空的Connection对象,即不会执行真正的数据库操作;

以上为本篇分享的全部内容。

关于本篇内容你有什么自己的想法或独到见解,欢迎在评论区一起交流探讨下吧。

  • 22
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值