https://shardingsphere.apache.org/document/current/cn/features/sharding/concept/sharding/html
Sharding-JDBC 中的分片策略有兩個維度:分庫(數據源分片)策略和分表策略。分庫策略表示數據路由到的物理目標數據源,分表分片策略表示數據被路由到的目標表。分表策略是依賴於分庫策略的,也就是說要先分庫再分表,固然也能夠不分庫只分表。跟 Mycat 不同,Sharding-JDBC 沒有提供內置的分片算法,而是經過抽象成接口,讓開發者自行實現,這樣能夠根據業務實際狀況靈活地實現分片。java
包含分片鍵和分片算法,分片算法是須要自定義的。能夠用於分庫,也能夠用於分表。因爲分片算法和業務實現緊密相關,所以Sharding-JDBC並未提供內置分片算法,而是經過分片策略將各類場景提煉出來,提供更高層級的抽象,並提供接口讓應用開發者自行實現分片算法。Sharding-JDBC 提供了 5 種分片策略(接口),策略所有繼承自 ShardingStrategy,能夠根據狀況選擇實現相應的接口。node
標準分片策略。提供對SQL語句中的=, IN和BETWEEN AND的分片操做支持。mysql
StandardShardingStrategy只支持單分片鍵,提供PreciseShardingAlgorithm和RangeShardingAlgorithm兩個分片算法。git
複合分片策略。提供對SQL語句中的=, IN和BETWEEN AND的分片操做支持。github
ComplexShardingStrategy支持多分片鍵,因爲多分片鍵之間的關係複雜,所以Sharding-JDBC並未作過多的封裝,而是直接將分片鍵值組合以及分片操做符交於算法接口,徹底由應用開發者實現,提供最大的靈活度。算法
Inline表達式分片策略。使用Groovy的Inline表達式,提供對SQL語句中的=和IN的分片操做支持。spring
InlineShardingStrategy只支持單分片鍵,對於簡單的分片算法,能夠經過簡單的配置使用,從而避免繁瑣的Java代碼開發,如: tuser${user_id % 8} 表示t_user表按照user_id按8取模分紅8個表,表名稱爲t_user_0到t_user_7。sql
經過Hint而非SQL解析的方式分片的策略。數據庫
不分片的策略。
https://shardingsphere.apache.org/document/current/cn/features/sharding/concept/inline-expression/
對應 InlineShardingStrategy 類。只支持單分片鍵,提供對=和 IN 操做的支持。行內表達式的配置比較簡單。
例如:
${begin..end}表示範圍區間
${[unit1, unit2, unit_x]}表示枚舉值
t_user_$->{u_id % 8} 表示 t_user 表根據 u_id 模 8,而分紅 8 張表,表名稱爲 t_user_0 到 t_user_7。
行表達式中若是出現連續多個${ expression }或$->{ expression }表達式,整個表達式最終的結果將會根據每一個子表達式的結果進行笛卡爾組合。
例如,如下行表達式:
${['db1', 'db2']}_table${1..3}
最終會解析爲:
db1_table1, db1_table2, db1_table3,
db2_table1, db2_table2, db2_table3
對應 StandardShardingStrategy 類。
標準分片策略只支持單分片鍵,提供了提供 PreciseShardingAlgorithm 和 RangeShardingAlgorithm 兩個分片算法,分別對應於 SQL 語句中的=, IN 和 BETWEEN AND。
若是要使用標準分片策略,必需要實現 PreciseShardingAlgorithm,用來處理=和 IN 的分片。RangeShardingAlgorithm 是可選的。若是沒有實現,SQL 語句會發到全部的數據節點上執行。
玩起來也特別簡單
測試類
mybatis.mapper-locations=classpath:mapper/*.xml mybatis.config-location=classpath:mybatis-config.xml
@RunWith(SpringRunner.class) @SpringBootTest @Slf4j public class test { @Resource UserService userService; /** * 先執行插入 */ @Test public void insert(){ userService.insert(); } @Test public void select(){ UserInfo userInfo1= userService.getUserInfoByUserId(532299550304501761L); System.out.println("------userInfo1:"+userInfo1); UserInfo userInfo2= userService.getUserInfoByUserId(532299547905359872L); System.out.println("------userInfo2:"+userInfo2); } }
@Configuration @MapperScan(basePackages = "com.ghy.shardingjdbccostom.mapper", sqlSessionFactoryRef = "sqlSessionFactory") public class DataSourceConfig { @Bean @Primary public DataSource shardingDataSource() throws SQLException { // 配置真實數據源 Map<String, DataSource> dataSourceMap = new HashMap<>(); // 配置第一個數據源 DruidDataSource dataSource1 = new DruidDataSource(); dataSource1.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource1.setUrl("jdbc:mysql://localhost:3306/ds0?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8"); dataSource1.setUsername("root"); dataSource1.setPassword("root"); dataSourceMap.put("ds0", dataSource1); // 配置第二個數據源 DruidDataSource dataSource2 = new DruidDataSource(); dataSource2.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource2.setUrl("jdbc:mysql://localhost:3306/ds1?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8"); dataSource2.setUsername("root"); dataSource2.setPassword("root"); dataSourceMap.put("ds1", dataSource2); // 配置Order表規則 TableRuleConfiguration orderTableRuleConfig = new TableRuleConfiguration("user_info", "ds${0..1}.user_info"); // 分表策略,使用 Standard 自定義實現,這裏沒有分表,表名固定爲user_info StandardShardingStrategyConfiguration tableInlineStrategy = new StandardShardingStrategyConfiguration("user_id", new TblPreShardAlgo(),new TblRangeShardAlgo()); orderTableRuleConfig.setTableShardingStrategyConfig(tableInlineStrategy); // 分庫策略,使用 Standard 自定義實現 StandardShardingStrategyConfiguration dataBaseInlineStrategy =new StandardShardingStrategyConfiguration("user_id", new DBShardAlgo()); orderTableRuleConfig.setDatabaseShardingStrategyConfig(dataBaseInlineStrategy); // 添加表配置 ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration(); shardingRuleConfig.getTableRuleConfigs().add(orderTableRuleConfig); // 獲取數據源對象 DataSource dataSource = ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfig, new Properties()); return dataSource; } // 事務管理器 @Bean public DataSourceTransactionManager transactitonManager(DataSource shardingDataSource) { return new DataSourceTransactionManager(shardingDataSource); } }
public class DBShardAlgo implements PreciseShardingAlgorithm<Long> { @Override public String doSharding(Collection<String> collection, PreciseShardingValue<Long> preciseShardingValue) { String db_name="ds"; Long num = preciseShardingValue.getValue()%2; db_name = db_name + num; for (String each : collection) { if (each.equals(db_name)) { return each; } } throw new IllegalArgumentException(); } }
public class TblPreShardAlgo implements PreciseShardingAlgorithm<Long> { @Override public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Long> shardingColumn) { // 不分表 for (String tbname : availableTargetNames) { return tbname ; } throw new IllegalArgumentException(); } }
public class TblRangeShardAlgo implements RangeShardingAlgorithm<Long> { @Override public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<Long> rangeShardingValue) { System.out.println("範圍-*-*-*-*-*-*-*-*-*-*-*---------------"+availableTargetNames); System.out.println("範圍-*-*-*-*-*-*-*-*-*-*-*---------------"+rangeShardingValue); Collection<String> collect = new LinkedHashSet<>(); Range<Long> valueRange = rangeShardingValue.getValueRange(); for (Long i = valueRange.lowerEndpoint(); i <= valueRange.upperEndpoint(); i++) { for (String each : availableTargetNames) { if (each.endsWith(i % availableTargetNames.size() + "")) { collect.add(each); } } } // return collect; } }
其它業務層和數據庫層代碼和之前寫法同樣,後面代碼會發布,就不一一搞了
好比:根據日期和 ID 兩個字段分片,每月 3 張表,先根據日期,再根據 ID 取模。對應 ComplexShardingStrategy 類。能夠支持等值查詢和範圍查詢。複合分片策略支持多分片鍵,提供了 ComplexKeysShardingAlgorithm,分片算法須要本身實現。因爲多分片鍵之間的關係複雜,所以Sharding-JDBC並未作過多的封裝,而是直接將分片鍵值組合以及分片操做符交於算法接口,徹底由應用開發者實現,提供最大的靈活度
Sharding -jdbc 在使用分片策略的時候,與分片算法是成對出現的,每種策略都對應一到兩種分片算法(不分片策略NoneShardingStrategy除外)
分庫分表最核心的兩點SQL 路由 、 SQL 改寫 :
SQL 路由:解析原生SQL,肯定須要使用哪些數據庫,哪些數據表Route (路由)引擎:爲何要用Route 引擎呢?在實際查詢當中,數據可能不僅是存在一臺MYSQL服務器上,
SELECT * FROM t_order WHERE order _id IN(1,3,6)
數據分佈:
ds0.t_order0 (1,3,5,7) ds1.t_order0(2,4,6)
這個SELECT 查詢就須要走2個database,若是這個SQL原封不動的執行,確定會報錯(表不存在),Sharding-jdbc 必需要對這個sql進行改寫,將庫名和表名 2個路由加上
SELECT * FROM ds0.t_order0 WHERE order _id IN(1,3) SELECT * FROM ds0.t_order1 WHERE order _id IN(6)
SQL 改寫:將SQL 按照必定規則,重寫FROM 的數據庫和表名(Route 返回路由決定須要去哪些庫表中執行SQL)
配置主要分爲三個部分
# 複合分片 sharding.jdbc.datasource.names=ds0,ds1 sharding.jdbc.datasource.ds0.type=com.alibaba.druid.pool.DruidDataSource sharding.jdbc.datasource.ds0.driver-class-name=com.mysql.jdbc.Driver sharding.jdbc.datasource.ds0.url=jdbc:mysql://127.0.0.1:5306/ds0?useUnicode=yes&characterEncoding=utf8 sharding.jdbc.datasource.ds0.username=root sharding.jdbc.datasource.ds0.password=root sharding.jdbc.datasource.ds1.type=com.alibaba.druid.pool.DruidDataSource sharding.jdbc.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver sharding.jdbc.datasource.ds1.url=jdbc:mysql://127.0.0.1:5306/ds1?useUnicode=yes&characterEncoding=utf8 sharding.jdbc.datasource.ds1.username=root sharding.jdbc.datasource.ds1.password=root # 分庫配置 (行表達式分片策略 + 行表達式分片算法) sharding.jdbc.config.sharding.default-database-strategy.inline.sharding-column=user_id sharding.jdbc.config.sharding.default-database-strategy.inline.algorithm-expression=ds$->{user_id % 2} sharding.jdbc.config.sharding.binding-tables=t_order,t_order_item # t_order分表配置 (複合分片策略) sharding.jdbc.config.sharding.tables.t_order.actual-data-nodes=ds$->{0..1}.t_order$->{0..1}_$->{0..1} sharding.jdbc.config.sharding.tables.t_order.table-strategy.complex.sharding-columns=user_id,order_id sharding.jdbc.config.sharding.tables.t_order.table-strategy.complex.algorithm-class-name=ai.yunxi.sharding.config.ComplexShardingAlgorithm # t_order_item分表配置 (複合分片策略) sharding.jdbc.config.sharding.tables.t_order_item.actual-data-nodes=ds$->{0..1}.t_order_item$->{0..1}_$->{0..1} # 標準 和 inline 都是單分片鍵 ,複合分片策略能夠配置則多分片鍵 sharding.jdbc.config.sharding.tables.t_order_item.table-strategy.complex.sharding-columns=user_id,order_id # 自定義算法,讓使用者根據業務自定義實現(開發性接口更靈活方便) sharding.jdbc.config.sharding.tables.t_order_item.table-strategy.complex.algorithm-class-name=ai.yunxi.sharding.config.ComplexShardingAlgorithm # 定義廣播表 sharding.jdbc.config.sharding.broadcast-tables=t_province sharding.jdbc.config.props.sql.show=true
import io.shardingsphere.api.algorithm.sharding.ListShardingValue; import io.shardingsphere.api.algorithm.sharding.ShardingValue; import io.shardingsphere.api.algorithm.sharding.complex.ComplexKeysShardingAlgorithm; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; public class ComplexShardingAlgorithm implements ComplexKeysShardingAlgorithm { /** * * @param collection 在加載配置文件時,會解析表分片規則。將結果存儲到 collection中,doSharding()參數使用 * @param shardingValues SQL中對應的 * @return */ @Override public Collection<String> doSharding(Collection<String> collection, Collection<ShardingValue> shardingValues) { System.out.println("collection:" + collection + ",shardingValues:" + shardingValues); Collection<Integer> orderIdValues = getShardingValue(shardingValues, "order_id"); Collection<Integer> userIdValues = getShardingValue(shardingValues, "user_id"); List<String> shardingSuffix = new ArrayList<>(); // user_id,order_id分片鍵進行分表 for (Integer userId : userIdValues) { for (Integer orderId : orderIdValues) { String suffix = userId % 2 + "_" + orderId % 2; for (String s : collection) { if (s.endsWith(suffix)) { shardingSuffix.add(s); } } } } return shardingSuffix; } /** * 例如: SELECT * FROM T_ORDER user_id = 100000 AND order_id = 1000009 * 循環 獲取SQL 中 分片鍵列對應的value值 * @param shardingValues sql 中分片鍵的value值 -> 1000009 * @param key 分片鍵列名 -> user_id * @return shardingValues 集合 -> [1000009] */ private Collection<Integer> getShardingValue(Collection<ShardingValue> shardingValues, final String key) { Collection<Integer> valueSet = new ArrayList<>(); Iterator<ShardingValue> iterator = shardingValues.iterator(); while (iterator.hasNext()) { ShardingValue next = iterator.next(); if (next instanceof ListShardingValue) { ListShardingValue value = (ListShardingValue) next; // user_id,order_id分片鍵進行分表 if (value.getColumnName().equals(key)) { return value.getValues(); } } } return valueSet; } }
Hint分片策略(HintShardingStrategy
)相比於上面幾種分片策略稍有不一樣,這種分片策略無需配置分片健,分片健值也再也不從 SQL中解析,而是由外部指定分片信息,讓 SQL在指定的分庫、分表中執行。ShardingSphere
經過 Hint
API實現指定操做,實際上就是把分片規則tablerule
、databaserule
由集中配置變成了個性化配置。
舉個例子,若是咱們但願訂單表t_order
用 user_id
作分片健進行分庫分表,可是 t_order
表中卻沒有 user_id
這個字段,這時能夠經過 Hint API 在外部手動指定分片健或分片庫。
下邊咱們這邊給一條無分片條件的SQL,看如何指定分片健讓它路由到指定庫表。
SELECT * FROM t_order;
使用 Hint分片策略一樣須要自定義,實現 HintShardingAlgorithm
接口並重寫 doSharding()
方法。
/** * @author xinzhifu * @description hit分表算法 * @date 2020/11/2 12:06 */ public class MyTableHintShardingAlgorithm implements HintShardingAlgorithm<String> { @Override public Collection<String> doSharding(Collection<String> tableNames, HintShardingValue<String> hintShardingValue) { Collection<String> result = new ArrayList<>(); for (String tableName : tableNames) { for (String shardingValue : hintShardingValue.getValues()) { if (tableName.endsWith(String.valueOf(Long.valueOf(shardingValue) % tableNames.size()))) { result.add(tableName); } } } return result; } }
自定義完算法只實現了一部分,還須要在調用 SQL 前經過 HintManager
指定分庫、分表信息。因爲每次添加的規則都放在 ThreadLocal
內,因此要先執行 clear()
清除掉上一次的規則,不然會報錯;addDatabaseShardingValue
設置分庫分片健鍵值,addTableShardingValue
設置分表分片健鍵值。setMasterRouteOnly
讀寫分離強制讀主庫,避免形成主從複製致使的延遲。
// 清除掉上一次的規則,不然會報錯 HintManager.clear(); // HintManager API 工具類實例 HintManager hintManager = HintManager.getInstance(); // 直接指定對應具體的數據庫 hintManager.addDatabaseShardingValue("ds",0); // 設置表的分片健 hintManager.addTableShardingValue("t_order" , 0); hintManager.addTableShardingValue("t_order" , 1); hintManager.addTableShardingValue("t_order" , 2); // 在讀寫分離數據庫中,Hint 能夠強制讀主庫 hintManager.setMasterRouteOnly();
debug 調試看到,咱們對 t_order
表設置分表分片健鍵值,能夠在自定義的算法 HintShardingValue
參數中成功拿到。
properties
文件中配置無需再指定分片健,只需自定義的 Hint分片算法類路徑便可。
# Hint分片算法 spring.shardingsphere.sharding.tables.t_order.table-strategy.hint.algorithm-class-name=com.ghy.shardingjdbccostom.config.MyTableHintShardingAlgorithm
對應 NoneShardingStrategy。不分片的策略(只在一個節點存儲)。
建立了分片策略以後,須要進一步實現分片算法,做爲參數傳遞給分片策略。Sharding-JDBC 目前提供 4 種分片算法。
對應 PreciseShardingAlgorithm,用於處理使用單一鍵做爲分片鍵的=與 IN 進行分片的場景。須要配合 StandardShardingStrategy 使用。
對應 RangeShardingAlgorithm,用於處理使用單一鍵做爲分片鍵的 BETWEEN AND 進行分片的場景。須要配合 StandardShardingStrategy 使用。若是不配置範圍分片算法,範圍查詢默認會路由到全部節點。
對應 ComplexKeysShardingAlgorithm,用於處理使用多鍵做爲分片鍵進行分片的場景,包含多個分片鍵的邏輯較複雜,須要應用開發者自行處理其中的複雜度。須要配合 ComplexShardingStrategy 使用。
對應 HintShardingAlgorithm ,用於處理使用 Hint 行分片的場景。 須要配合 HintShardingStrategy 使用。
https://github.com/apache/shardingsphere
https://shardingsphere.apache.org/document/legacy/4.x/document/cn/overview/
DAO:AbstractRoutingDataSource
ORM:MyBatis 插件
JDBC:Sharding-JDBC
Proxy:Mycat、Sharding-Proxy
Server:特定數據庫或者版本
定位爲輕量級 Java 框架,在 Java 的 JDBC 層提供的額外服務。 它使用客戶端直連數據庫,以 jar 包形式提供服務,無需額外部署和依賴,可理解爲加強版的 JDBC 驅動,徹底兼容 JDBC 和各類 ORM 框架。也就是說,在 maven 的工程裏面,咱們使用它的方式是引入依賴,而後進行配置就能夠了,不用像 Mycat 同樣獨立運行一個服務,客戶端不須要修改任何一行代碼,原來是 SSM 鏈接數據庫,仍是 SSM,由於它是支持 MyBatis 的。跟 mycat 同樣,由於數據源有多個,因此要配置數據源,並且分片規則是定義在客戶端的。
咱們在項目內引入 Sharding-JDBC 的依賴,咱們的業務代碼在操做數據庫的時候,就會經過 Sharding-JDBC 的代碼鏈接到數據庫。也就是分庫分表的一些核心動做,好比 SQL 解析,路由,執行,結果處理,都是由它來完成的。它工做在客戶端。
固然,在 Sharding-Sphere 裏面一樣提供了代理 Proxy 的版本,跟 Mycat 的做用是同樣的。Sharding-Sidecar 是一個 Kubernetes 的雲原生數據庫代理,正在開發中
分庫分表後的幾大問題,Sharding-JDBC 所有解決了:跨庫關聯查詢(ER 表)、排序翻頁計算、分佈式事務、全局主鍵
https://shardingsphere.apache.org/document/current/cn/features/sharding/concept/key-generator/
無中心化分佈式主鍵(包括 UUID 雪花 SNOWFLAKE)使用 key-generator-column-name 配置,生成了一個 18 位的 ID。
Properties 配置:
spring.shardingsphere.sharding.tables.user_info.key-generator.column=user_id
spring.shardingsphere.sharding.tables.user_info.key-generator.type=SNOWFLAKE
keyGeneratorColumnName:指定須要生成 ID 的列
KeyGenerotorClass:指定生成器類,默認是 DefaultKeyGenerator.java,裏面使用了雪花算法。
注意:ID 要用 BIGINT。Mapper.xml insert 語句裏面不能出現主鍵。不然會報錯:Sharding value must implements Comparable
咱們用到分佈式事務的場景有兩種,一種是跨應用(好比微服務場景),一種是單應用多個數據庫(分庫分表的場景),對於代碼層的使用來講的同樣的。
https://shardingsphere.apache.org/document/current/cn/features/transaction/
XA 模型的不足:須要鎖定資源
SEATA:支持 AT、XA、TCC、SAGA
SEATA 是一種全局事務的框架。
玩起來也特別容易首先添加依賴
<dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>sharding-transaction-xa-core</artifactId> <version>4.1.1</version> </dependency>
而後在業務層上加入下面註解就能夠了
@ShardingTransactionType(TransactionType.XA) @Transactional(rollbackFor = Exception.class)
https://seata.io/zh-cn/docs/overview/what-is-seata.html
https://github.com/seata/seata
https://github.com/seata/seata-workshop(官網推薦的搭建例子教程)
https://github.com/seata/seata-samples(官網推薦的搭建例子教程)
https://shardingsphere.apache.org/document/current/cn/features/sharding/principle/
ShardingSphere 的 3 個產品的數據分片主要流程是徹底一致的。 核心由 SQL 解析 => 執行器優化 => SQL 路由 => SQL 改寫 => SQL 執行 => 結果歸併
的流程組成。
分爲詞法解析和語法解析。 先經過詞法解析器將 SQL 拆分爲一個個不可再分的單詞。再使用語法解析器對 SQL 進行理解,並最終提煉出解析上下文。 解析上下文包括表、選擇項、排序項、分組項、聚合函數、分頁信息、查詢條件以及可能須要修改的佔位符的標記。目前常見的 SQL 解析器主要有 fdb,jsqlparser和 Druid。Sharding-JDBC1.4.x 以前的版本使用 Druid 做爲 SQL 解析器。從 1.5.x 版本開始,Sharding-JDBC 採用徹底自研的 SQL 解析引擎。
合併和優化分片條件,如 OR 等。
根據解析上下文匹配用戶配置的分片策略,並生成路由路徑。目前支持分片路由和廣播路由。
SQL 路由是根據分片規則配置以及解析上下文中的分片條件,將 SQL 定位至真正的數據源。它又分爲直接路由、簡單路由和笛卡爾積路由。
直接路由,使用 Hint 方式。
Binding 表是指使用一樣的分片鍵和分片規則的一組表,也就是說任何狀況下, Binding 表的分片結果應與主表一致。例如:order 表和 order_item 表,都根據 order_id 分片,結果應是 order_1 與 order_item_1 成對出現。這樣的關聯查詢和單表查詢複雜度和性能至關。若是分片條件不是等於,而是 BETWEEN 或 IN,則路由結果不必定落入單庫(表),所以一條邏輯 SQL 最終可能拆分爲多條 SQL 語句。
笛卡爾積查詢最爲複雜,由於沒法根據 Binding 關係定位分片規則的一致性,因此非 Binding 表的關聯查詢須要拆解爲笛卡爾積組合執行。查詢性能較低,並且數據庫鏈接數較高,需謹慎使用。
例如:將邏輯表名稱改爲真實表名稱,優化分頁查詢等。
由於可能連接到多個真實數據源, Sharding -JDBC 將採用多線程併發執行 SQL。
例如數據的組裝、分頁、排序等等。
Sharding-JDBC中全部的代碼:https://github.com/ljx958720/springSphere.git