在分佈式服務中,要實現數據源得選擇有如下相關方案
- DAO:繼承 AbstractRoutingDataSource 類,實現對應的切換數據源的方法,結合自定義註解 + 切面實現動態數據源切換。
- ORM:MyBatis 插件進行數據源切換
- JDBC:Sharding-JDBC 基於客戶端的分庫分表方案
- Proxy:Mycat、Sharding-Proxy 基於代理的分庫分表方案
- Server:特定數據庫或者版本
- .........
基本概念及架構:
Sharding JDBC 是從噹噹網的內部架構 ddframe 裏面的一個分庫分表的模塊脫胎出來的,用來解決噹噹的分庫分表的問題,把跟業務相關的敏感的代碼剝離後,就得到了 Sharding-JDBC。它是一個工作在客戶端的分庫分表的解決方案。
DubboX,Elastic-job 也是噹噹開源出來的產品。
2018 年 5 月,因爲增加了 Proxy 的版本和 Sharding-Sidecar(尚未發佈),Sharding-JDBC 更名爲 Sharding Sphere,從一個客戶端的組件變成了一個套件。
2018 年 11 月,Sharding-Sphere 正式進入 Apache 基金會孵化器,這也是對Sharding-Sphere 的質量和影響力的認可。不過現在還沒有畢業(名字帶 incubator),
一般我們用的還是 io.shardingsphere 的包。因爲更名後和捐獻給 Apache 之後的 groupId 都不一樣,在引入依賴的時候千萬要注意。主體功能是相同的,但是在某些類的用法上有些差異,如果要升級的話 import 要全部修改,有些類和方法也要修改。
定位爲輕量級Java框架,在Java的JDBC層提供的額外服務。 它使用客戶端直連數據庫,以jar包形式提供服務,無需額外部署和依賴,可理解爲增強版的JDBC驅動,完全兼容JDBC和各種ORM框架。
- 適用於任何基於JDBC的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC。
- 支持任何第三方的數據庫連接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP等。
- 支持任意實現JDBC規範的數據庫。目前支持MySQL,Oracle,SQLServer,PostgreSQL以及任何遵循SQL92標準的數據庫。
在 maven 的工程裏面,我們使用它的方式是引入依賴,然後進行配置就可以了,不用像 Mycat 一樣獨立運行一個服務,客戶端不需要修改任何一行代碼,原來是 SSM 連接數據庫,還是 SSM,因爲它是支持 MyBatis 的。
我們在項目內引入 Sharding-JDBC 的依賴,我們的業務代碼在操作數據庫的時候,就會通過 Sharding-JDBC 的代碼連接到數據庫。分庫分表的一些核心動作,比如 SQL 解析,路由,執行,結果處理,都是由它來完成的。它工作在客戶端。
在 Sharding-Sphere 裏面同樣提供了代理 Proxy 的版本,跟 Mycat 的作用是一樣的。Sharding-Sidecar 是一個 Kubernetes 的雲原生數據庫代理,正在開發中。
核心功能 :
分庫分表後的幾大問題:跨庫關聯查詢、分佈式事務、排序翻頁計算、全局主鍵。
數據分片
- 分庫 & 分表
- 讀寫分離:https://shardingsphere.apache.org/document/current/cn/features/read-write-split/
- 分片策略定製化
- 無中心化分佈式主鍵(包括 UUID、雪花、LEAF)
分佈式事務:https://shardingsphere.apache.org/document/current/cn/features/transaction/
- 標準化事務接口
- XA 強一致事務
- 柔性事務
核心概念:
- 邏輯表:水平拆分的數據庫(表)的相同邏輯和數據結構表的總稱。例:訂單數據根據主鍵尾數拆分爲 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
分片,則此兩張表互爲綁定表關係。綁定表之間的多表關聯查詢不會出現笛卡爾積關聯,關聯查詢效率將大大提升。 - 廣播表:指所有的分片數據源中都存在的表,表結構和表中的數據在每個數據庫中均完全一致。適用於數據量不大且需要與海量數據的表進行關聯查詢的場景,例如:字典表。
- 分片鍵:根據指定的分片鍵進行路由。分片鍵不一定是主鍵,也不一定有業務含義。
使用規範 :
雖然 Apache ShardingSphere 希望能夠完全兼容所有的SQL以及單機數據庫,但分佈式爲數據庫帶來了更加複雜的場景。包括一些特殊的 sql 或者分頁都帶來了巨大的挑戰。對於這方面sharding-jdbc也做出了相關的說明
https://shardingsphere.apache.org/document/current/cn/features/sharding/use-norms/
與 Mycat 對比 :
Sharding-JDBC | Mycat | |
工作 層面 | JDBC 協議 | MySQL 協議/JDBC 協議 |
運行方式 | Jar 包,客戶端 | 獨立服務,服務端 |
開發 方式 | 代碼/配置改動 | 連接地址(數據源) |
運維 方式 | 無 | 管理獨立服務,運維成本高 |
性能 | 多線程併發按操作,性能高 | 獨立服務+網絡開銷,存在性能損失風險 |
功能 範圍 | 協議層面 | 包括分佈式事務、數據遷移等 |
適用 操作 | OLTP | OLTP+OLAP |
支持 數據庫 | 基於 JDBC 協議的數據庫 | MySQL 和其他支持 JDBC 協議的數據庫 |
支持 語言 | Java 項目中使用 | 支持 JDBC 協議的語言 |
維度 | 二維,支持分庫又分表,比如user表繼續拆分爲user1、user2 | 一維,分了庫後表不可以繼續拆分,或者單庫分表 |
從易用性和功能完善的角度來說,Mycat 似乎比 Sharding-JDBC 要好,因爲有現成的分片規則,也提供了 4 種 ID 生成方式,通過註解可以支持高級功能,比如跨庫關聯查詢。
建議:小型項目,分片規則簡單的項目可以用 Sharding-JDBC。大型項目,可以用Mycat。
Sharding-JDBC 案例 :
Sharding-JDBC要實現分庫分表的方案主要分爲以下幾個步驟:
- 配置數據源。
- 配置表規則 TableRuleConfiguration。
- 配置分庫+分表策略 DatabaseShardingStrategyConfig,TableShardingStrategyConfig。
- 獲取數據源對象。
- 執行數據庫操作。
1.首先我們創建一個標準的springboot工程。還需要引入相關依賴:
<dependencies>
<!--sharding-jdbc -->
<dependency>
<groupId>io.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
2.編寫類 :
1 public class ShardJDBCTest {
2 public static void main(String[] args) throws SQLException {
3 // 1. 配置真實數據源
4 Map<String, DataSource> dataSourceMap = new HashMap<>();
5
6 // 配置第一個數據源
7 DruidDataSource dataSource1 = new DruidDataSource();
8 dataSource1.setDriverClassName("com.mysql.jdbc.Driver");
9 dataSource1.setUrl("jdbc:mysql://192.168.1.101:3306/shard0");
10 dataSource1.setUsername("root");
11 dataSource1.setPassword("123456");
12 dataSourceMap.put("ds0", dataSource1);
13
14 // 配置第二個數據源
15 DruidDataSource dataSource2 = new DruidDataSource();
16 dataSource2.setDriverClassName("com.mysql.jdbc.Driver");
17 dataSource2.setUrl("jdbc:mysql://192.168.1.104:3306/shard1");
18 dataSource2.setUsername("root");
19 dataSource2.setPassword("123456");
20 dataSourceMap.put("ds1", dataSource2);
21
22 // 2. 配置Order表規則
23 TableRuleConfiguration orderTableRuleConfig = new TableRuleConfiguration();
24 orderTableRuleConfig.setLogicTable("order");
25 orderTableRuleConfig.setActualDataNodes("ds${0..1}.order${0..1}");
26
27
28 // 3. 配置分庫 + 分表策略
29 orderTableRuleConfig.setDatabaseShardingStrategyConfig(new InlineShardingStrategyConfiguration("order_id", "ds${order_id % 2}"));
30 orderTableRuleConfig.setTableShardingStrategyConfig(new InlineShardingStrategyConfiguration("order_id", "order${order_id % 2}"));
31
32 // 配置分片規則
33 ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
34 shardingRuleConfig.getTableRuleConfigs().add(orderTableRuleConfig);
35
36 Map<String, Object> map = new HashMap<>();
37
38 // 4.獲取數據源對象
39 DataSource dataSource = ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfig, map, new Properties());
40
41 String sql = "SELECT * from order WHERE user_id=?";
42 try (
43 Connection conn = dataSource.getConnection();
44 PreparedStatement preparedStatement = conn.prepareStatement(sql)) {
45 preparedStatement.setInt(1, 2673);
46 System.out.println();
47 // 5.執行sql
48 try (ResultSet rs = preparedStatement.executeQuery()) {
49 while (rs.next()) {
50 // %2結果,路由到 shard1.order1
51 System.out.println("order_id---------" + rs.getInt(1));
52 System.out.println("user_id---------" + rs.getInt(2));
53 System.out.println("create_time---------" + rs.getTime(3));
54 System.out.println("total_price---------" + rs.getInt(4));
55 }
56 }
57 }
58 }
59 }
3.在兩個庫上都建立對應的 order1、order2 表,表結構一致。字段自己調整就行
運行上述main方法可以查看到相應的效果。
總結:ShardingRuleConfiguration 可以包含多個 TableRuleConfiguration(多張表),也可以設置默認的分庫和分表策略。每個 TableRuleConfiguration 可以針對表設置 ShardingStrategyConfiguration,包括分庫分分表策略。
ShardingStrategyConfiguration 有 5 種實現(標準、行內、複合、Hint、無)。ShardingDataSourceFactory 利用 ShardingRuleConfiguration 創建數據源。有了數據源,就可以走 JDBC 的流程了。
更多配置可以參考 https://shardingsphere.apache.org/document/current/cn/user-manual/shardingsphere-jdbc/configuration/
整合SpringBoot :
Sharding-JDBC 進行與 SpringBoot 的整合是方便的,主要是進行配置文件的配置。
1.創建標準的SpringBoot 工程,再加入以下依賴:
<dependencies>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>io.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!--xa分佈式事務-->
<dependency>
<groupId>io.shardingsphere</groupId>
<artifactId>sharding-transaction-2pc-xa</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>io.shardingsphere</groupId>
<artifactId>sharding-transaction-spring-boot-starter</artifactId>
<version>3.1.0</version>
</dependency>
</dependencies>
2.進行 分庫分表規則配置,新建 application-sharding.yml 文件 :
sharding:
jdbc:
datasource:
# 數據源
names: ds0,ds1
ds0:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.1.101:3306/shard0
username: root
password: 123456
ds1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.1.104:3306/shard1
username: root
password: 123456
config:
sharding:
# 默認數據源,不分庫分表到達這個數據源
default-data-source-name: ds0
#【默認分庫策略】對user_id取模
default-database-strategy:
inline:
sharding-column: user_id
algorithm-expression: ds$->{user_id % 2}
# 【分表策略】
tables:
# dictionary是廣播表
dictionary:
key-generator-column-name: dictionary_id
actual-data-nodes: ds$->{0..1}.dictionary
# user表只分庫不分表
user:
key-generator-column-name: user_id
actual-data-nodes: ds$->{0..1}.user
# order表分庫分表
order:
key-generator-column-name: order_id
actual-data-nodes: ds$->{0..1}.order$->{0..1}
table-strategy:
inline:
sharding-column: order_id
algorithm-expression: order$->{order_id%2}
# order_item表分庫分表
order_item:
key-generator-column-name: order_item_id
actual-data-nodes: ds$->{0..1}.order_item$->{0..1}
table-strategy:
inline:
sharding-column: order_id
algorithm-expression: order_item$->{order_id%2}
props:
sql.show: true
3.其他關於 mybatis 的相關配置這裏就不貼出來了。然後在數據庫中創建對應的表。編寫 dao、service 進行測試。關於事務、全局ID、自定義分片策略下篇博客中會詳細介紹。