Apache ShardingSphere 後設資料載入剖析
唐國強
小米軟體工程師,主要負責 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_1 、 t_order_2 算是 t_order 的節點,所以在概念上來分析, t_order 是 logicTable,而 t_order_1 和 t_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。
歡迎大家掃碼關注
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70001955/viewspace-2784739/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- DistSQL:像資料庫一樣使用 Apache ShardingSphereSQL資料庫Apache
- 基於 Apache ShardingSphere 構建高可用分散式資料庫Apache分散式資料庫
- [Apache Doris] Apache Doris 後設資料設計及DDL操作原始碼閱讀Apache原始碼
- Android 資源載入機制剖析Android
- Apache ShardingSphere HINT 實用指南Apache
- MySQL入門--後設資料MySql
- 從NewSQL的角度看Apache ShardingSphereSQLApache
- 提名 Apache ShardingSphere Committer,說說方法ApacheMIT
- Apache ShardingSphere 遇上得物“彩虹橋”Apache
- flink 類載入剖析
- Apache Ignite剖析Apache
- ICDE 2022|Apache ShardingSphere:一個功能全面和可插拔的資料分片平臺Apache
- 三年沉澱,Apache ShardingSphere 5.0.0 開啟資料應用新篇章Apache
- Apache ShardingSphere 企業行|走進搜狐Apache
- Apache頂級專案ShardingSphere — SQL Parser的設計與實現ApacheSQL
- Apache ShardingSphere:由開源驅動的分散式資料庫中介軟體生態Apache分散式資料庫
- 資料載入
- Apache ShardingSphere 企業行|走進騰訊Apache
- 重磅|Apache ShardingSphere 5.0.0 即將正式釋出Apache
- Apache ShardingSphere 如何實現分散式事務Apache分散式
- Apache畢業賀禮—Apache ShardingSphere跌宕起伏的開源之路Apache
- 博文乾貨|Apache InLong 使用 Apache Pulsar 建立資料入庫Apache
- 全鏈路線上生產資料庫壓測利器:Apache ShardingSphere 影子庫特性升級資料庫Apache
- Apache ShardingSphere 首篇論文被 ICDE 收錄,全球資料庫發展迎來新局面Apache資料庫
- Apache ShardingSphere 5.0.0 核心優化及升級指南Apache優化
- 資料治理--後設資料
- 資料來源Parquet之使用程式設計方式載入資料程式設計
- 資料治理之後設資料管理的利器——Atlas入門寶典
- Apache ShardingSphere 5.1.2 釋出|全新驅動 API + 雲原生部署,打造高效能資料閘道器ApacheAPI
- Apache ShardingSphere 5.1.0 執行引擎效能優化揭祕Apache優化
- Apache ShardingSphere 在京東白條場景的落地之旅Apache
- JVM類載入器ClassLoader原始碼剖析JVM原始碼
- 徹底剖析JVM類載入機制JVM
- 馬蜂窩:大資料剖析90後如何過情人節大資料
- 資料治理之後設資料管理
- SQL 居然還能在 Apache ShardingSphere 上實現這些功能?SQLApache
- DevOps後設資料管理dev
- PayPal 後設資料之旅