关于数据库:Apache-ShardingSphere-元数据加载剖析

62次阅读

共计 9786 个字符,预计需要花费 25 分钟才能阅读完成。

作者唐国城,小米软件工程师,次要负责 MIUI 浏览器服务端研发工作。酷爱开源,酷爱技术,喜爱摸索,热衷于钻研学习各种开源中间件,很快乐能参加到 ShardingSphere 社区建设中,心愿在社区中努力提高本人,为 ShardingSphere 社区的倒退做更多的工作。

元数据在 ShardingSphere 中加载的过程

一、概述
元数据是示意数据的数据。从数据库角度而言,则概括为数据库的任何数据都是元数据,因而如列名、数据库名、用户名、表名等以及数据自定义库表存储的对于数据库对象的信息都是元数据。而 ShardingSphere 中的外围性能如数据分片、加解密等都是须要基于数据库的元数据生成路由或者加密解密的列实现,由此可见 元数据是 ShardingSphere 零碎运行的外围,同样也是每一个数据存储相干中间件或者组件的外围数据。有了元数据的注入,相当于整个零碎有了神经中枢,能够联合元数据实现对于库、表、列的个性化操作,如数据分片、数据加密、SQL 改写等。

而对于 ShardingSphere 元数据的加载过程,首先须要弄清楚在 ShardingSphere 中元数据的类型以及分级。在 ShardingSphere 中元数据次要围绕着 ShardingSphereMetaData 来进行开展,其中较为外围的是 ShardingSphereSchema。该构造是数据库的元数据,同时也为数据源元数据的顶层对象,在 ShardingSphere 中数据库元数据的构造如下图。对于每一层来说,下层数据来源于上层数据的组装,所以上面咱们采纳从下往上的分层形式进行逐个分析。

ShardingSphere 数据库元数据结构图

二、ColumMetaData 和 IndexMetaData

ColumMetaData 和 IndexMetaData 是组成 TableMetaData 的根本元素,上面咱们离开讲述两种元数据的构造以及加载过程。ColumMetaData 次要构造如下:

public final class ColumnMetaData {
    // 列名
    private final String name;
    // 数据类型
    private final int dataType;
    // 是否主键
    private final boolean primaryKey;
    // 是否主动生成
    private final boolean generated;
    // 是否辨别大小写
    private final boolean caseSensitive;
}

其加载过程次要封装在 org.apache.shardingsphere.infra.metadata.schema.builder.loader.ColumnMetaDataLoader#load 办法中,次要过程是通过数据库链接获取元数据匹配表名加载某个表名下所有的列的元数据。外围代码如下:

/**
 * Load column meta data list.
 *
 * @param connection connection
 * @param tableNamePattern table name pattern
 * @param databaseType database type
 * @return column meta data list
 * @throws SQLException SQL exception
 */
public static Collection<ColumnMetaData> load(final Connection connection, final String tableNamePattern, final DatabaseType databaseType) throws SQLException {Collection<ColumnMetaData> result = new LinkedList<>();
    Collection<String> primaryKeys = loadPrimaryKeys(connection, tableNamePattern);
    List<String> columnNames = new ArrayList<>();
    List<Integer> columnTypes = new ArrayList<>();
    List<String> columnTypeNames = new ArrayList<>();
    List<Boolean> isPrimaryKeys = new ArrayList<>();
    List<Boolean> isCaseSensitives = new ArrayList<>();
    try (ResultSet resultSet = connection.getMetaData().getColumns(connection.getCatalog(), connection.getSchema(), tableNamePattern, "%")) {while (resultSet.next()) {String tableName = resultSet.getString(TABLE_NAME);
            if (Objects.equals(tableNamePattern, tableName)) {String columnName = resultSet.getString(COLUMN_NAME);
                columnTypes.add(resultSet.getInt(DATA_TYPE));
                columnTypeNames.add(resultSet.getString(TYPE_NAME));
                isPrimaryKeys.add(primaryKeys.contains(columnName));
                columnNames.add(columnName);
            }
        }
    }
    try (Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery(generateEmptyResultSQL(tableNamePattern, databaseType))) {for (int i = 0; i < columnNames.size(); i++) {isCaseSensitives.add(resultSet.getMetaData().isCaseSensitive(resultSet.findColumn(columnNames.get(i))));
            result.add(new ColumnMetaData(columnNames.get(i), columnTypes.get(i), isPrimaryKeys.get(i),
                    resultSet.getMetaData().isAutoIncrement(i + 1), isCaseSensitives.get(i)));
        }
    }
    return result;
}

IndexMetaData 其实是表中的索引的名称,所以没有简单的构造属性,只有一个名称,所以不开展赘述,重点讲述一下加载过程。加载过程和 column 相似,次要流程在 org.apache.shardingsphere.infra.metadata.schema.builder.loader.IndexMetaDataLoader#load 办法中,根本流程同样也是通过数据库链接获取相干数据库和表的元数据中的 indexInfo 组织外围的 IndexMetaData,实现代码如下:

public static Collection<IndexMetaData> load(final Connection connection, final String table) throws SQLException {Collection<IndexMetaData> result = new HashSet<>();
    try (ResultSet resultSet = connection.getMetaData().getIndexInfo(connection.getCatalog(), connection.getSchema(), table, false, false)) {while (resultSet.next()) {String indexName = resultSet.getString(INDEX_NAME);
            if (null != indexName) {result.add(new IndexMetaData(indexName));
            }
        }
    } catch (final SQLException ex) {if (ORACLE_VIEW_NOT_APPROPRIATE_VENDOR_CODE != ex.getErrorCode()) {throw ex;}
    }
    return result;
}

三、TableMetaData

该类型是组成 ShardingSphereMetaData 的根本元素,其构造如下:

public final class TableMetaData {
    // 表名
    private final String name;
    // 列元数据
    private final Map<String, ColumnMetaData> columns;
    // 索引元数据
    private final Map<String, IndexMetaData> indexes;
    // 省略一些办法
}

从上述构造能够看出,TableMetaData 其实是由 ColumnMetaData 和 IndexMetaData 组装而来,所以 TableMetaData 的加载过程能够了解为是一个中间层,具体的实现还是 ColumnMetaDataLoader 和 IndexMetaDataLoader 拿到表名以及相干链接进行数据加载。所以比较简单的 TableMetaData 加载过程次要在 org.apache.shardingsphere.infra.metadata.schema.builder.loader.TableMetaDataLoader#load 办法,其加载的外围流程如下:

public static Optional<TableMetaData> load(final DataSource dataSource, final String tableNamePattern, final DatabaseType databaseType) throws SQLException {
    // 获取链接
    try (MetaDataLoaderConnectionAdapter connectionAdapter = new MetaDataLoaderConnectionAdapter(databaseType, dataSource.getConnection())) {
        // 依据不同的数据库类型,格式化表名的含糊匹配字段
        String formattedTableNamePattern = databaseType.formatTableNamePattern(tableNamePattern);
        // 加载 ColumnMetaData 和 IndexMetaData 组装 TableMetaData
        return isTableExist(connectionAdapter, formattedTableNamePattern)
                ? Optional.of(new TableMetaData(tableNamePattern, ColumnMetaDataLoader.load(connectionAdapter, formattedTableNamePattern, databaseType), IndexMetaDataLoader.load(connectionAdapter, formattedTableNamePattern)))
                : Optional.empty();}
}

四、SchemaMetaData

通过下两层的剖析,很显著这一层是元数据裸露的最外层,最外的层的构造为 ShardingSphereSchema,其次要构造为:

/**
 * ShardingSphere schema.
 */
@Getter
public final class ShardingSphereSchema {

    private final Map<String, TableMetaData> tables;

    @SuppressWarnings("CollectionWithoutInitialCapacity")
    public ShardingSphereSchema() {tables = new ConcurrentHashMap<>();
    }

    public ShardingSphereSchema(final Map<String, TableMetaData> tables) {this.tables = new ConcurrentHashMap<>(tables.size(), 1);
        tables.forEach((key, value) -> this.tables.put(key.toLowerCase(), value));
    }

和 schema 的概念符合,一个 schema 含有若干个表。ShardingSphereSchema 的属性是一个 map 构造,key 为 tableName,value 是表名对应表的元数据。次要是通过构造函数实现初始化。所以,还是重点对于表元数据的加载,上面咱们从入口跟进。

整个元数据加载的外围入口在 org.apache.shardingsphere.infra.context.metadata.MetaDataContextsBuilder#build 中。在 build 中次要是通过配置的规定,组装和加载绝对应的元数据,外围代码如下:

/**
 * Build meta data contexts.
 * 
 * @exception SQLException SQL exception
 * @return meta data contexts
 */
public StandardMetaDataContexts build() throws SQLException {Map<String, ShardingSphereMetaData> metaDataMap = new HashMap<>(schemaRuleConfigs.size(), 1);
    Map<String, ShardingSphereMetaData> actualMetaDataMap = new HashMap<>(schemaRuleConfigs.size(), 1);
    for (String each : schemaRuleConfigs.keySet()) {Map<String, DataSource> dataSourceMap = dataSources.get(each);
        Collection<RuleConfiguration> ruleConfigs = schemaRuleConfigs.get(each);
        DatabaseType databaseType = DatabaseTypeRecognizer.getDatabaseType(dataSourceMap.values());
        // 获取配置的规定
        Collection<ShardingSphereRule> rules = ShardingSphereRulesBuilder.buildSchemaRules(each, ruleConfigs, databaseType, dataSourceMap);
        // 加载 actualTableMetaData 和 logicTableMetaData
        Map<TableMetaData, TableMetaData> tableMetaDatas = SchemaBuilder.build(new SchemaBuilderMaterials(databaseType, dataSourceMap, rules, props));
        // 组装规定元数据
        ShardingSphereRuleMetaData ruleMetaData = new ShardingSphereRuleMetaData(ruleConfigs, rules);
        // 组装数据源元数据
        ShardingSphereResource resource = buildResource(databaseType, dataSourceMap);
        // 组装数据库元数据
        ShardingSphereSchema actualSchema = new ShardingSphereSchema(tableMetaDatas.keySet().stream().filter(Objects::nonNull).collect(Collectors.toMap(TableMetaData::getName, v -> v)));
        actualMetaDataMap.put(each, new ShardingSphereMetaData(each, resource, ruleMetaData, actualSchema));
        metaDataMap.put(each, new ShardingSphereMetaData(each, resource, ruleMetaData, buildSchema(tableMetaDatas)));
    }
    // 
    OptimizeContextFactory optimizeContextFactory = new OptimizeContextFactory(actualMetaDataMap);
    return new StandardMetaDataContexts(metaDataMap, buildGlobalSchemaMetaData(metaDataMap), executorEngine, props, optimizeContextFactory);
}

通过上述代码能够看出在 build 办法中,次要基于配置的 schemarule 加载了数据库的根本数据如数据库类型、数据库连接池等,通过这些数据实现对于 ShardingSphereResource 的组装;实现 ShardingSphereRuleMetaData 如配置规定、加密规定、认证规定等数据组装;实现 ShardingSphereSchema 中的必要数据库元数据的加载。跟踪找到表元数据的加载办法即 org.apache.shardingsphere.infra.metadata.schema.builder.SchemaBuilder#build,在这个办法中,别离加载了 actualTableMetaData 以及 logicTableMetaData,那么什么是 actualTable,什么是 logicTable 呢?简略的来说对于 t_order_1t_order_2 算是 t_order 的节点,所以在概念上来剖析,t_order 是 logicTable,而 t_order_1t_order_2 是 actualTable。明确了这两个概念后,咱们再来一起看 build 办法,次要分为以下两步:

1. actualTableMetaData 加载

actualTableMetaData 是零碎分片的根底表,在 5.0.0-beta 版本中,咱们采纳了数据库方言的形式利用 SQL 进行元数据的查问加载,所以根本流程就是首先通过通过 SQL 进行数据库元数据的查问加载,如果没找到数据库方言加载器,则采纳 JDBC 驱动连贯进行获取,再联合 ShardingSphereRule 中配置的表名,进行配置表的元数据的加载。外围代码如下所示:

private static Map<String, TableMetaData> buildActualTableMetaDataMap(final SchemaBuilderMaterials materials) throws SQLException {Map<String, TableMetaData> result = new HashMap<>(materials.getRules().size(), 1);
    // 数据库方言 SQL 加载元数据
    appendRemainTables(materials, result);
    for (ShardingSphereRule rule : materials.getRules()) {if (rule instanceof TableContainedRule) {for (String table : ((TableContainedRule) rule).getTables()) {if (!result.containsKey(table)) {TableMetaDataBuilder.load(table, materials).map(optional -> result.put(table, optional));
                }
            }
        }
    }
    return result;
}

2. logicTableMetaData 加载

由上述的概念能够看出 logicTable 是 actualTable 基于不同的规定组装而来的理论的逻辑节点,可能是分片节点也可能是加密节点或者是其余,所以 logicTableMetaData 是以 actualTableMetaData 为根底,联合具体的配置规定如分库分表规定等关联的节点。在具体流程上,首先获取配置规定的表名,而后判断是否曾经加载过 actualTableMetaData,通过 TableMetaDataBuilder#decorate 办法联合配置规定,生成相干逻辑节点的元数据。外围代码流程如下所示:

private static Map<String, TableMetaData> buildLogicTableMetaDataMap(final SchemaBuilderMaterials materials, final Map<String, TableMetaData> tables) throws SQLException {Map<String, TableMetaData> result = new HashMap<>(materials.getRules().size(), 1);
    for (ShardingSphereRule rule : materials.getRules()) {if (rule instanceof TableContainedRule) {for (String table : ((TableContainedRule) rule).getTables()) {if (tables.containsKey(table)) {TableMetaData metaData = TableMetaDataBuilder.decorate(table, tables.get(table), materials.getRules());
                    result.put(table, metaData);
                }
            }
        }
    }
    return result;
}

至此,外围元数据加载实现,封装成一个 Map 进行返回,供各个需要场景进行应用。

元数据加载优化剖析

尽管说元数据是咱们零碎的外围,是必不可少的,然而在系统启动时进行数据加载,必然会导致系统的负载减少,系统启动效率低。所以咱们须要对加载的过程进行优化,目前次要是以下两方面的摸索:

一、应用 SQL 查问替换原生 JDBC 驱动连贯

在 5.0.0-beta 版本之前,采纳的形式是通过原生 JDBC 驱动原生形式加载。在 5.0.0-beta 版本中,咱们逐渐采纳了应用数据库方言,通过 SQL 查问的形式,多线程形式实现了元数据的加载。进一步提高了零碎数据加载的速度。具体的方言 Loader 能够查看 org.apache.shardingsphere.infra.metadata.schema.builder.spi.DialectTableMetaDataLoader 的相干实现。

二、缩小元数据的加载次数

对于零碎通用的资源的加载,咱们遵循一次加载,多处应用。当然在这个过程中,咱们也要衡量空间和工夫,所以咱们在一直的进行优化,缩小元数据的反复加载,进步零碎整体的效率。

更多具体性能,欢送下载应用 Apache ShardingSphere 5.0.0-beta。

欢送大家扫码关注

正文完
 0