在分布式服務中,要實現數據源得選擇有如下相關方案
- 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 的質量和影響力的認可。
官網 :https://shardingsphere.apache.org/index_zh.html
一般我們用的還是 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)
分布式事務
- 標准化事務接口
- 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也做出了相關的說明
與 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工程。還需要引入相關依賴:
<dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core</artifactId> <version>5.0.0-alpha</version> </dependency> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>3.4.1</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>
2.編寫類 :
public class ShardingJdbc5Test { public static void main(String[] args) throws SQLException { //代表真實的數據源 Map<String, DataSource> dataSourceMap = new HashMap<>(); //數據源1 HikariDataSource source = new HikariDataSource(); source.setDriverClassName("com.mysql.cj.jdbc.Driver"); source.setJdbcUrl(String.format("jdbc:mysql://%s:%s/%s?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8", "192.168.1.101", 3306, "study")); source.setUsername("root"); source.setPassword("123456"); //數據源2 HikariDataSource source2 = new HikariDataSource(); source2.setDriverClassName("com.mysql.cj.jdbc.Driver"); source2.setJdbcUrl(String.format("jdbc:mysql://%s:%s/%s?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8", "192.168.1.101", 3306, "study2")); source2.setUsername("root"); source2.setPassword("123456"); dataSourceMap.put("ds0", source); dataSourceMap.put("ds1", source2); //創建分片規則 // * 針對數據庫 // * 針對表 //* 一定要配置分片鍵 //* 一定要配置分片算法 //* 完全唯一id的問題 ShardingRuleConfiguration configuration = new ShardingRuleConfiguration(); ShardingTableRuleConfiguration tableRuleConfiguration = new ShardingTableRuleConfiguration("t_order", "ds${0..1}.t_order_${0..1}"); tableRuleConfiguration.setKeyGenerateStrategy(new KeyGenerateStrategyConfiguration("order_id", "snowflake")); //把邏輯表和真實表的對應關系添加到分片規則配置中 configuration.getTables().add(tableRuleConfiguration); //設置數據庫分庫規則 configuration.setDefaultDatabaseShardingStrategy( new StandardShardingStrategyConfiguration ("user_id", "db-inline")); Properties properties = new Properties(); properties.setProperty("algorithm-expression", "ds${user_id%2}"); //設置分庫策略 configuration.getShardingAlgorithms(). put("db-inline", new ShardingSphereAlgorithmConfiguration("INLINE", properties)); //設置表的分片規則(數據的水平拆分) configuration.setDefaultTableShardingStrategy(new StandardShardingStrategyConfiguration ("order_id", "order-inline")); //設置分表策略 Properties props = new Properties(); props.setProperty("algorithm-expression", "t_order_${order_id%2}"); configuration.getShardingAlgorithms().put("order-inline", new ShardingSphereAlgorithmConfiguration("INLINE", props)); //設置主鍵生成策略 // * UUID // * 雪花算法 Properties idProperties = new Properties(); idProperties.setProperty("worker-id", "123"); configuration.getKeyGenerators().put("snowflake", new ShardingSphereAlgorithmConfiguration( "SNOWFLAKE", idProperties)); //被代理的 數據源 DataSource dataSource = ShardingSphereDataSourceFactory .createDataSource(dataSourceMap, Collections.singleton(configuration), new Properties()); //初始化數據庫表 String sql = "CREATE TABLE IF NOT EXISTS t_order (order_id BIGINT NOT NULL AUTO_INCREMENT, user_id INT NOT NULL, address_id BIGINT NOT NULL, status VARCHAR(50), PRIMARY KEY (order_id))"; try (Connection connection = dataSource.getConnection(); Statement statement = connection.createStatement()) { statement.executeUpdate(sql); } System.out.println("-------------- Process Success Begin ---------------"); Random random = new Random(); System.out.println("---------------------------- Insert Data ----------------------------"); List<Long> result = new ArrayList<>(10); for (int i = 1; i <= 10; i++) { Order order = new Order(); order.setUserId(random.nextInt(10000)); order.setAddressId(i); order.setStatus("INSERT_TEST"); String insertSql = "INSERT INTO t_order (user_id, address_id, status) VALUES (?, ?, ?)"; try (Connection connection = dataSource.getConnection(); PreparedStatement preparedStatement = connection.prepareStatement(insertSql, Statement.RETURN_GENERATED_KEYS)) { preparedStatement.setInt(1, order.getUserId()); preparedStatement.setLong(2, order.getAddressId()); preparedStatement.setString(3, order.getStatus()); preparedStatement.executeUpdate(); try (ResultSet resultSet = preparedStatement.getGeneratedKeys()) { if (resultSet.next()) { order.setOrderId(resultSet.getLong(1)); } } } } System.out.println("-------------- Process Success Finish --------------"); } }
3.在兩個庫上都建立對應的 order1、order2 表,表結構一致。字段自己調整就行
運行上述main方法可以查看到相應的效果。
總結:ShardingRuleConfiguration 可以包含多個 ShardingTableRuleConfiguration(多張表),也可以設置默認的分庫和分表策略。每個 ShardingTableRuleConfiguration 可以針對表設置 ShardingSphereAlgorithmConfiguration,包括分庫分表策略。
ShardingSphereAlgorithmConfiguration有 5 種實現(標准、自動、復合、Hint、自定義)。ShardingDataSourceFactory 利用 ShardingRuleConfiguration 創建數據源。有了數據源,就可以走 JDBC 的流程了。
- 自動分片算法:取模分片算法(MOD)、哈希取模分片算法(HASH_MOD)、基於分片容量的范圍分片算法(VOLUME_RANGE)、基於分片邊界的范圍分片算法(BOUNDARY_RANGE)、自動時間段分片算法(AUTO_INTERVAL)
- 標准分片算法:行表達式分片算法(INLINE)、時間范圍分片算法(INTERVAL)
- 復合分片算法:復合行表達式分片算法(COMPLEX_INLINE)多個分片鍵,其他都是單一的
- Hint 分片算法:Hint 行表達式分片算法(HINT_INLINE)
- 自定義類分片算法(CLASS_BASED)
更多配置可以參考 https://shardingsphere.apache.org/document/current/cn/user-manual/shardingsphere-jdbc/configuration
整合SpringBoot :
Sharding-JDBC 進行與 SpringBoot (2.3.0)的整合是方便的,主要是進行配置文件的配置。
1.創建標准的SpringBoot 工程,再加入以下依賴:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.4.1</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.72</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.9</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> </dependency> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId> <version>5.0.0-alpha</version> </dependency> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>3.4.2</version> </dependency> </dependencies>
2.進行 分庫分表規則配置,新建 application-sharding.properties文件 :
server.port=8080 spring.shardingsphere.datasource.names=ds-0,ds-1 spring.shardingsphere.datasource.common.type=com.zaxxer.hikari.HikariDataSource spring.shardingsphere.datasource.common.driver-class-name=com.mysql.cj.jdbc.Driver spring.shardingsphere.datasource.ds-0.username=root spring.shardingsphere.datasource.ds-0.password=123456 spring.shardingsphere.datasource.ds-0.jdbc-url=jdbc:mysql://192.168.1.101:3306/study?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8 spring.shardingsphere.datasource.ds-1.username=root spring.shardingsphere.datasource.ds-1.password=123456 spring.shardingsphere.datasource.ds-1.jdbc-url=jdbc:mysql://192.168.1.101:3306/study2?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8 # 行表達式分庫分表策略 # 針對分庫的規則配置 spring.shardingsphere.rules.sharding.default-database-strategy.standard.sharding-column=user_id spring.shardingsphere.rules.sharding.default-database-strategy.standard.sharding-algorithm-name=database-inline spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=ds-$->{0..1}.t_order_$->{0..1} spring.shardingsphere.rules.sharding.tables.t_order.table-strategy.standard.sharding-column=order_id spring.shardingsphere.rules.sharding.tables.t_order.table-strategy.standard.sharding-algorithm-name=t-order-inline # 單個 綁定表 #spring.shardingsphere.rules.sharding.binding-tables=t_order,t_order_detail # 如果有多個 #spring.shardingsphere.rules.sharding.binding-tables[0]=t_order,t_order_detail #spring.shardingsphere.rules.sharding.binding-tables[1]=t_order,t_order_detail # 采用key-generate-strategy 的 字段 spring.shardingsphere.rules.sharding.tables.t_order.key-generate-strategy.column=order_id spring.shardingsphere.rules.sharding.tables.t_order.key-generate-strategy.key-generator-name=snowflake # 分庫策略--行表達式分片算法 spring.shardingsphere.rules.sharding.sharding-algorithms.database-inline.type=INLINE spring.shardingsphere.rules.sharding.sharding-algorithms.database-inline.props.algorithm-expression=ds-$->{user_id % 2} # 分表策略--行表達式分片算法 spring.shardingsphere.rules.sharding.sharding-algorithms.t-order-inline.type=INLINE spring.shardingsphere.rules.sharding.sharding-algorithms.t-order-inline.props.algorithm-expression=t_order_$->{order_id % 2} # 雪花算法 spring.shardingsphere.rules.sharding.key-generators.snowflake.type=SNOWFLAKE spring.shardingsphere.rules.sharding.key-generators.snowflake.props.worker-id=123
3.其他關於 mybatis 的相關配置這里就不貼出來了。然后在數據庫中創建對應的表。編寫 dao、service 進行測試。關於事務、全局ID、自定義分片策略下篇博客中會詳細介紹。