1.Sharding-JDBC快速入門
1.引入maven依賴
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-core</artifactId>
<version>${latest.release.version}</version>
</dependency>
注意: 請將${latest.release.version}更改為實際的版本號。
2.規則配置
Sharding-JDBCk可以通過Java、YAML、Spring命名空間和Spring Boot Starter四種方式配置,開發者可根據場景選擇適合的配置方式。
- Java配置
配置項説明
數據分片 ShardingDataSourceFactory
數據分片的數據源創建工廠
|
名稱
|
數據類型
|
説明
|
|
dataSourceMap
|
Map<String, DataSource>
|
數據源配置
|
|
shardingRuleConfig
|
ShardingRuleConfiguration
|
數據分片配置規則
|
|
props (?)
|
Properties
|
屬性配置
|
ShardingRuleConfiguration
分片規則配置對象
|
名稱
|
數據類型
|
説明
|
|
tableRuleConfigs
|
Collection
|
分片規則列表
|
|
bindingTableGroups (?)
|
Collection
|
綁定表規則列表
|
|
broadcastTables (?)
|
Collection
|
廣播表規則列表
|
|
defaultDataSourceName (?)
|
String
|
未配置分片規則的表將通過默認數據源定位
|
|
defaultDatabaseShardingStrategyConfig (?)
|
ShardingStrategyConfiguration
|
默認分庫策略
|
|
defaultTableShardingStrategyConfig (?)
|
ShardingStrategyConfiguration
|
默認分表策略
|
|
defaultKeyGeneratorConfig (?)
|
KeyGeneratorConfiguration
|
默認自增列值生成器配置,缺省將使用org.apache.shardingsphere.core.keygen.generator.impl.SnowflakeKeyGenerator
|
|
masterSlaveRuleConfigs (?)
|
Collection
|
讀寫分離規則,缺省表示不使用讀寫分離
|
TableRuleConfiguration
表封片規則配置對象
|
名稱
|
數據類型
|
説明
|
|
logicTable
|
String
|
邏輯表名稱
|
|
actualDataNodes (?)
|
String
|
由數據源名 + 表名組成,以小數點分隔。多個表以逗號分隔,支持inline表達式。缺省表示使用已知數據源與邏輯表名稱生成數據節點,用於廣播表(即每個庫中都需要一個同樣的表用於關聯查詢,多為字典表)或只分庫不分表且所有庫的表結構完全一致的情況
|
|
databaseShardingStrategyConfig (?)
|
ShardingStrategyConfiguration
|
分庫策略,缺省表示使用默認分庫策略
|
|
tableShardingStrategyConfig (?)
|
ShardingStrategyConfiguration
|
分表策略,缺省表示使用默認分表策略
|
|
keyGeneratorConfig (?)
|
KeyGeneratorConfiguration
|
自增列值生成器配置,缺省表示使用默認自增主鍵生成器
|
|
encryptorConfiguration (?)
|
EncryptorConfiguration
|
加解密生成器配置
|
StandardShardingStrategyConfiguration
ShardingStrategyConfiguration的實現類,用於單分片鍵的標準分片場景
|
名稱
|
數據類型
|
説明
|
|
shardingColumn
|
String
|
分片列名稱
|
|
preciseShardingAlgorithm
|
PreciseShardingAlgorithm
|
精確分片算法,用於=和IN
|
|
rangeShardingAlgorithm (?)
|
RangeShardingAlgorithm
|
範圍分片算法,用於BETWEEN
|
ComplexShardingStrategyConfiguration
ShardingStrategyConfiguration的實現類,用於多分片鍵的複合分片場景
|
名稱
|
數據類型
|
説明
|
|
shardingColumns
|
String
|
分片列名稱,多個列以逗號分隔
|
|
shardingAlgorithm
|
ComplexKeysShardingAlgorithm
|
複合分片算法
|
InlineShardingStrategyConfiguration
ShardingStrategyConfiguration的實現類,用於配置行表達式分片策略
|
名稱
|
數據類型
|
説明
|
|
shardingColumn
|
String
|
分片列名稱
|
|
algorithmExpression
|
String
|
分片算法行表達式,需符合groovy語法,詳情請參考
|
HintShardingStrategyConfiguration
ShardingStrategyConfiguration的實現類,用於配置Hint方式分片策略
|
名稱
|
數據類型
|
説明
|
|
shardingAlgorithm
|
HintShardingAlgorithm
|
Hint分片算法
|
NoneShardingStrategyConfiguration
ShardingStrategyConfiguration的實現類,用於配置不分片的策略
KeyGeneratorConfiguration
|
名稱
|
數據類型
|
説明
|
|
column
|
String
|
自增列名稱
|
|
type
|
String
|
自增列值生成器類型,可自定義或選擇內置類型:SNOWFLAKE/UUID
|
|
props
|
Properties
|
自增列值生成器的相關屬性配置
|
Properties
屬性配置項,可以為以下自增列值生成器的屬性
SNOWFLAKE
|
名稱
|
數據類型
|
説明
|
|
worker.id (?)
|
long
|
工作機器唯一id,默認為0
|
|
max.tolerate.time.difference.milliseconds (?)
|
long
|
最大容忍時鐘回退時間,單位:毫秒。默認為10毫秒
|
|
max.vibration.offset (?)
|
int
|
最大抖動上限值,範圍[0, 4096),默認為1。注:若使用此算法生成值作分片值,建議配置此屬性。此算法在不同毫秒內所生成的key取模2^n (2^n一般為分庫或分表數) 之後結果總為0或1。為防止上述分片問題,建議將此屬性值配置為(2^n)-1
|
EncryptRuleConfiguration
|
名稱
|
數據類型
|
説明
|
|
encryptors
|
Map<String, EncryptorRuleConfiguration>
|
加解密器配置列表,可自定義或選擇內置類型:MD5/AES
|
|
tables
|
Map<String, EncryptTableRuleConfiguration>
|
加密表配置列表
|
EncryptorRuleConfiguration
|
名稱
|
數據類型
|
説明
|
|
type
|
String
|
加解密器類型,可自定義或選擇內置類型:MD5/AES
|
|
properties
|
Properties
|
屬性配置, 注意:使用AES加密器,需要配置AES加密器的KEY屬性:aes.key.value
|
EncryptTableRuleConfiguration
|
名稱
|
數據類型
|
説明
|
|
tables
|
Map<String, EncryptColumnRuleConfiguration>
|
加密列配置列表
|
EncryptColumnRuleConfiguration
|
名稱
|
數據類型
|
説明
|
|
plainColumn
|
String
|
存儲明文的字段
|
|
cipherColumn
|
String
|
存儲密文的字段
|
|
assistedQueryColumn
|
String
|
輔助查詢字段,針對ShardingQueryAssistedEncryptor類型的加解密器進行輔助查詢
|
|
encryptor
|
String
|
加解密器名字
|
Properties
屬性配置項,可以為以下屬性
|
名稱
|
數據類型
|
説明
|
|
sql.show (?)
|
boolean
|
是否開啓SQL顯示,默認值: false
|
|
executor.size (?)
|
int
|
工作線程數量,默認值: CPU核數
|
|
max.connections.size.per.query (?)
|
int
|
每個物理數據庫為每次查詢分配的最大連接數量。默認值: 1
|
|
check.table.metadata.enabled (?)
|
boolean
|
是否在啓動時檢查分表元數據一致性,默認值: false
|
|
query.with.cipher.column (?)
|
boolean
|
當存在明文列時,是否使用密文列查詢,默認值: true
|
|
allow.range.query.with.inline.sharding (?)
|
boolean
|
當使用inline分表策略時,是否允許範圍查詢,默認值: false
|
- Yaml配置
數據分片
dataSources: #數據源配置,可配置多個data_source_name
<data_source_name>: #<!!數據庫連接池實現類> `!!`表示實例化該類
driverClassName: #數據庫驅動類名
url: #數據庫url連接
username: #數據庫用户名
password: #數據庫密碼
# ... 數據庫連接池的其它屬性
shardingRule:
tables: #數據分片規則配置,可配置多個logic_table_name
<logic_table_name>: #邏輯表名稱
actualDataNodes: #由數據源名 + 表名組成,以小數點分隔。多個表以逗號分隔,支持inline表達式。缺省表示使用已知數據源與邏輯表名稱生成數據節點,用於廣播表(即每個庫中都需要一個同樣的表用於關聯查詢,多為字典表)或只分庫不分表且所有庫的表結構完全一致的情況
databaseStrategy: #分庫策略,缺省表示使用默認分庫策略,以下的分片策略只能選其一
standard: #用於單分片鍵的標準分片場景
shardingColumn: #分片列名稱
preciseAlgorithmClassName: #精確分片算法類名稱,用於=和IN。。該類需實現PreciseShardingAlgorithm接口並提供無參數的構造器
rangeAlgorithmClassName: #範圍分片算法類名稱,用於BETWEEN,可選。。該類需實現RangeShardingAlgorithm接口並提供無參數的構造器
complex: #用於多分片鍵的複合分片場景
shardingColumns: #分片列名稱,多個列以逗號分隔
algorithmClassName: #複合分片算法類名稱。該類需實現ComplexKeysShardingAlgorithm接口並提供無參數的構造器
inline: #行表達式分片策略
shardingColumn: #分片列名稱
algorithmInlineExpression: #分片算法行表達式,需符合groovy語法
hint: #Hint分片策略
algorithmClassName: #Hint分片算法類名稱。該類需實現HintShardingAlgorithm接口並提供無參數的構造器
none: #不分片
tableStrategy: #分表策略,同分庫策略
keyGenerator:
column: #自增列名稱,缺省表示不使用自增主鍵生成器
type: #自增列值生成器類型,缺省表示使用默認自增列值生成器。可使用用户自定義的列值生成器或選擇內置類型:SNOWFLAKE/UUID
props: #屬性配置, 注意:使用SNOWFLAKE算法,需要配置worker.id與max.tolerate.time.difference.milliseconds屬性。若使用此算法生成值作分片值,建議配置max.vibration.offset屬性
<property-name>: 屬性名稱
bindingTables: #綁定表規則列表
- <logic_table_name1, logic_table_name2, ...>
- <logic_table_name3, logic_table_name4, ...>
- <logic_table_name_x, logic_table_name_y, ...>
broadcastTables: #廣播表規則列表
- table_name1
- table_name2
- table_name_x
defaultDataSourceName: #未配置分片規則的表將通過默認數據源定位
defaultDatabaseStrategy: #默認數據庫分片策略,同分庫策略
defaultTableStrategy: #默認表分片策略,同分庫策略
defaultKeyGenerator: #默認的主鍵生成算法 如果沒有設置,默認為SNOWFLAKE算法
type: #默認自增列值生成器類型,缺省將使用org.apache.shardingsphere.core.keygen.generator.impl.SnowflakeKeyGenerator。可使用用户自定義的列值生成器或選擇內置類型:SNOWFLAKE/UUID
props:
<property-name>: #自增列值生成器屬性配置, 比如SNOWFLAKE算法的worker.id與max.tolerate.time.difference.milliseconds
masterSlaveRules: #讀寫分離規則,詳見讀寫分離部分
<data_source_name>: #數據源名稱,需要與真實數據源匹配,可配置多個data_source_name
masterDataSourceName: #詳見讀寫分離部分
slaveDataSourceNames: #詳見讀寫分離部分
loadBalanceAlgorithmType: #詳見讀寫分離部分
props: #讀寫分離負載算法的屬性配置
<property-name>: #屬性值
props: #屬性配置
sql.show: #是否開啓SQL顯示,默認值: false
executor.size: #工作線程數量,默認值: CPU核數
max.connections.size.per.query: # 每個查詢可以打開的最大連接數量,默認為1
check.table.metadata.enabled: #是否在啓動時檢查分表元數據一致性,默認值: false
- Spring Boot配置
配置項
數據分片
spring.shardingsphere.datasource.names= #數據源名稱,多數據源以逗號分隔
spring.shardingsphere.datasource.<data-source-name>.type= #數據庫連接池類名稱
spring.shardingsphere.datasource.<data-source-name>.driver-class-name= #數據庫驅動類名
spring.shardingsphere.datasource.<data-source-name>.url= #數據庫url連接
spring.shardingsphere.datasource.<data-source-name>.username= #數據庫用户名
spring.shardingsphere.datasource.<data-source-name>.password= #數據庫密碼
spring.shardingsphere.datasource.<data-source-name>.xxx= #數據庫連接池的其它屬性
spring.shardingsphere.sharding.tables.<logic-table-name>.actual-data-nodes= #由數據源名 + 表名組成,以小數點分隔。多個表以逗號分隔,支持inline表達式。缺省表示使用已知數據源與邏輯表名稱生成數據節點,用於廣播表(即每個庫中都需要一個同樣的表用於關聯查詢,多為字典表)或只分庫不分表且所有庫的表結構完全一致的情況
#分庫策略,缺省表示使用默認分庫策略,以下的分片策略只能選其一
#用於單分片鍵的標準分片場景
spring.shardingsphere.sharding.tables.<logic-table-name>.database-strategy.standard.sharding-column= #分片列名稱
spring.shardingsphere.sharding.tables.<logic-table-name>.database-strategy.standard.precise-algorithm-class-name= #精確分片算法類名稱,用於=和IN。該類需實現PreciseShardingAlgorithm接口並提供無參數的構造器
spring.shardingsphere.sharding.tables.<logic-table-name>.database-strategy.standard.range-algorithm-class-name= #範圍分片算法類名稱,用於BETWEEN,可選。該類需實現RangeShardingAlgorithm接口並提供無參數的構造器
#用於多分片鍵的複合分片場景
spring.shardingsphere.sharding.tables.<logic-table-name>.database-strategy.complex.sharding-columns= #分片列名稱,多個列以逗號分隔
spring.shardingsphere.sharding.tables.<logic-table-name>.database-strategy.complex.algorithm-class-name= #複合分片算法類名稱。該類需實現ComplexKeysShardingAlgorithm接口並提供無參數的構造器
#行表達式分片策略
spring.shardingsphere.sharding.tables.<logic-table-name>.database-strategy.inline.sharding-column= #分片列名稱
spring.shardingsphere.sharding.tables.<logic-table-name>.database-strategy.inline.algorithm-expression= #分片算法行表達式,需符合groovy語法
#Hint分片策略
spring.shardingsphere.sharding.tables.<logic-table-name>.database-strategy.hint.algorithm-class-name= #Hint分片算法類名稱。該類需實現HintShardingAlgorithm接口並提供無參數的構造器
#分表策略,同分庫策略
spring.shardingsphere.sharding.tables.<logic-table-name>.table-strategy.xxx= #省略
spring.shardingsphere.sharding.tables.<logic-table-name>.key-generator.column= #自增列名稱,缺省表示不使用自增主鍵生成器
spring.shardingsphere.sharding.tables.<logic-table-name>.key-generator.type= #自增列值生成器類型,缺省表示使用默認自增列值生成器。可使用用户自定義的列值生成器或選擇內置類型:SNOWFLAKE/UUID
spring.shardingsphere.sharding.tables.<logic-table-name>.key-generator.props.<property-name>= #屬性配置, 注意:使用SNOWFLAKE算法,需要配置worker.id與max.tolerate.time.difference.milliseconds屬性。若使用此算法生成值作分片值,建議配置max.vibration.offset屬性
spring.shardingsphere.sharding.binding-tables[0]= #綁定表規則列表
spring.shardingsphere.sharding.binding-tables[1]= #綁定表規則列表
spring.shardingsphere.sharding.binding-tables[x]= #綁定表規則列表
spring.shardingsphere.sharding.broadcast-tables[0]= #廣播表規則列表
spring.shardingsphere.sharding.broadcast-tables[1]= #廣播表規則列表
spring.shardingsphere.sharding.broadcast-tables[x]= #廣播表規則列表
spring.shardingsphere.sharding.default-data-source-name= #未配置分片規則的表將通過默認數據源定位
spring.shardingsphere.sharding.default-database-strategy.xxx= #默認數據庫分片策略,同分庫策略
spring.shardingsphere.sharding.default-table-strategy.xxx= #默認表分片策略,同分表策略
spring.shardingsphere.sharding.default-key-generator.type= #默認自增列值生成器類型,缺省將使用org.apache.shardingsphere.core.keygen.generator.impl.SnowflakeKeyGenerator。可使用用户自定義的列值生成器或選擇內置類型:SNOWFLAKE/UUID
spring.shardingsphere.sharding.default-key-generator.props.<property-name>= #自增列值生成器屬性配置, 比如SNOWFLAKE算法的worker.id與max.tolerate.time.difference.milliseconds
spring.shardingsphere.sharding.master-slave-rules.<master-slave-data-source-name>.master-data-source-name= #詳見讀寫分離部分
spring.shardingsphere.sharding.master-slave-rules.<master-slave-data-source-name>.slave-data-source-names[0]= #詳見讀寫分離部分
spring.shardingsphere.sharding.master-slave-rules.<master-slave-data-source-name>.slave-data-source-names[1]= #詳見讀寫分離部分
spring.shardingsphere.sharding.master-slave-rules.<master-slave-data-source-name>.slave-data-source-names[x]= #詳見讀寫分離部分
spring.shardingsphere.sharding.master-slave-rules.<master-slave-data-source-name>.load-balance-algorithm-class-name= #詳見讀寫分離部分
spring.shardingsphere.sharding.master-slave-rules.<master-slave-data-source-name>.load-balance-algorithm-type= #詳見讀寫分離部分
spring.shardingsphere.props.sql.show= #是否開啓SQL顯示,默認值: false
spring.shardingsphere.props.executor.size= #工作線程數量,默認值: CPU核數
2核心概念
2.1數據分片
核心概念
SQL
- 邏輯表
水平拆分的數據庫(表)的相同邏輯和數據結構表的總稱。例:訂單數據根據主鍵尾數拆分為10張表,分別是t_order_0到t_order_9,他們的邏輯表名為t_order - 真實表
在分片的數據庫中真實存在的物理表。即上個示例中的t_order_0到t_order_9 - 數據節點
數據分片的最小單元。由數據源名稱和數據表組成,例:ds_0.t_order_0 - 綁定表
指分片規則一致的主表和子表。例如:t_order表和t_order_item表,均按照order_id分片,則此兩張表互為綁定表關係。綁定表之間的多表關聯查詢不會出現笛卡爾積關聯,關聯查詢效率將大大提升。舉例説明,如果SQL為:
SELECT i.* FROM t_order o JOIN t_order_item i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
- 廣播表
指所有的分片數據源中都存在的表,表結構和表中的數據在每個數據庫中均完全一致。適用於數據量不大且需要與海量數據的表進行關聯查詢的場景,例如:字典表
分片
- 分片鍵
用於分片的數據庫字段,是將數據庫(表)水平拆分的關鍵字段。例:將訂單表中的訂單主鍵的尾數取模分片, 則訂單主鍵為分片字段。 SQL中如果無分片字段,將執行全路由,性能較差。 除了對單分片字段的支持,ShardingSphere也支持根據多個字段進行分片 - 分片算法
通過分片算法將數據分片,支持通過=、>=、<=、>、<、BETWEEN和IN分片。分片算法需要應用方開發者自行實現,可實現的靈活度非常高。
目前提供4種分片算法。由於分片算法和業務實現緊密相關,因此並未提供內置分片算法,而是通過分片策略將各種場景提煉出來,提供更高層級的抽象,並提供接口讓應用開發者自行實現分片算法。
- 精確分片算法
對應PreciseShardingAlgorithm,用於處理使用單一鍵作為分片鍵的=與IN進行分片的場景。需要配合StandardShardingStrategy使用 - 範圍分片算法
對應RangeShardingAlgorithm,用於處理使用單一鍵作為分片鍵的BETWEEN AND、>、<、>=、<=進行分片的場景。需要配合StandardShardingStrategy使用 - 複合分片算法
對應ComplexKeysShardingAlgorithm,用於處理使用多鍵作為分片鍵進行分片的場景,包含多個分片鍵的邏輯較複雜,需要應用開發者自行處理其中的複雜度。需要配合ComplexShardingStrategy使用 - Hint分片算法
對應HintShardingAlgorithm,用於處理使用Hint行分片的場景。需要配合HintShardingStrategy使用
- 分片策略
- 標準分片策略
對應StandardShardingStrategy。提供對SQL語句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支持。StandardShardingStrategy只支持單分片鍵,提供PreciseShardingAlgorithm和RangeShardingAlgorithm兩個分片算法。PreciseShardingAlgorithm是必選的,用於處理=和IN的分片。RangeShardingAlgorithm是可選的,用於處理BETWEEN AND, >, <, >=, <=分片,如果不配置RangeShardingAlgorithm,SQL中的BETWEEN AND將按照全庫路由處理 - 複合分片策略
對應ComplexShardingStrategy。複合分片策略。提供對SQL語句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支持。ComplexShardingStrategy支持多分片鍵,由於多分片鍵之間的關係複雜,因此並未進行過多的封裝,而是直接將分片鍵值組合以及分片操作符透傳至分片算法,完全由應用開發者實現,提供最大的靈活度 - 行表達式分片策略
對應InlineShardingStrategy。使用Groovy的表達式,提供對SQL語句中的=和IN的分片操作支持,只支持單分片鍵。對於簡單的分片算法,可以通過簡單的配置使用,從而避免繁瑣的Java代碼開發,如:t_user_$->{u_id % 8}表示t_user表根據u_id模8,而分成8張表,表名稱為t_user_0到t_user_7 - Hint分片策略
對應HintShardingStrategy。通過Hint指定分片值而非從SQL中提取分片值的方式進行分片的策略 - 不分片策略
對應NoneShardingStrategy。不分片的策略
- SQL Hint
對於分片字段非SQL決定,而由其他外置條件決定的場景,可使用SQL Hint靈活的注入分片字段。例:內部系統,按照員工登錄主鍵分庫,而數據庫中並無此字段。SQL Hint支持通過Java API和SQL註釋(待實現)兩種方式使用
2.2 內核剖析
解析引擎
抽象語法樹
解析過程分為詞法解析和語法解析。 詞法解析器用於將SQL拆解為不可再分的原子符號,稱為Token。並根據不同數據庫方言所提供的字典,將其歸類為關鍵字,表達式,字面量和操作符。 再使用語法解析器將SQL轉換為抽象語法樹
例如,以下SQL:
SELECT id, name FROM t_user WHERE status = 'ACTIVE' AND age > 18
解析之後的為抽象語法樹見下圖:
最後,通過對抽象語法樹的遍歷去提煉分片所需的上下文,並標記有可能需要改寫的位置。 供分片使用的解析上下文包含查詢選擇項(Select Items)、表信息(Table)、分片條件(Sharding Condition)、自增主鍵信息(Auto increment Primary Key)、排序信息(Order By)、分組信息(Group By)以及分頁信息(Limit、Rownum、Top)。 SQL的一次解析過程是不可逆的,一個個Token的按SQL原本的順序依次進行解析,性能很高。 考慮到各種數據庫SQL方言的異同,在解析模塊提供了各類數據庫的SQL方言字典
SQL解析引擎
SQL解析作為分庫分表類產品的核心,其性能和兼容性是最重要的衡量指標。 ShardingSphere的SQL解析器經歷了3代產品的更新迭代。
第一代SQL解析器為了追求性能與快速實現,在1.4.x之前的版本使用Druid作為SQL解析器。經實際測試,它的性能遠超其它解析器。
第二代SQL解析器從1.5.x版本開始,ShardingSphere採用完全自研的SQL解析引擎。 由於目的不同,ShardingSphere並不需要將SQL轉為一顆完全的抽象語法樹,也無需通過訪問器模式進行二次遍歷。它採用對SQL半理解的方式,僅提煉數據分片需要關注的上下文,因此SQL解析的性能和兼容性得到了進一步的提高。
第三代SQL解析器則從3.0.x版本開始,ShardingSphere嘗試使用ANTLR作為SQL解析的引擎,並計劃根據DDL -> TCL -> DAL –> DCL -> DML –>DQL這個順序,依次替換原有的解析引擎,目前仍處於替換迭代中。 使用ANTLR的原因是希望ShardingSphere的解析引擎能夠更好的對SQL進行兼容。對於複雜的表達式、遞歸、子查詢等語句,雖然ShardingSphere的分片核心並不關注,但是會影響對於SQL理解的友好度。 經過實例測試,ANTLR解析SQL的性能比自研的SQL解析引擎慢3-10倍左右。為了彌補這一差距,ShardingSphere將使用PreparedStatement的SQL解析的語法樹放入緩存。 因此建議採用PreparedStatement這種SQL預編譯的方式提升性能。
第三代SQL解析引擎的整體結構劃分如下圖所示:
- 路由引擎
- 分片路由
用於根據分片鍵進行路由的場景,又細分為直接路由、標準路由和笛卡爾積路由這3種類型
- 直接路由
滿足直接路由的條件相對苛刻,它需要通過Hint(使用HintAPI直接指定路由至庫表)方式分片,並且是隻分庫不分表的前提下,則可以避免SQL解析和之後的結果歸併。 因此它的兼容性最好,可以執行包括子查詢、自定義函數等複雜情況的任意SQL。直接路由還可以用於分片鍵不在SQL中的場景。例如,設置用於數據庫分片的鍵為3
hintManager.setDatabaseShardingValue(3);
假如路由算法為value % 2,當一個邏輯庫t_order對應2個真實庫t_order_0和t_order_1時,路由後SQL將在t_order_1上執行。下方是使用API的代碼樣例:
String sql = "SELECT * FROM t_order";
try (
HintManager hintManager = HintManager.getInstance();
Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
hintManager.setDatabaseShardingValue(3);
try (ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) {
//...
}
}
}
- 標準路由
標準路由是ShardingSphere最為推薦使用的分片方式,它的適用範圍是不包含關聯查詢或僅包含綁定表之間關聯查詢的SQL。 當分片運算符是等於號時,路由結果將落入單庫(表),當分片運算符是BETWEEN或IN時,則路由結果不一定落入唯一的庫(表),因此一條邏輯SQL最終可能被拆分為多條用於執行的真實SQL。 舉例説明,如果按照order_id的奇數和偶數進行數據分片,一個單表查詢的SQL如下:
SELECT * FROM t_order WHERE order_id IN (1, 2);
那麼路由的結果應為:
SELECT * FROM t_order_0 WHERE order_id IN (1, 2);
SELECT * FROM t_order_1 WHERE order_id IN (1, 2);
- 笛卡爾路由
笛卡爾路由是最複雜的情況,它無法根據綁定表的關係定位分片規則,因此非綁定表之間的關聯查詢需要拆解為笛卡爾積組合執行。 如果上個示例中的SQL並未配置綁定表關係,那麼路由的結果應為:
SELECT * FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE order_id IN (1, 2);
SELECT * FROM t_order_0 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE order_id IN (1, 2);
SELECT * FROM t_order_1 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE order_id IN (1, 2);
SELECT * FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE order_id IN (1, 2);
笛卡爾路由查詢性能較低,需謹慎使用
- 廣播路由
對於不攜帶分片鍵的SQL,則採取廣播路由的方式。根據SQL類型又可以劃分為全庫表路由、全庫路由、全實例路由、單播路由和阻斷路由這5種類型
- 全庫表路由
全庫表路由用於處理對數據庫中與其邏輯表相關的所有真實表的操作,主要包括不帶分片鍵的DQL和DML,以及DDL等。例如
SELECT * FROM t_order WHERE good_prority IN (1, 10);
則會遍歷所有數據庫中的所有表,逐一匹配邏輯表和真實表名,能夠匹配得上則執行。路由後成為
SELECT * FROM t_order_0 WHERE good_prority IN (1, 10);
SELECT * FROM t_order_1 WHERE good_prority IN (1, 10);
SELECT * FROM t_order_2 WHERE good_prority IN (1, 10);
SELECT * FROM t_order_3 WHERE good_prority IN (1, 10);
- 全庫路由
全庫路由用於處理對數據庫的操作,包括用於庫設置的SET類型的數據庫管理命令,以及TCL這樣的事務控制語句。 在這種情況下,會根據邏輯庫的名字遍歷所有符合名字匹配的真實庫,並在真實庫中執行該命令,例如:
SET autocommit=0;
在t_order中執行,t_order有2個真實庫。則實際會在t_order_0和t_order_1上都執行這個命令
- 全實例路由
全實例路由用於DCL操作,授權語句針對的是數據庫的實例。無論一個實例中包含多少個Schema,每個數據庫的實例只執行一次。例如:
CREATE USER customer@127.0.0.1 identified BY '123';
這個命令將在所有的真實數據庫實例中執行,以確保customer用户可以訪問每一個實例
- 單播路由
單播路由用於獲取某一真實表信息的場景,它僅需要從任意庫中的任意真實表中獲取數據即可。例如:
DESCRIBE t_order;
t_order的兩個真實表t_order_0,t_order_1的描述結構相同,所以這個命令在任意真實表上選擇執行一次
- 阻斷路由
阻斷路由用於屏蔽SQL對數據庫的操作,例如:
USE order_db;
這個命令不會在真實數據庫中執行,因為ShardingSphere採用的是邏輯Schema的方式,無需將切換數據庫Schema的命令發送至數據庫中。
路由引擎的整體結構劃分如下圖:
- 改寫引擎
- 正確性改寫
在包含分表的場景中,需要將分表配置中的邏輯表名稱改寫為路由之後所獲取的真實表名稱。僅分庫則不需要表名稱的改寫。除此之外,還包括補列和分頁信息修正等內容
- 標識符改寫
需要改寫的標識符包括表名稱、索引名稱以及Schema名稱。
表名稱改寫是指將找到邏輯表在原始SQL中的位置,並將其改寫為真實表的過程。表名稱改寫是一個典型的需要對SQL進行解析的場景。 從一個最簡單的例子開始,若邏輯SQL為:
SELECT order_id FROM t_order WHERE order_id=1;
假設該SQL配置分片鍵order_id,並且order_id=1的情況,將路由至分片表1。那麼改寫之後的SQL應該為:
SELECT order_id FROM t_order_1 WHERE order_id=1;
在這種最簡單的SQL場景中,是否將SQL解析為抽象語法樹似乎無關緊要,只要通過字符串查找和替換就可以達到SQL改寫的效果。 但是下面的場景,就無法僅僅通過字符串的查找替換來正確的改寫SQL了:
SELECT order_id FROM t_order WHERE order_id=1 AND remarks=' t_order xxx';
正確改寫的SQL應該是
SELECT order_id FROM t_order_1 WHERE order_id=1 AND remarks=' t_order xxx';
- 補列
需要在查詢語句中補列通常由兩種情況導致。 第一種情況是ShardingSphere需要在結果歸併時獲取相應數據,但該數據並未能通過查詢的SQL返回。 這種情況主要是針對GROUP BY和ORDER BY。結果歸併時,需要根據GROUP BY和ORDER BY的字段項進行分組和排序,但如果原始SQL的選擇項中若並未包含分組項或排序項,則需要對原始SQL進行改寫。 先看一下原始SQL中帶有結果歸併所需信息的場景:
SELECT order_id, user_id FROM t_order ORDER BY user_id;
由於使用user_id進行排序,在結果歸併中需要能夠獲取到user_id的數據,而上面的SQL是能夠獲取到user_id數據的,因此無需補列。
如果選擇項中不包含結果歸併時所需的列,則需要進行補列,如以下SQL:
SELECT order_id FROM t_order ORDER BY user_id;
由於原始SQL中並不包含需要在結果歸併中需要獲取的user_id,因此需要對SQL進行補列改寫。補列之後的SQL是:
SELECT order_id, user_id AS ORDER_BY_DERIVED_0 FROM t_order ORDER BY user_id;
- 分頁修正
從多個數據庫獲取分頁數據與單數據庫的場景是不同的。 假設每10條數據為一頁,取第2頁數據。在分片環境下獲取LIMIT 10, 10,歸併之後再根據排序條件取出前10條數據是不正確的。 舉例説明,若SQL為:
SELECT score FROM t_score ORDER BY score DESC LIMIT 1, 2;
下圖展示了不進行SQL的改寫的分頁執行結果:
通過圖中所示,想要取得兩個表中共同的按照分數排序的第2條和第3條數據,應該是95和90。 由於執行的SQL只能從每個表中獲取第2條和第3條數據,即從t_score_0表中獲取的是90和80;從t_score_0表中獲取的是85和75。 因此進行結果歸併時,只能從獲取的90,80,85和75之中進行歸併,那麼結果歸併無論怎麼實現,都不可能獲得正確的結果。
正確的做法是將分頁條件改寫為LIMIT 0, 3,取出所有前兩頁數據,再結合排序條件計算出正確的數據。 下圖展示了進行SQL改寫之後的分頁執行結果。
越獲取偏移量位置靠後數據,使用LIMIT分頁方式的效率就越低。 有很多方法可以避免使用LIMIT進行分頁。比如構建行記錄數量與行偏移量的二級索引,或使用上次分頁數據結尾ID作為下次查詢條件的分頁方式等。
分頁信息修正時,如果使用佔位符的方式書寫SQL,則只需要改寫參數列表即可,無需改寫SQL本身
- 批量拆分
在使用批量插入的SQL時,如果插入的數據是跨分片的,那麼需要對SQL進行改寫來防止將多餘的數據寫入到數據庫中。 插入操作與查詢操作的不同之處在於,查詢語句中即使用了不存在於當前分片的分片鍵,也不會對數據產生影響;而插入操作則必須將多餘的分片鍵刪除。
INSERT INTO t_order (order_id, xxx) VALUES (1, 'xxx'), (2, 'xxx'), (3, 'xxx');
假設數據庫仍然是按照order_id的奇偶值分為兩片的,僅將這條SQL中的表名進行修改,然後發送至數據庫完成SQL的執行 ,則兩個分片都會寫入相同的記錄。 雖然只有符合分片查詢條件的數據才能夠被查詢語句取出,但存在冗餘數據的實現方案並不合理。因此需要將SQL改寫為:
INSERT INTO t_order_0 (order_id, xxx) VALUES (2, 'xxx');
INSERT INTO t_order_1 (order_id, xxx) VALUES (1, 'xxx'), (3, 'xxx');
2.3 使用規範
- SQL
由於SQL語法靈活複雜,分佈式數據庫和單機數據庫的查詢場景又不完全相同,難免有和單機數據庫不兼容的SQL出現。
本文詳細羅列出已明確可支持的SQL種類以及已明確不支持的SQL種類,儘量讓使用者避免踩坑。
示例
支持的SQL
|
SQL
|
必要條件
|
|
SELECT * FROM tbl_name
|
|
|
SELECT * FROM tbl_name WHERE (col1 = ? or col2 = ?) and col3 = ?
|
|
|
SELECT * FROM tbl_name WHERE col1 = ? ORDER BY col2 DESC LIMIT ?
|
|
|
SELECT COUNT(*), SUM(col1), MIN(col1), MAX(col1), AVG(col1) FROM tbl_name WHERE col1 = ?
|
|
|
SELECT COUNT(col1) FROM tbl_name WHERE col2 = ? GROUP BY col1 ORDER BY col3 DESC LIMIT ?, ?
|
|
|
INSERT INTO tbl_name (col1, col2,…) VALUES (?, ?, ….)
|
|
|
INSERT INTO tbl_name VALUES (?, ?,….)
|
|
|
INSERT INTO tbl_name (col1, col2, …) VALUES (?, ?, ….), (?, ?, ….)
|
|
|
UPDATE tbl_name SET col1 = ? WHERE col2 = ?
|
|
|
DELETE FROM tbl_name WHERE col1 = ?
|
|
|
CREATE TABLE tbl_name (col1 int, …)
|
|
|
ALTER TABLE tbl_name ADD col1 varchar(10)
|
|
|
DROP TABLE tbl_name
|
|
|
TRUNCATE TABLE tbl_name
|
|
|
CREATE INDEX idx_name ON tbl_name
|
|
|
DROP INDEX idx_name ON tbl_name
|
|
|
DROP INDEX idx_name
|
|
|
SELECT DISTINCT * FROM tbl_name WHERE col1 = ?
|
|
|
SELECT COUNT(DISTINCT col1) FROM tbl_name
|
不支持的SQL
|
SQL
|
不支持原因
|
|
INSERT INTO tbl_name (col1, col2, …) VALUES(1+2, ?, …)
|
VALUES語句不支持運算表達式
|
|
INSERT INTO tbl_name (col1, col2, …) SELECT col1, col2, … FROM tbl_name WHERE col3 = ?
|
INSERT … SELECT
|
|
SELECT COUNT(col1) as count_alias FROM tbl_name GROUP BY col1 HAVING count_alias > ?
|
HAVING
|
|
SELECT * FROM tbl_name1 UNION SELECT * FROM tbl_name2
|
UNION
|
|
SELECT * FROM tbl_name1 UNION ALL SELECT * FROM tbl_name2
|
UNION ALL
|
|
SELECT * FROM ds.tbl_name1
|
包含schema
|
|
SELECT SUM(DISTINCT col1), SUM(col1) FROM tbl_name
|
詳見DISTINCT支持情況詳細説明
|
|
SELECT * FROM tbl_name WHERE to_date(create_time, ‘yyyy-mm-dd’) = ?
|
會導致全路由
|
DISTINCT支持情況詳細説明
|
SQL
|
|
|
SELECT DISTINCT * FROM tbl_name WHERE col1 = ?
|
|
|
SELECT DISTINCT col1 FROM tbl_name
|
|
|
SELECT DISTINCT col1, col2, col3 FROM tbl_name
|
|
|
SELECT DISTINCT col1 FROM tbl_name ORDER BY col1
|
|
|
SELECT DISTINCT col1 FROM tbl_name ORDER BY col2
|
|
|
SELECT DISTINCT(col1) FROM tbl_name
|
|
|
SELECT AVG(DISTINCT col1) FROM tbl_name
|
|
|
SELECT SUM(DISTINCT col1) FROM tbl_name
|
|
|
SELECT COUNT(DISTINCT col1) FROM tbl_name
|
|
|
SELECT COUNT(DISTINCT col1) FROM tbl_name GROUP BY col1
|
|
|
SELECT COUNT(DISTINCT col1 + col2) FROM tbl_name
|
|
|
SELECT COUNT(DISTINCT col1), SUM(DISTINCT col1) FROM tbl_name
|
|
|
SELECT COUNT(DISTINCT col1), col1 FROM tbl_name GROUP BY col1
|
|
|
SELECT col1, COUNT(DISTINCT col1) FROM tbl_name GROUP BY col1
|
不支持的SQL
|
SQL
|
不支持原因
|
|
SELECT SUM(DISTINCT col1), SUM(col1) FROM tbl_name
|
同時使用普通聚合函數和DISTINCT聚合函數
|
- 分頁
完全支持MySQL的分頁查詢
- 分頁性能
性能瓶頸
查詢偏移量過大的分頁會導致數據庫獲取數據性能低下,以MySQL為例:
SELECT * FROM t_order ORDER BY id LIMIT 1000000, 10
這句SQL會使得MySQL在無法利用索引的情況下跳過1000000條記錄後,再獲取10條記錄,其性能可想而知。 而在分庫分表的情況下(假設分為2個庫),為了保證數據的正確性,SQL會改寫為:
SELECT * FROM t_order ORDER BY id LIMIT 0, 1000010
即將偏移量前的記錄全部取出,並僅獲取排序後的最後10條記錄。這會在數據庫本身就執行很慢的情況下,進一步加劇性能瓶頸。 因為原SQL僅需要傳輸10條記錄至客户端,而改寫之後的SQL則會傳輸1,000,010 * 2的記錄至客户端
ShardingSphere的優化
ShardingSphere進行了2個方面的優化。
首先,採用流式處理 + 歸併排序的方式來避免內存的過量佔用。由於SQL改寫不可避免的佔用了額外的帶寬,但並不會導致內存暴漲。 與直覺不同,大多數人認為ShardingSphere會將1,000,010 * 2記錄全部加載至內存,進而佔用大量內存而導致內存溢出。 但由於每個結果集的記錄是有序的,因此ShardingSphere每次比較僅獲取各個分片的當前結果集記錄,駐留在內存中的記錄僅為當前路由到的分片的結果集的當前遊標指向而已。 對於本身即有序的待排序對象,歸併排序的時間複雜度僅為O(n),性能損耗很小。
其次,ShardingSphere對僅落至單分片的查詢進行進一步優化。 落至單分片查詢的請求並不需要改寫SQL也可以保證記錄的正確性,因此在此種情況下,ShardingSphere並未進行SQL改寫,從而達到節省帶寬的目的。
- 分頁方案優化
由於LIMIT並不能通過索引查詢數據,因此如果可以保證ID的連續性,通過ID進行分頁是比較好的解決方案:
SELECT * FROM t_order WHERE id > 100000 AND id <= 100010 ORDER BY id
或通過記錄上次查詢結果的最後一條記錄的ID進行下一頁的查詢:
SELECT * FROM t_order WHERE id > 100000 LIMIT 10
- 分頁子查詢
MySQL和PostgreSQL都支持LIMIT分頁,無需子查詢:
SELECT * FROM t_order o ORDER BY id LIMIT ? OFFSET ?
3分庫分表策略場景應用
常見策略Range
- 場景方案:
自增ID,根據ID範圍進行分表(左閉右開)
- 規則案例
- 1~1000000是table_1
- 1000000~2000000是table_2
- 2000000~3000000是table_3
- …更多
- 優點
- id是自增長,可以無限增長
- 擴容不用遷移數據,容易理解和維護
- 缺點
- 大部分讀和寫都會訪問新的數據,有IO瓶頸,整體資源利用率低
- 數據傾斜嚴重,熱點數據過於集中,部分節點有拼頸
Range策略延伸進階
- Range範圍分庫分表常用場景
- 數字
- 自增ID範圍
- 時間
- 年、月、日範圍
- 比如按照月份生成庫或表pay_log_2022_01、pay_log_2022_02
- 空間
- 地理位置: 省份、區域(華東、華北、華南)
- 比如按照省份生成庫或表
- 基於Range範圍分庫分表業務場景
- 微博發送記錄、微信消息記錄、日誌記錄,id曾長/時間分區都行
- 水平分表為主,水平分庫則容易造成資源的浪費
- 網站簽到等活動流水數據時間分區最好
- 水平分區為主,水平分庫則容易造成資源的浪費
- 大區劃分
- SaaS業務水平拆分(華東、華南、華北等)
- Hash取模策略
- 案例規則
- 用户ID是整數型的,要分2庫,每個庫數量4表,一共8張表
- 用户ID取模後,值是0到7的要平均分配到每張表
A庫ID=userId % 庫數量 2
表ID userId /庫數量 2 % 表數據4
4.SpringBoot+MybatisPlus整 合Sharding-Jdbc實戰
SpringBoot2.5+MybatisPlus+Sharding-Jdbc集成
- 框架版本説明
<properties>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<spring.boot.version>2.5.5</spring.boot.version>
<mybatisplus.boot.starter.version>3.4.0</mybatisplus.boot.starter.version>
<lombok.version>1.18.16</lombok.version>
<sharding-jdbc.version>4.1.1</sharding-jdbc.version>
<junit.version>4.12</junit.version>
<druid.version>1.1.16</druid.version>
<!--跳過單元測試-->
<skipTests>true</skipTests>
</properties>
- pom文件配置
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring.boot.version}</version>
<scope>test</scope>
</dependency>
<!--mybatis plus和springboot整合-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatisplus.boot.starter.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<!--<scope>provided</scope>-->
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>${sharding-jdbc.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<configuration>
<fork>true</fork>
<addResources>true</addResources>
</configuration>
</plugin>
</plugins>
</build>
訂單案例分庫分表SQL腳本創建説明
- 分庫分表
- 2庫2表
- 數據庫
- shop_order_0
- product_order_0
- product_order_1
- shop_order_1
- product_order_0
- product_order_1
- SQL腳本
CREATE TABLE `product_order_0` (
`id` bigint NOT NULL AUTO_INCREMENT,
`out_trade_no` varchar(64) DEFAULT NULL COMMENT '訂單唯一標識',
`state` varchar(11) DEFAULT NULL COMMENT 'NEW 未支付訂單,PAY已經支付訂單,CANCEL超時取消訂單',
`create_time` datetime DEFAULT NULL COMMENT '訂單生成時間',
`pay_amount` decimal(16,2) DEFAULT NULL COMMENT '訂單實際支付價格',
`nickname` varchar(64) DEFAULT NULL COMMENT '暱稱',
`user_id` bigint DEFAULT NULL COMMENT '用户id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
CREATE TABLE `ad_config` (
`id` bigint unsigned NOT NULL COMMENT '主鍵id',
`config_key` varchar(1024) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '配置key',
`config_value` varchar(1024) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '配置value',
`type` varchar(128) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '類型',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
#綁定表
CREATE TABLE `product_order_item_0` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`product_order_id` bigint DEFAULT NULL COMMENT '訂單號',
`product_id` bigint DEFAULT NULL COMMENT '產品id',
`product_name` varchar(128) DEFAULT NULL COMMENT '商品名稱',
`buy_num` int DEFAULT NULL COMMENT '購買數量',
`user_id` bigint DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
- 實體模型類
//數據庫實體類
public interface ProductOrderMapper extends BaseMapper<ProductOrderDO> {
}
public interface ProductOrderItemMapper extends BaseMapper<ProductOrderItemDO> {
}
public interface AdConfigMapper extends BaseMapper<AdConfigDO> {
}
@Data
@TableName("product_order")
@EqualsAndHashCode(callSuper = false)
public class ProductOrderDO {
private Long id;
private String outTradeNo;
private String state;
private Date createTime;
private Double payAmount;
private String nickname;
private Long userId;
}
@Data
@TableName("product_order_item")
@EqualsAndHashCode(callSuper = false)
public class ProductOrderItemDO {
private Long id;
private Long productOrderId;
private Long productId;
private String productName;
private Integer buyNum;
private Long userId;
}
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("ad_config")
public class AdConfigDO {
private Integer id;
private String configKey;
private String configValue;
private String type;
}
Sharding-Jdbc常規數據源配置和⽔平分表
- 配置文件配置
# 打印執行的數據庫以及語句
spring.shardingsphere.props.sql.show=true
# 數據源 db0
spring.shardingsphere.datasource.names=ds0,ds1
# 第一個數據庫
spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://192.168.2.133:3306/shop_order_0?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=123456
# 第二個數據庫
spring.shardingsphere.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.ds1.jdbc-url=jdbc:mysql://192.168.2.133:3306/shop_order_1?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.shardingsphere.datasource.ds1.username=root
spring.shardingsphere.datasource.ds1.password=123456
#配置workId
spring.shardingsphere.sharding.tables.product_order.key-generator.props.worker.id=1
#配置廣播表
spring.shardingsphere.sharding.broadcast-tables=ad_config
spring.shardingsphere.sharding.tables.ad_config.key-generator.column=id
spring.shardingsphere.sharding.tables.ad_config.key-generator.type=SNOWFLAKE
#配置【默認分庫策略】
#spring.shardingsphere.sharding.default-database-strategy.inline.sharding-column=user_id
#spring.shardingsphere.sharding.default-database-strategy.inline.algorithm-expression=ds$->{user_id % 2 }
#配置分庫規則
#spring.shardingsphere.sharding.tables.product_order.database-strategy.inline.sharding-column=user_id
#spring.shardingsphere.sharding.tables.product_order.database-strategy.inline.algorithm-expression=ds$->{user_id % 2 }
#id生成策略
spring.shardingsphere.sharding.tables.product_order.key-generator.column=id
spring.shardingsphere.sharding.tables.product_order.key-generator.type=SNOWFLAKE
# 指定product_order表的數據分佈情況,配置數據節點,行表達式標識符使用 ${...} 或 $->{...},
# 但前者與 Spring 本身的文件佔位符衝突,所以在 Spring 環境中建議使用 $->{...}
#spring.shardingsphere.sharding.tables.product_order.actual-data-nodes=ds0.product_order_$->{0..1}
#spring.shardingsphere.sharding.tables.product_order.actual-data-nodes=ds$->{0..1}.product_order_$->{0..1}
# 指定product_order表的分片策略,分片策略包括【分片鍵和分片算法】
#spring.shardingsphere.sharding.tables.product_order.table-strategy.inline.sharding-column=id
#spring.shardingsphere.sharding.tables.product_order.table-strategy.inline.algorithm-expression=product_order_$->{id % 2}
# 指定product_order_item表的分片策略,分片策略包括【分片鍵和分片算法】
#spring.shardingsphere.sharding.tables.product_order_item.actual-data-nodes=ds$->{0..1}.product_order_item_$->{0..1}
#spring.shardingsphere.sharding.tables.product_order_item.table-strategy.inline.sharding-column=product_order_id
#spring.shardingsphere.sharding.tables.product_order_item.table-strategy.inline.algorithm-expression=product_order_item_$->{product_order_id % 2}
#配置綁定表
#spring.shardingsphere.sharding.binding‐tables[0]=product_order,product_order_item
#精準分片-水平分表
# 指定product_order表的數據分佈情況,配置數據節點,在 Spring 環境中建議使用 $->{...}
spring.shardingsphere.sharding.tables.product_order.actual-data-nodes=ds$->{0..1}.product_order_$->{0..1}
#spring.shardingsphere.sharding.tables.product_order.actual-data-nodes=ds0.product_order_$->{0..1}
SpringBoot+Sharding-Jdbc單元測試
5.分庫分表之Snowflake雪花算法
分庫分表下常⻅主鍵id⽣產策略實現
- 單庫下⼀般使⽤Mysql⾃增ID, 但是分庫分表後,會造成不同分⽚
上的數據表主鍵會重複 - 需求
- 性能強勁
- 全局唯一
- 防⽌惡意⽤户規矩id的規則來獲取數據
- 業界常⽤ID解決⽅案
- 數據庫⾃增ID
- 利⽤⾃增id, 設置不同的⾃增步⻓
- 缺點
- 依靠數據庫系統的功能實現,但是未來擴容麻煩
- 主從切換時的不⼀致可能會導致重複發號
- 性能瓶頸存在單台sql上
- UUID
- 性能⾮常⾼,沒有⽹絡消耗
- 缺點
- ⽆序的字符串,不具備趨勢⾃增特性
- UUID太⻓,不易於存儲,浪費存儲空間,很多場景不
適⽤
- Redis發號器
- 利⽤Redis的INCR和INCRBY來實現,原⼦操作,線程安
全,性能⽐Mysql強勁 - 缺點
- 需要佔⽤⽹絡資源,增加系統複雜度
- Snowflake雪花算法
- twitter 開源的分佈式 ID ⽣成算法,代碼實現簡單、不佔
⽤寬帶、數據遷移不受影響 - ⽣成的 id 中包含有時間戳,所以⽣成的 id 按照時間遞增
- 部署了多台服務器,需要保證系統時間⼀樣,機器編號不
⼀樣 - 缺點
- 依賴系統時鐘(多台服務器時間⼀定要⼀樣)
- 分佈式ID⽣成器Snowflake需要注意的問題
- 分佈式部署就需要分配不同的workId, 如果workId相同,
可能會導致⽣成的id相同 - 分佈式情況下,需要保證各個系統時間⼀致,如果服務器
的時鐘回撥,就會導致⽣成的 id 重複
- 配置實操
#配置workId
spring.shardingsphere.sharding.tables.product_order.key-generator.props.worker.id=1
- 方式實現一
- 訂單id使⽤MybatisPlus的配置
@TableId(value = "id", type = IdType.ASSIGN_ID)
默認實現類為DefaultIdentifierGenerator雪花算法
- 方式實現二
- 使⽤Sharding-Jdbc配置⽂件,註釋DO類⾥⾯的id分配策略
#id生成策略
spring.shardingsphere.sharding.tables.product_order.key-generator.column=id
spring.shardingsphere.sharding.tables.product_order.key-generator.type=SNOWFLAKE
- workerId動態指定
- 動態制定workerId
/**
* sharding-jdbc 使用雪花算法生成主鍵時,workId配置
*
*/
@Configuration
@Slf4j
public class WorkIdConfig {
/**
* 設置workerIdValue的值
*/
static {
int workId = 0;
try {
workId = getWorkId();
} catch (Exception e) {
log.error("生成workId發生異常.", e);
}
System.setProperty("workerIdValue", String.valueOf(workId));
}
/**
* 根據機器名稱生成workId
* @return
*/
private static int getWorkId() {
String hostAddress = System.getenv("HOSTNAME");
log.info("============= hostAddress: {} =============", hostAddress);
int[] ints = StringUtils.toCodePoints(hostAddress);
int sum = 0;
for (int b : ints) {
sum += b;
}
int workId = (sum % 32);
log.info("============== workId:{} =============", workId);
return workId;
}
}
配置文件配置:
spring.shardingsphere.sharding.tables.product_order.key-generator.props.worker.id=${workerIdValue}
6.⼴播表和綁定表配置實戰
Sharding-Jdbc⼴播表介紹和配置實戰
- 什麼是⼴播表
- 指所有的分⽚數據源中都存在的表,表結構和表中的數據在每
個數據庫中均完全⼀致 - 適⽤於數據量不⼤且需要與海量數據的表進⾏關聯查詢的場景
- 例如:字典表、配置表
- 注意點
- 分庫分表中間件,對應的數據庫字段,不能是sql的關鍵字,
否則容易出問題,且報錯不明顯
- 配置實戰
- ad_config表
- 配置文件
#配置廣播表
spring.shardingsphere.sharding.broadcast-tables=ad_config
spring.shardingsphere.sharding.tables.ad_config.key-generator.column=id
spring.shardingsphere.sharding.tables.ad_config.key-generator.type=SNOWFLAKE
Sharding-Jdbc綁定表配置實戰
- 什麼是綁定表
- 指分⽚規則⼀致的主表和⼦表
- ⽐如product_order表和product_order_item表,均按照
order_id分⽚,則此兩張表互為綁定表關係 - 綁定表之間的多表關聯查詢不會出現笛卡爾積關聯,關聯查詢
效率將⼤⼤提升
- 分庫分表配置
- 分庫規則 根據 user_id 進⾏分庫
- 分表規則 根據 product_order_id 訂單號進⾏分表
- 實戰代碼配置
#配置綁定表
#錯誤配置,因為帶有空格所以會導致綁定關係失敗
spring.shardingsphere.sharding.binding‐tables[0] = product_order,product_order_item
#正確配置綁定表實現,邏輯表兩邊不能帶有空格
spring.shardingsphere.sharding.binding-tables[0] =product_order,product_order_item
7.Sharding-Jdbc分庫分表分佈式事務
- 問題:分庫分錶帶來的分佈式事務問題
- 操作內容同時分佈在不同庫中,不可避免會帶來跨庫事務問題,即分佈式事務
- 常見分佈式事務處理方案
- 事務消息
- 最大努力通知型
- 分佈式框架
- Seata:支持AT、TCC、SAGA和XA多種模式
- 使用最對的AT模式
- RocketMQ: 自帶事務消息分佈式事務
- 高併發場景分佈式事務解決
- 解決思路:服務自理:
- 庫存服務扣減庫存前保存一個Task任務,記錄扣減的商品,數量還有關聯的訂單ID(扣減和保存任務在同個事務裏面)
- 然後使用定時Task任務表掃描(庫存允許一定時間內不同步,最終一致性)
- 訂單被超時取消:一定時間內訂單超時未支付被關閉,則恢復這個商品的庫存
- 訂單不存在:下單、鎖定庫存後,程序問題導致訂單服務異常回滾,查詢不到訂單,則恢復這個商品庫存
8.Sharding-Jdbc分庫分表常⻅問題
- 多維度查詢方案: 空間換時間