1?ShardingJDBC介紹
? ? ? ? 1.1 常見概念術語
? ? ? ? ? ? ? ? ① 數據節點Node:數據分片的最小單元,由數據源名稱和數據表組成
? ? ? ? ? ? ? ? ? ? ? ? 如:ds0.product_order_0
? ? ? ? ? ? ? ? ② 真實表:再分片的數據庫中真實存在的物理表
? ? ? ? ? ? ? ? ? ? ? ? 如:product_order_0
? ? ? ? ? ? ? ? ③ 邏輯表:相同邏輯和數據結構表的總稱
? ? ? ? ? ? ? ? ? ? ? ? 如:product_order
? ? ? ? ? ? ? ? ④ 綁定規則:指分片規則一致的主表和子表
? ? ? ? ? ? ? ? ? ? ? ? 如:order表和order_item表,都是按照order_id分片
? ? ? ? ? ? ? ? 綁定表之間的多表關聯查詢不會出現笛卡爾積關聯,關聯查詢效率提升
? ? ? ? 1.2 常見分片算法
? ? ? ? ? ? ? ? 分片鍵:用于分片的數據庫字段,是將數據庫(表)水平拆分的關鍵字段
? ? ? ? ? ? ? ??ShardingJDBC既支持單分片鍵,也支持多個字段進行分片
? ? ? ? ? ? ? ? 分片策略
????????????????
? ? ? ? ? ? ? ? ? ? ? ? ① 行表達式分片:InlineShardingStrategy
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 只支持單分片鍵
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 使用groovy表達式,提供對SQL語言的 = 和 IN 的分片操作支持
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 如:product_order_$->{user_id % 2} => product_order_0 和 product_order_1
? ? ? ? ? ? ? ? ? ? ? ? ② 標準分片:StandardShardingStrategy
????????????????????????????????只支持單分片鍵
????????????????????????????????PreciseShardingAlgorithm:精準分片,處理?= 和 IN 的分片操作
????????????????????????????????RangeShardingAlgorithm:范圍分片,處理?BETWEEN AND?的分片操作
? ? ? ? ? ? ? ? ? ? ? ? ③ 復合分片:ComplexShardingStrategy
????????????????????????????????支持多分片鍵
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 提供??= 、 IN 和?BETWEEN AND?的分片操作
? ? ? ? ? ? ? ? ? ? ? ? ④ Hint分片:HintShardingStrategy
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 無需配置分片鍵,外部手動指定分片鍵
? ? ? ? ? ? ? ? ? ? ? ? ⑤ 不分片:NoneShardingStrategy
2 快速入門
? ? ? ? SpringBoot整合ShardingJDBC
(1)導入依賴
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.5.5</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.38</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.1</version></dependency><dependency><groupId>org.apache.shardingsphere</groupId><artifactId>sharding-jdbc-spring-boot-starter</artifactId><version>4.1.1</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.30</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><version>2.5.5</version></dependency> </dependencies>
(2)編寫啟動類
@SpringBootApplication @EnableTransactionManagement @MapperScan("com.pandy.mapper") public class OrderApplication {public static void main(String[] args) {SpringApplication.run(OrderApplication.class, args);} }
(3)創建數據庫、表(2個庫,4個表)
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;
(4)編寫實體類
@Data @TableName("product_order") @EqualsAndHashCode(callSuper = false) public class ProductOrderDO {@TableId(value = "id",type = IdType.AUTO)private Long id;private String outTradeNo;private String state;private Date createTime;private Double payAmount;private String nickname;private Long userId; }
(5)編寫配置信息-分庫分表(這里以分表為例,以user_id為分片鍵)
# 打印執行的數據庫以及語句 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.jdbc.Driver spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://192.168.5.135:3306/sharding_db_0?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true spring.shardingsphere.datasource.ds0.username=root spring.shardingsphere.datasource.ds0.password=root# 第二個數據庫 spring.shardingsphere.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver spring.shardingsphere.datasource.ds1.jdbc-url=jdbc:mysql://192.168.5.135:3306/sharding_db_1?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true spring.shardingsphere.datasource.ds1.username=root spring.shardingsphere.datasource.ds1.password=root# 指定product_order表的數據分布情況,配置數據節點,行表達式標識符使用 ${...} 或 $->{...}, # 但前者與 Spring 本身的文件占位符沖突,所以在 Spring 環境中建議使用 $->{...} spring.shardingsphere.sharding.tables.product_order.actual-data-nodes=ds0.product_order_$->{0..1}# 指定product_order表的分片策略,分片策略包括【分片鍵和分片算法】 spring.shardingsphere.sharding.tables.product_order.table-strategy.inline.sharding-column=user_id spring.shardingsphere.sharding.tables.product_order.table-strategy.inline.algorithm-expression=product_order_$->{user_id % 2}
(6)單元測試
@Test public void testInsertProductOrder() {for(int i=0; i<10; i++) {ProductOrderDO orderDO = new ProductOrderDO();orderDO.setOutTradeNo(UUID.randomUUID().toString().replaceAll("-",""));orderDO.setCreateTime(new Date());orderDO.setPayAmount(100d);orderDO.setState("NEW");orderDO.setNickname("pandy-" + i);orderDO.setUserId(Long.parseLong(i + ""));productOrderMapper.insert(orderDO);} }
分庫分表執行邏輯
(7)主鍵重復問題
? ? ? ? 使用自增主鍵,出現主鍵ID重復問題
3 分庫分表常見主鍵ID生成策略
? ? ? ? 需求:
? ? ? ? ? ? ? ? ① 性能強勁
? ? ? ? ? ? ? ? ② 全局唯一
? ? ? ? ? ? ? ? ③ 防止惡意用戶根據ID規則來猜測和獲取數據
? ? ? ? 3.1 業界常見解決方案
? ? ? ? ? ? ? ? (1)自增ID,設置不同的自增步長
? ? ? ? ? ? ? ? ? ? ? ? 缺點:① 未來擴容比較麻煩
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ② 主從切換時不一致可能會導致重復ID
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ③ 性能瓶頸
? ? ? ? ? ? ? ? (2)UUID
UUID.randomUUID().toString().replaceAll("-","");
? ? ? ? ? ? ? ? ? ? ? ? 優點:性能非常高,沒有網絡消耗
? ? ? ? ? ? ? ? ? ? ? ? 缺點:① 無序的字符串,不具備趨勢自增特性
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ② UUID太長,不易于存儲,浪費存儲空間
? ? ? ? ? ? ? ? (3)Redis發號器
? ? ? ? ? ? ? ? ? ? ? ? 利用Redis的incr 或incrby 來實現,原子操作,線程安全
? ? ? ? ? ? ? ? ? ? ? ? 缺點:① 需要占用網絡資源,增加系統復雜性
? ? ? ? ? ? ? ? (4)snowflake雪花算法
? ? ? ? ? ? ? ? ? ? ? ? twitter開源的分布式ID生成算法
? ? ? ? ? ? ? ? ? ? ? ? 生成的ID中包含時間戳,所以生成的ID按照時間遞增
? ? ? ? ? ? ? ? ? ? ? ? 部署多臺服務器,需要保證系統時間一樣,機器編號不一樣
? ? ? ? ? ? ? ? ? ? ? ? 缺點:依賴系統時間(時鐘回撥問題)
? ? ? ? ? ? ? ? 配置使用shardingjdbc的雪花算法
# 配置ID使用雪花算法
spring.shardingsphere.sharding.key-generator.column=id
spring.shardingsphere.sharding.key-generator.type=SNOWFLAKE
? ? ? ? 看一下源碼,?shardingjdbc的雪花算法是怎么解決時鐘回撥問題的?
4 廣播表和綁定表配置?
? ? ? ? 廣播表:指所有的分片數據源中都存在的表,表結構和表中的數據在每個數據庫中均完全一致
? ? ? ? ? ? ? ? 如:字典表,配置表等
(1)創建一個配置表
CREATE TABLE `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;
(3)創建實體類
@Data @TableName("config") @EqualsAndHashCode(callSuper = false) public class ConfigDO {@TableId(value = "id")private Long id;private String configKey;private String configValue;private String type; }
(3)添加廣播表配置
#配置廣播表 spring.shardingsphere.sharding.broadcast-tables=config
(4)測試代碼?
@Test public void insertConfig() {ConfigDO configDO = new ConfigDO();configDO.setConfigKey("iphone");configDO.setConfigValue("iphone16秒殺廣告");configDO.setType("AD");configMapper.insert(configDO); }
5 分庫分表核心流程
? ? ? ? 解析 --> 路由 --> 改寫?--> 執行?--> 結果歸并
? ? ? ? (1)解析
? ? ? ? ? ? ? ? 詞法解析
? ? ? ? ? ? ? ? 語法解析
? ? ? ? (2)路由
? ? ? ? ? ? ? ? 分片路由(帶分片鍵):直接路由,標準路由,笛卡爾積路由
? ? ? ? ? ? ? ? 廣播路由(不帶分片鍵):全庫表路由,全庫路由,全實例路由
? ? ? ? (3)改寫
? ? ? ? ? ? ? ? 將邏輯SQL改寫為可以正確執行的真實SQL
? ? ? ? (4)執行
? ? ? ? ? ? ? ? 采用自動化的執行引擎
? ? ? ? ? ? ? ? 內存限制模式:適用于OLAP(連接數量不做限制,多線程并發執行)
? ? ? ? ? ? ? ? 連接限制模式:適用于OLAP(1庫1線程,多庫多線程,保證數據庫資源足夠多使用)
? ? ? ? (5)結果歸并
? ? ? ? ? ? ? ? 從各個數據節點獲取多數據結果集,組合成為一個結果集
? ? ? ? ? ? ? ? 流式歸并:每一次從結果集中獲取到數據
? ? ? ? ? ? ? ? 內存歸并:分片結果集的數據存儲在內存中