Sharding-JDBC 原始碼之啟動流程分析
Sharding-JDBC 是 ShardingSphere 開源的分散式資料庫中介軟體產品之一,提供標準化的資料分片、分散式事務和資料庫治理功能,可適用於如Java同構、異構語言、雲原生等各種多樣化的應用場景。
Sharding-JDBC 在 Java 的 JDBC 層提供額外服務,它使用客戶端直連資料庫,以jar包形式提供服務,無需額外部署和依賴,可理解為增強版的JDBC驅動,完全相容JDBC和各種ORM框架
接下來,讓我們由簡入繁,逐漸拉來 Sharding-JDBC 的序幕。
首先基於 SpringBoot2.4 快速搭建一個 Demo,主要 jar 包依賴版本分別是:
- io.shardingsphere.sharding-jdbc-spring-boot-starter:3.0.0.M1
- com.alibaba.druid-spring-boot-starter:1.1.17
- org.mybatis.spring.boot.mybatis-spring-boot-starter:2.1.4
配置 application.properties :
spring.application.name=shardingjdbcdemo
server.port=8080
spring.main.allow-bean-definition-overriding=true
#資料來源名稱
sharding.jdbc.datasource.names=ds0,ds1
#資料來源 ds0 的配置
sharding.jdbc.datasource.ds0.type=com.alibaba.druid.pool.DruidDataSource
sharding.jdbc.datasource.ds0.driver-class-name=com.mysql.cj.jdbc.Driver
sharding.jdbc.datasource.ds0.url=jdbc\:mysql\://localhost\:3306/d_sharding_demo1?useUnicode\=true&characterEncoding\=UTF-8&autoReconnect\=true&failOverReadOnly\=false&allowMultiQueries\=true&serverTimezone=GMT
sharding.jdbc.datasource.ds0.username=root
sharding.jdbc.datasource.ds0.password=123456
sharding.jdbc.datasource.ds0.initialSize=1
sharding.jdbc.datasource.ds0.minIdle=1
sharding.jdbc.datasource.ds0.maxActive=50
#資料來源 ds1 的配置
sharding.jdbc.datasource.ds1.type=com.alibaba.druid.pool.DruidDataSource
sharding.jdbc.datasource.ds1.driver-class-name=com.mysql.cj.jdbc.Driver
sharding.jdbc.datasource.ds1.url=jdbc\:mysql\://localhost\:3306/d_sharding_demo2?useUnicode\=true&characterEncoding\=UTF-8&autoReconnect\=true&failOverReadOnly\=false&allowMultiQueries\=true&serverTimezone=GMT
sharding.jdbc.datasource.ds1.username=root
sharding.jdbc.datasource.ds1.password=123456
sharding.jdbc.datasource.ds1.initialSize=1
sharding.jdbc.datasource.ds1.minIdle=1
sharding.jdbc.datasource.ds1.maxActive=50
#控制檯展示 sql 語句
sharding.jdbc.config.sharding.props.sql.show=true
#實際節點
sharding.jdbc.config.sharding.tables.t_goods.actual-data-nodes=ds$->{0..1}.t_goods_$->{0..1}
#分表列
sharding.jdbc.config.sharding.tables.t_goods.table-strategy.inline.shardingColumn=goods_id
#分片策略
sharding.jdbc.config.sharding.tables.t_goods.table-strategy.inline.algorithm-expression=t_goods_$->{goods_id % 2}
#分庫列
sharding.jdbc.config.sharding.tables.t_goods.databaseStrategy.standard.shardingColumn=type
#分庫策略
sharding.jdbc.config.sharding.tables.t_goods.databaseStrategy.standard..preciseAlgorithmClassName=com.example.shardingjdbcdemo.algorithm.MyPreciseShardingAlgorithm
客戶端測試程式
@Test
public void insert() {
GoodsDTO order = new GoodsDTO();
for (int i = 0; i < 1; i++) {
order.setGoodsId(i + 1L);
order.setGoodsName("商品" + (i + 1));
order.setType(0);
orderInfoMapper.insertGoods(order);
}
}
原始碼分析
當執行上面的客戶端程式時,Springboot 會去拉取 application.properties
中的配置,下面我們來看下 sharding-jdbc-spring-boot-starter
中的類,
SpringBootShardingRuleConfigurationProperties
獲取配置
@ConfigurationProperties(prefix = "sharding.jdbc.config.sharding")
public class SpringBootShardingRuleConfigurationProperties extends YamlShardingRuleConfiguration {
}
@ConfigurationProperties
註解修飾的類會將配置檔案中的屬性名稱匹配的的值繫結到類的屬性欄位上。
SpringBootConfiguration
獲取資料來源及分庫分表策略
@Configuration
@EnableConfigurationProperties({SpringBootShardingRuleConfigurationProperties.class, SpringBootMasterSlaveRuleConfigurationProperties.class})
public class SpringBootConfiguration implements EnvironmentAware {
@Autowired
private SpringBootShardingRuleConfigurationProperties shardingProperties;
/**
* 1. 實現 EnvironmentAware 介面,獲取配置資訊
* 2. 根據配置檔案資訊獲取資料來源配置
*/
@Override
public void setEnvironment(final Environment environment) {
setDataSourceMap(environment);
}
@SuppressWarnings("unchecked")
private void setDataSourceMap(final Environment environment) {
String prefix = "sharding.jdbc.datasource.";
String dataSources = environment.getProperty(prefix + "names");
// 獲取資料來源並存入 dataSourceMap 中
for (String each : dataSources.split(",")) {
try {
Map<String, Object> dataSourceProps = PropertyUtil.handle(environment, prefix + each, Map.class);
Preconditions.checkState(!dataSourceProps.isEmpty(), "Wrong datasource properties!");
DataSource dataSource = DataSourceUtil.getDataSource(dataSourceProps.get("type").toString(), dataSourceProps);
dataSourceMap.put(each, dataSource);
} catch (final ReflectiveOperationException ex) {
throw new ShardingException("Can't find datasource type!", ex);
}
}
}
@Bean
public DataSource dataSource() throws SQLException {
// 獲取資料來源資訊
return null == masterSlaveProperties.getMasterDataSourceName()
? ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingProperties.getShardingRuleConfiguration(), shardingProperties.getConfigMap(), shardingProperties.getProps())
: MasterSlaveDataSourceFactory.createDataSource(dataSourceMap, masterSlaveProperties.getMasterSlaveRuleConfiguration(), masterSlaveProperties.getConfigMap());
}
@EnableConfigurationProperties
可以使被@ConfigurationProperties
註解的類生效;- 實現
EnvironmentAware
介面,可以通過Environment
獲取配置檔案中的配置資訊; @Bean
註解的dataSource
方法會去真正的解析並封裝配置資料,即通過shardingProperties.getShardingRuleConfiguration()
。
接下來看如何獲取路由規則配置
public class YamlShardingRuleConfiguration {
/**
* ...... 省略部分屬性定義
*/
private Map<String, YamlTableRuleConfiguration> tables = new LinkedHashMap<>();
/**
* 將 YamlShardingRuleConfiguration 轉為 ShardingRuleConfiguration
*/
public ShardingRuleConfiguration getShardingRuleConfiguration() {
ShardingRuleConfiguration result = new ShardingRuleConfiguration();
result.setDefaultDataSourceName(defaultDataSourceName);
// 遍歷從配置檔案中繫結的 tables 物件
for (Entry<String, YamlTableRuleConfiguration> entry : tables.entrySet()) {
YamlTableRuleConfiguration tableRuleConfig = entry.getValue();
tableRuleConfig.setLogicTable(entry.getKey());
// 將構建完的 tableRuleConfig 新增到 tableRuleConfigs 中
result.getTableRuleConfigs().add(tableRuleConfig.build());
}
// 獲取繫結表
result.getBindingTableGroups().addAll(bindingTables);
// 獲取預設分庫策略
if (null != defaultDatabaseStrategy) {
result.setDefaultDatabaseShardingStrategyConfig(defaultDatabaseStrategy.build());
}
// 獲取預設分表策略
if (null != defaultTableStrategy) {
result.setDefaultTableShardingStrategyConfig(defaultTableStrategy.build());
}
// 獲取預設主鍵生成器
if (null != defaultKeyGeneratorClassName) {
result.setDefaultKeyGenerator(KeyGeneratorFactory.newInstance(defaultKeyGeneratorClassName));
}
// 獲取主從路由配置
Collection<MasterSlaveRuleConfiguration> masterSlaveRuleConfigs = new LinkedList<>();
for (Entry<String, YamlMasterSlaveRuleConfiguration> entry : masterSlaveRules.entrySet()) {
YamlMasterSlaveRuleConfiguration each = entry.getValue();
each.setName(entry.getKey());
masterSlaveRuleConfigs.add(entry.getValue().getMasterSlaveRuleConfiguration());
}
result.setMasterSlaveRuleConfigs(masterSlaveRuleConfigs);
return result;
}
}
在上面的 getShardingRuleConfiguration
中,關鍵是在 tables
的遍歷上面,會逐一構建各個表的路由規則。
迴圈呼叫 tableRuleConfig.build()
構建表的規則配置:
public TableRuleConfiguration build() {
Preconditions.checkNotNull(logicTable, "Logic table cannot be null.");
// 表配置物件
TableRuleConfiguration result = new TableRuleConfiguration();
result.setLogicTable(logicTable);
result.setActualDataNodes(actualDataNodes);
// 設定分庫策略
if (null != databaseStrategy) {
// 構建分庫策略
result.setDatabaseShardingStrategyConfig(databaseStrategy.build());
}
// 設定分表策略
if (null != tableStrategy) {
result.setTableShardingStrategyConfig(tableStrategy.build());
}
if (!Strings.isNullOrEmpty(keyGeneratorClassName)) {
result.setKeyGenerator(KeyGeneratorFactory.newInstance(keyGeneratorClassName));
}
result.setKeyGeneratorColumnName(keyGeneratorColumnName);
result.setLogicIndex(logicIndex);
return result;
}
在構建表配置時,會去封裝邏輯表名,實際節點以及分庫分表策略。
構建分庫分表策略databaseStrategy.build()
:
public ShardingStrategyConfiguration build() {
int shardingStrategyConfigCount = 0;
ShardingStrategyConfiguration result = null;
// 首先獲取標準策略配置
if (null != standard) {
shardingStrategyConfigCount++;
// 只建立精確分片演算法
if (null == standard.getRangeAlgorithmClassName()) {
result = new StandardShardingStrategyConfiguration(standard.getShardingColumn(),
ShardingAlgorithmFactory.newInstance(standard.getPreciseAlgorithmClassName(), PreciseShardingAlgorithm.class));
} else {
// 建立包含範圍和精確的分片演算法
result = new StandardShardingStrategyConfiguration(standard.getShardingColumn(),
ShardingAlgorithmFactory.newInstance(standard.getPreciseAlgorithmClassName(), PreciseShardingAlgorithm.class),
ShardingAlgorithmFactory.newInstance(standard.getRangeAlgorithmClassName(), RangeShardingAlgorithm.class));
}
}
// 複雜分片演算法
if (null != complex) {
shardingStrategyConfigCount++;
result = new ComplexShardingStrategyConfiguration(complex.getShardingColumns(), ShardingAlgorithmFactory.newInstance(complex.getAlgorithmClassName(), ComplexKeysShardingAlgorithm.class));
}
// 內聯表示式分片演算法
if (null != inline) {
shardingStrategyConfigCount++;
result = new InlineShardingStrategyConfiguration(inline.getShardingColumn(), inline.getAlgorithmExpression());
}
// 暗示分片演算法,需要根據業務場景手工設定
if (null != hint) {
shardingStrategyConfigCount++;
result = new HintShardingStrategyConfiguration(ShardingAlgorithmFactory.newInstance(hint.getAlgorithmClassName(), HintShardingAlgorithm.class));
}
if (null != none) {
shardingStrategyConfigCount++;
result = new NoneShardingStrategyConfiguration();
}
// 斷言分庫或分表最多隻能含有一個演算法,配置多個分片演算法將丟擲異常
Preconditions.checkArgument(shardingStrategyConfigCount <= 1, "Only allowed 0 or 1 sharding strategy configuration.");
return result;
}
通過以上步驟,就已經將分庫分表的所有配置資訊封裝完成,接下來將會初始化 ShardingDataSource
,在初始化 ShardingDataSource
時會校驗資料庫表欄位索引完成性及建立 ShardingContext
。
校驗資料庫完成性
回到 SpringBootConfiguration
的 dataSource
方法上
@Bean
public DataSource dataSource() throws SQLException {
return null == masterSlaveProperties.getMasterDataSourceName()
? ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingProperties.getShardingRuleConfiguration(), shardingProperties.getConfigMap(), shardingProperties.getProps())
: MasterSlaveDataSourceFactory.createDataSource(dataSourceMap, masterSlaveProperties.getMasterSlaveRuleConfiguration(), masterSlaveProperties.getConfigMap());
}
在執行 ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingProperties.getShardingRuleConfiguration(), shardingProperties.getConfigMap(), shardingProperties.getProps())
時會去初始化 ShardingDataSource
,接下來看下 ShardingDataSource
的建構函式:
public ShardingDataSource(final Map<String, DataSource> dataSourceMap, final ShardingRule shardingRule, final Map<String, Object> configMap, final Properties props) throws SQLException {
super(dataSourceMap.values());
if (!configMap.isEmpty()) {
ConfigMapContext.getInstance().getShardingConfig().putAll(configMap);
}
shardingProperties = new ShardingProperties(null == props ? new Properties() : props);
// 從環境變數中獲取工作執行緒數,預設是虛擬機器的處理器數量
int executorSize = shardingProperties.getValue(ShardingPropertiesConstant.EXECUTOR_SIZE);
// 建立執行引擎執行緒池
executorEngine = new ExecutorEngine(executorSize);
ShardingMetaData shardingMetaData = new JDBCShardingMetaData(dataSourceMap, shardingRule, getDatabaseType());
// 初始化 ShardingMetaData 並進行校驗
shardingMetaData.init(shardingRule);
boolean showSQL = shardingProperties.getValue(ShardingPropertiesConstant.SQL_SHOW);
// 建立上下文
shardingContext = new ShardingContext(dataSourceMap, shardingRule, getDatabaseType(), executorEngine, shardingMetaData, showSQL);
}
在 ShardingDataSource
建構函式中會進行一系列的初始化,其中包括 init
方法:
public void init(final ShardingRule shardingRule) throws SQLException {
tableMetaDataMap = new HashMap<>(shardingRule.getTableRules().size(), 1);
for (TableRule each : shardingRule.getTableRules()) {
refresh(each, shardingRule);
}
}
init
方法會迴圈遍歷 tableRules
,獲取表的後設資料,具體實現在 refresh 中,refresh
方法會呼叫 getTableMetaData
方法來獲取表後設資料並進行表結構對比:
private TableMetaData getTableMetaData(final String logicTableName, final List<DataNode> actualDataNodes,
final ShardingDataSourceNames shardingDataSourceNames, final Map<String, Connection> connectionMap) throws SQLException {
Collection<ColumnMetaData> result = null;
for (DataNode each : actualDataNodes) {
Collection<ColumnMetaData> columnMetaDataList = getColumnMetaDataList(each, shardingDataSourceNames, connectionMap);
// 將第一次迴圈獲得的 columnMetaDataList 設定給 result
if (null == result) {
result = columnMetaDataList;
}
// 並以第一次的作為參照與其他分表進行對比
if (!result.equals(columnMetaDataList)) {
// 當表結構不一致時丟擲異常
throw new ShardingException(getErrorMsgOfTableMetaData(logicTableName, result, columnMetaDataList));
}
}
return new TableMetaData(result);
}
那麼是根據什麼判斷表結構不一致的呢?我們看下 ColumnMetaData
有哪些元素便知:
public final class ColumnMetaData {
private final String columnName;
private final String columnType;
private final String keyType;
}
- 從
ColumnMetaData
的資料結構上,我們看到,只有當列名稱、列型別(包括長度)、索引型別完全一致時,才認為表結構是完成的。 - 在獲得表的後設資料後,將後設資料封裝在
ShardingMetaData.tableMetaDataMap
中並依次返,至此ShardingDataSource
建立完成,Sharding-JDBC 的啟動流程已全部完成。
總結
- Sharding-JDBC 啟動時主要是拉取分庫分表的配置,包括資料來源、分片策略,並校驗配置是否有誤;
- 同時,會校驗邏輯表在實際節點的表結構是否一致,通過列名稱、列型別與 Key 型別進行判斷,全部相同時才認為表結構一致;
- 封裝表後設資料並建立
ShardingDataSource
資料來源物件。
擴充套件:Mysql 表結構中的 Key 有哪些及含義是什麼?
Key 的含義:表示該列是否被索引
Key 型別 | 含義 |
---|---|
“” | 該列沒有索引或屬於組合索引中的輔助索引 |
PRI | 該列是主鍵索引或是組合主鍵索引之一的列 |
UNI | 該列是唯一索引的第一列(唯一索引允許空值) |
MUL | 該列是非唯一索引的第一列,也就是普通索引 |
- 如果多個 Key 型別應用於表的給定列,則 Key 按 PRI、UNI、MUL; 的順序顯示優先順序最高的一個;
- 如果一個唯一索引不能包含空值並且表中沒有主鍵,那麼它可以顯示為PRI;
- 如果多個列組成一個複合唯一索引,則唯一索引可能顯示為MUL,儘管這些列的組合是唯一的,但每列仍然可以包含給定值的多個引用。
相關文章
- Tomcat原始碼分析--啟動流程Tomcat原始碼
- Flutter啟動流程原始碼分析Flutter原始碼
- Activity啟動流程原始碼分析原始碼
- apiserver原始碼分析——啟動流程APIServer原始碼
- Android Activity啟動流程原始碼分析Android原始碼
- Android原始碼分析:Activity啟動流程Android原始碼
- Netty啟動流程及原始碼分析Netty原始碼
- containerd 原始碼分析:啟動註冊流程AI原始碼
- Apache Flink原始碼分析---JobManager啟動流程Apache原始碼
- 以太坊原始碼分析(39)geth啟動流程分析原始碼
- Spring原始碼解析02:Spring IOC容器之XmlBeanFactory啟動流程分析和原始碼解析Spring原始碼XMLBean
- Android系統原始碼分析--Service啟動流程Android原始碼
- Android系統原始碼分析–Service啟動流程Android原始碼
- ThinkPHP6.0 原始碼分析之啟動分析PHP原始碼
- SpringBoot2 | SpringBoot啟動流程原始碼分析(一)Spring Boot原始碼
- Scrapy原始碼閱讀分析_2_啟動流程原始碼
- Android 系統原始碼-1:Android 系統啟動流程原始碼分析Android原始碼
- 原始碼閱讀之Activity啟動與App啟動流程 – Android 9.0原始碼APPAndroid
- 原始碼閱讀之Activity啟動與App啟動流程 - Android 9.0原始碼APPAndroid
- SpringBoot一站式啟動流程原始碼分析Spring Boot原始碼
- Sharding-JDBC 原始碼之 SQL 解析JDBC原始碼SQL
- SpringBoot原始碼學習4——SpringBoot內嵌Tomcat啟動流程原始碼分析Spring Boot原始碼Tomcat
- NioEventLoop啟動流程原始碼解析OOP原始碼
- 【zookeeper原始碼】啟動流程詳解原始碼
- SpringBoot原始碼解析-啟動流程(二)Spring Boot原始碼
- SpringBoot原始碼解析-啟動流程(一)Spring Boot原始碼
- React Native Android 原始碼分析之啟動過程React NativeAndroid原始碼
- SpringBoot配置外部Tomcat專案啟動流程原始碼分析(長文)Spring BootTomcat原始碼
- Android原始碼分析之View繪製流程Android原始碼View
- Spring5深度原始碼分析(三)之AnnotationConfigApplicationContext啟動原理分析Spring原始碼APPContext
- netty原始碼分析之服務端啟動全解析Netty原始碼服務端
- 死磕以太坊原始碼分析之挖礦流程分析原始碼
- ThinkPHP6 原始碼分析之請求流程PHP原始碼
- Android8.1 SystemUI原始碼分析之 Notification流程AndroidSystemUI原始碼
- 資料庫中介軟體 Sharding-JDBC 原始碼分析 —— 事務(一)之BED資料庫JDBC原始碼
- [原始碼分析] 訊息佇列 Kombu 之 啟動過程原始碼佇列
- nodejs啟動流程分析NodeJS
- FlutterApp啟動流程分析FlutterAPP