一、高性能架構模式
數據庫集群,第一種方式“讀寫分離”,第二種方式“數據庫分片”。
1.1 讀寫分離架構
讀寫分離原理:將數據庫讀寫操作分散到不同的節點上。
讀寫分離的基本實現:
- 主庫負責處理事務性的增刪改操作,從庫負責處理查詢操作。
- 讀寫分離是根據SQL語義的分析,將讀寫操作分別路由至主庫和從庫。
- 通過一主多從的配置方式,可以將查詢請求均勻的分散到多個數據副本,進一步提升系統處理能力。
- 使用多主多從,提升系統的吞吐量,可用性,任何一個數據庫宕機或者磁盤損壞,不影響系統正常運行。
- 根據業務需要,將用戶表讀寫操作路由到不同的數據庫
CAP理論:
在一個分布式系統中,涉及讀寫操作時,保證一致性(Consistence)、可用性(Availability)、分區容錯性(Partition Tolerance),只能做到CP,AP。
CP:為保證一致性,發生分區現象后,N1節點數據更新到y,當N1與N2之間的通道中斷后,N2為同步數據,客戶端訪問N2時返回Error。
AP:為保證可用性,客戶端訪問N2將數據返回給C。
CAP理論C實踐中不能完美實現,無法做到強一致性。可以采用適合的方式達到最終一致性。
- 基本可用(Basically Available):分布式系統在出現故障時,允許損失部分可用性,即保證核心可用。
- 軟狀態(Soft State):允許系統存在中間狀態,而該中間狀態不會影響系統整體可用性。這里的中間狀態就是 CAP 理論中的數據不一致。
最終一致性(Eventual Consistency):系統中的所有數據副本經過一定時間后,最終能夠達到一致的狀態。
1.2 數據庫分片架構
- 讀寫分離問題:
????????分散數據庫讀寫操作壓力,沒有分散存儲壓力,需要將存儲分散到多臺數據庫服務器上。
- 數據分片:
????????將存放在單一數據庫中的數據分散地存放到多個數據庫或表,數據分片的有效手段是對關系型是對關系型數據庫進行分庫和分表。數據分片的拆分方式又分為垂直分片和水平分片。
1.2.1 垂直分片
垂直分庫:
??按照業務拆分的方式稱為垂直分片,又稱為縱向拆分
,它的核心理念是專庫專用。 在拆分之前,一個數據庫由多個數據表構成,每個表對應著不同的業務。而拆分之后,則是按照業務將表進行歸類,分布到不同的數據庫中,從而將壓力分散至不同的數據庫。
將用戶表和訂單表垂直分片到不同的數據庫的方案:
????????垂直拆分可以緩解數據量和訪問量帶來的問題,但無法根治。如果垂直拆分之后,表中的數據量依然超過單節點所能承載的閾值,則需要水平分片來進一步處理。
?????????垂直分表:
垂直分表適合將表中某些不常用的列,或者是占了大量空間的列拆分出去。
1.2.2 水平分片
??水平分片又稱為橫向拆分。
相對于垂直分片,它不再將數據根據業務邏輯分類,而是通過某個字段(或某幾個字段),根據某種規則將數據分散至多個庫或表中,每個分片僅包含數據的一部分。 例如:根據主鍵分片,偶數主鍵的記錄放入 0 庫(或表),奇數主鍵的記錄放入 1 庫(或表),如下圖所示。
單表進行切分后,是否將多個表分散在不同的數據庫服務器中,可以根據實際的切分效果來確定。
-
水平分表:單表切分為多表后,新的表即使在同一個數據庫服務器中,也可能帶來可觀的性能提升,如果性能能夠滿足業務要求,可以不拆分到多臺數據庫服務器,畢竟業務分庫也會引入很多復雜性;
-
水平分庫:如果單表拆分為多表后,單臺服務器依然無法滿足性能要求,那就需要將多個表分散在不同的數據庫服務器中。
阿里巴巴Java開發手冊:
【推薦】單表行數超過 500 萬行或者單表容量超過 2GB,才推薦進行分庫分表。
說明:如果預計三年后的數據量根本達不到這個級別,
請不要在創建表時就分庫分表
。
第2章 解決方案
讀寫分離和數據分片具體的實現方式一般有兩種: 程序代碼封裝
和中間件封裝
。
2.1 程序代碼封裝
程序代碼封裝指在代碼中抽象一個數據訪問層(或中間層封裝)
,實現讀寫操作分離和數據庫服務器連接的管理。
其基本架構是:以讀寫分離為例
2.2 中間件封裝
????????中間件封裝指的是獨立一套系統出來
,實現讀寫操作分離和數據庫服務器連接的管理。對于業務服務器來說,訪問中間件和訪問數據庫沒有區別,在業務服務器看來,中間件就是一個數據庫服務器。
????????基本架構是:以讀寫分離為例
2.3 常用解決方案
- Apache ShardingSphere
? - 程序代碼封裝:ShardingSphere-JDBC
? - 中間件封裝:ShardingSphere-Proxy
? 官網:https://shardingsphere.apache.org/index_zh.html? 文檔:https://shardingsphere.apache.org/document/5.4.0/cn/overview/
??
- MyCat:數據庫中間件?
三、MySQL主從同步
3.1 MySQL主從同步原理
- 基本原理:
- slave會從master讀取binlog來進行數據同步
- 具體步驟:
- master將數據改變記錄到二進制日志中。
- 當slave上執行start slave命令之后,salve會創建一個IO線程用來連接master,請求master中的binlog.
- 當slave連接上master時,master會創建一個log dump線程,用于發送binlog的內容。在讀取binlog的內容。在讀取binlog的內容的操作中,會對主節點上的binlog加鎖,當讀取完成并發送給從服務器后解鎖。
- IO線程接收主節點binlog dump 進程發來的更新之后,保存到中繼日志(relay log)中。
- slave的SQL線程,讀取 relay log 日志,并解析成具體操作,實現主從操作一直,最終數據一致。
3.2 一主多從配置
docker方式創建,主從服務器IP一致,端口號不一致。
-
主服務器:容器名
mysql-master
,端口3307
-
從服務器:容器名
mysql-slave1
,端口3308
-
從服務器:容器名
mysql-slave2
,端口3309
注意:如果此時防火墻是開啟的,則先關閉防火墻,并重啟docker
,否則后續安裝的MySQL無法啟動
#關閉docker systemctl stop docker #關閉防火墻 systemctl stop firewalld #啟動docker systemctl start docker
3.2.1 準備主服務器
-
step1:在docker中創建并啟動MySQL主服務器:
端口3307
docker run -d \ -p 3307:3306 \ -v /mysql/master/conf:/etc/mysql/conf.d \ -v /mysql/master/data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=123456 \ --name mysql-master \ mysql:8.0.30
-
step2:創建MySQL主服務器配置文件:
默認情況下MySQL的binlog日志是自動開啟的,可以通過如下配置定義一些可選配置
vim mysql/master/conf/my.cnf
配置如下內容
[mysqld] # 服務器唯一id,默認值1 server-id=1 # 設置日志格式,默認值ROW binlog_format=STATEMENT # 二進制日志名,默認binlog # log-bin=binlog # 設置需要復制的數據庫,默認復制全部數據庫 #binlog-do-db=mytestdb1 #binlog-do-db=mytestdb2 # 設置不需要復制的數據庫 #binlog-ignore-db=mytestdb3 #binlog-ignore-db=mytestdb4
重啟MySQL容器
docker restart mysql-master
binlog格式說明:
-
binlog_format=STATEMENT:日志記錄的是主機數據庫的
寫指令
,性能高,但是now()之類的函數以及獲取系統參數的操作會出現主從數據不同步的問題。 -
binlog_format=ROW(默認):日志記錄的是主機數據庫的
寫后的數據
,批量操作時性能較差,解決now()或者 user()或者 @@hostname 等操作在主從機器上不一致的問題。 -
binlog_format=MIXED:是以上兩種level的混合使用,
有函數用ROW,沒函數用STATEMENT
binlog-ignore-db和binlog-do-db的優先級問題:
-
step3:使用命令行登錄MySQL主服務器:
-
#進入容器:env LANG=C.UTF-8 避免容器中顯示中文亂碼 docker exec -it mysql-master env LANG=C.UTF-8 /bin/bash #進入容器內的mysql命令行 mysql -uroot -p #修改默認密碼校驗方式 ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
-
step4:主機中創建slave用戶://幫助授權從機訪問二進制文件權限
-- 創建slave用戶 CREATE USER 'slave'@'%'; -- 設置密碼 ALTER USER 'slave'@'%' IDENTIFIED WITH mysql_native_password BY '123456'; -- 授予復制權限 GRANT REPLICATION SLAVE ON *.* TO 'slave'@'%'; -- 刷新權限 FLUSH PRIVILEGES;
-
step5:主機中查詢master狀態:
執行完此步驟后不要再操作主服務器MYSQL
,防止主服務器狀態值變化
SHOW MASTER STATUS;
記下File
和Position
的值。執行完此步驟后不要再操作主服務器MYSQL,防止主服務器狀態值變化。
reset master
3.2.2 準備從服務器1
可以配置多臺從機slave1、slave2...,這里以配置slave1為例,請參考slave1獨立完成slave2的配置
-
step1:在docker中創建并啟動MySQL從服務器:
端口3308
docker run -d \ -p 3308:3306 \ -v mysql/slave1/conf:/etc/mysql/conf.d \ -v mysql/slave1/data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=123456 \ --name mysql-slave1 \ mysql:8.0.29
-
step2:創建MySQL從服務器配置文件:
-
vim /mysql/slave1/conf/my.cnf
配置如下內容:
[mysqld] # 服務器唯一id,每臺服務器的id必須不同,如果配置其他從機,注意修改id server-id=2 # 中繼日志名,默認xxxxxxxxxxxx-relay-bin #relay-log=relay-bin
重啟MySQL容器
-
docker restart mysql-slave1
-
step3:使用命令行登錄MySQL從服務器:
#進入容器: docker exec -it mysql-slave1 env LANG=C.UTF-8 /bin/bash #進入容器內的mysql命令行 mysql -uroot -p #修改默認密碼校驗方式 ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
-
step4:在從機上配置主從關系:
在從機上執行以下SQL操作
CHANGE MASTER TO MASTER_HOST='192.168.200.130', MASTER_USER='slave',MASTER_PASSWORD='123456', MASTER_PORT=3306, MASTER_LOG_FILE='binlog.000003',MASTER_LOG_POS=1357;
3.2.3 準備從服務器2
參考3.2.2
3.2.4 啟動主從同步
分別在兩臺從機上啟動從機的復制功能,執行SQL:
START SLAVE; -- 查看狀態(不需要分號) SHOW SLAVE STATUS\G;
兩個關鍵進程:下面兩個參數都是Yes,則說明主從配置成功!
3.2.5 測試主從同步
在主機中執行以下SQL,在從機中查看數據庫、表和數據是否已經被同步
CREATE DATABASE db_user; USE db_user; CREATE TABLE t_user (id BIGINT AUTO_INCREMENT,uname VARCHAR(30),PRIMARY KEY (id) ); INSERT INTO t_user(uname) VALUES('zhang3'); INSERT INTO t_user(uname) VALUES(@@hostname);
3.2.6 常見問題
啟動主從同步后,常見錯誤是
Slave_IO_Running: No 或者 Connecting
的情況,此時查看下方的Last_IO_ERROR
錯誤日志,根據日志中顯示的錯誤信息在網上搜索解決方案即可
典型的錯誤例如:
Last_IO_Error: Got fatal error 1236 from master when reading data from binary log: 'Client requested master to start replication from position > file size'
解決方案:
-- 在從機停止slave -- 在從機上執行。功能說明:停止I/O 線程和SQL線程的操作。 stop slave; ? -- 在從機上執行。功能說明:用于刪除SLAVE數據庫的relaylog日志文件,并重新啟用新的relaylog文件。 reset slave; ? -- 在主機上執行。功能說明:刪除所有的binlog日志文件,并將日志索引文件清空,重新開始所有新的日志文件。 -- 用于第一次進行搭建主從庫時,進行主庫binlog初始化工作; reset master; ? -- 還原主服務器之前的操作 ? -- 在主機查看mater狀態 SHOW MASTER STATUS; -- 在主機刷新日志 FLUSH LOGS; -- 再次在主機查看mater狀態(會發現File和Position發生了變化) SHOW MASTER STATUS; -- 修改從機連接主機的SQL,并重新連接即可
第4章 ShardingSphere-JDBC讀寫分離
4.1 創建SpringBoot程序
4.1.1 創建項目
項目類型:Spring Initializr
SpringBoot腳手架:http://start.aliyun.com
項目名:sharding-jdbc-demo
SpringBoot版本:3.0.5
4.1.2 添加依賴
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency> ?<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency> ?<dependency><groupId>org.apache.shardingsphere</groupId><artifactId>shardingsphere-jdbc-core</artifactId><version>5.4.0</version></dependency> ?<!--兼容jdk17和spring boot3--><dependency><groupId>org.yaml</groupId><artifactId>snakeyaml</artifactId><version>1.33</version></dependency><dependency><groupId>org.glassfish.jaxb</groupId><artifactId>jaxb-runtime</artifactId><version>2.3.8</version></dependency> ?<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.30</version></dependency> ?<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3.1</version></dependency> ?<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency> </dependencies>
4.1.3 創建實體類
@TableName("t_user") @Data public class User {@TableId(type = IdType.AUTO)private Long id;private String uname; }
4.1.4 創建Mapper
? @Mapper public interface UserMapper extends BaseMapper<User> { }
4.1.5 配置 Spring Boot
application.properties:
# 配置 DataSource Driver spring.datasource.driver-class-name=org.apache.shardingsphere.driver.ShardingSphereDriver # 指定 YAML 配置文件 spring.datasource.url=jdbc:shardingsphere:classpath:shardingsphere.yaml
4.1.6 配置shardingsphere
shardingsphere.yaml
模式配置:
mode:type: Standalonerepository:type: JDBC
- 數據源配置:
dataSources:write_ds:dataSourceClassName: com.zaxxer.hikari.HikariDataSourcedriverClassName: com.mysql.cj.jdbc.DriverjdbcUrl: jdbc:mysql://192.168.200.130:3307/db_userusername: rootpassword: 123456read_ds_0:dataSourceClassName: com.zaxxer.hikari.HikariDataSourcedriverClassName: com.mysql.cj.jdbc.DriverjdbcUrl: jdbc:mysql://192.168.200.130:3308/db_userusername: rootpassword: 123456read_ds_1:dataSourceClassName: com.zaxxer.hikari.HikariDataSourcedriverClassName: com.mysql.cj.jdbc.DriverjdbcUrl: jdbc:mysql://192.168.200.130:3309/db_userusername: rootpassword: 123456
讀寫分離配置:
rules:- !READWRITE_SPLITTING#讀寫dataSources:readwrite_ds:writeDataSourceName: write_ds#數據源指向readDataSourceNames:#數據源指向- read_ds_0- read_ds_1transactionalReadQueryStrategy: PRIMARY # 事務內讀請求的路由策略,可選值:PRIMARY(路由至主庫)、FIXED(同一事務內路由至固定數據源)、DYNAMIC(同一事務內路由至非固定數據源)。默認值:DYNAMICloadBalancerName: random#負載均衡隨機loadBalancers:random:type: RANDOM
輸出sql:
props:sql-show: true
4.2 測試
4.2.1 讀寫分離測試
? @SpringBootTest class ShardingJdbcDemoApplicationTests { ?@Autowiredprivate UserMapper userMapper; ?/*** 寫入數據的測試*/@Testpublic void testInsert(){ ?User user = new User();user.setUname("張三豐");userMapper.insert(user);} ? }
4.2.2 負載均衡測試
/*** 負載均衡測試*/ @Test public void testSelect(){ ?for (int i = 0; i < 100; i++) {User user1 = userMapper.selectById(1);} }
負載均衡算法配置:
rules:- !READWRITE_SPLITTINGloadBalancers:random:type: RANDOMround_robin:type: ROUND_ROBINweight:type: WEIGHTprops:read_ds_0: 1read_ds_1: 2#設置權重
4.2.3 事務測試
transactionalReadQueryStrategy: PRIMARY
事務內讀請求的路由策略,可選值:
PRIMARY(路由至主庫)
FIXED(同一事務內路由至固定數據源)
DYNAMIC(同一事務內路由至非固定數據源)。默認值:DYNAMIC
1、測試1
不添加@Transactional:insert對主庫操作,select對從庫操作
2、測試2
添加@Transactional:則insert和select按照transactionalReadQueryStrategy的配置執行
/*** 事務測試*/ @Transactional//開啟事務 @Test public void testTrans(){ ?User user = new User();user.setUname("鐵錘");userMapper.insert(user); ?List<User> users = userMapper.selectList(null); }
注意:在JUnit環境下的@Transactional注解,默認情況下就會對事務進行回滾(即使在沒加注解@Rollback,也會對事務回滾)
3、常見錯誤
ShardingSphere-JDBC遠程連接的方式默認的密碼加密規則是:mysql_native_password
因此需要在服務器端修改服務器的密碼加密規則,如下:
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
第5章 ShardingSphere-JDBC垂直分片
5.1 準備服務器
服務器規劃:使用docker
方式創建如下容器
-
服務器:容器名
server-user
,端口3301
-
服務器:容器名
server-order
,端口3302
5.1.1 創建server-user容器
-
step1:創建容器:
docker run -d \ -p 3301:3306 \ -v server/user/conf:/etc/mysql/conf.d \ -v server/user/data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=123456 \ --name server-user \ mysql:8.0.29
-
step2:登錄MySQL服務器:
#進入容器: docker exec -it server-user env LANG=C.UTF-8 /bin/bash #進入容器內的mysql命令行 mysql -uroot -p #修改默認密碼插件 ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
-
step3:創建數據庫:
CREATE DATABASE db_user; USE db_user; CREATE TABLE t_user (id BIGINT AUTO_INCREMENT,uname VARCHAR(30),PRIMARY KEY (id) );
5.1.2 創建server-order容器
-
step1:創建容器:
docker run -d \ -p 3302:3306 \ -v /server/order/conf:/etc/mysql/conf.d \ -v /server/order/data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=123456 \ --name server-order \ mysql:8.0.30
-
step2:登錄MySQL服務器:
#進入容器: docker exec -it server-order env LANG=C.UTF-8 /bin/bash #進入容器內的mysql命令行 mysql -uroot -p #修改默認密碼插件 ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
-
step3:創建數據庫:
CREATE DATABASE db_order; USE db_order; CREATE TABLE t_order (id BIGINT AUTO_INCREMENT,order_no VARCHAR(30),user_id BIGINT,PRIMARY KEY(id) );
5.2 程序實現
5.2.1 創建實體類
@TableName("t_order") @Data public class Order {@TableId(type = IdType.AUTO)private Long id;private String orderNo;private Long userId; }
5.2.2 創建Mapper
? @Mapper public interface OrderMapper extends BaseMapper<Order> { }
5.2.3 配置垂直分片
模式配置
mode:type: Standalonerepository:type: JDBC
數據源配置:
dataSources:user_ds:dataSourceClassName: com.zaxxer.hikari.HikariDataSourcedriverClassName: com.mysql.cj.jdbc.DriverjdbcUrl: jdbc:mysql://192.168.200.130:3301/db_userusername: rootpassword: 123456order_ds:dataSourceClassName: com.zaxxer.hikari.HikariDataSourcedriverClassName: com.mysql.cj.jdbc.DriverjdbcUrl: jdbc:mysql://192.168.200.130:3302/db_orderusername: rootpassword: 123456
垂直分片配置:
rules:- !SHARDINGtables:t_user:actualDataNodes: user_ds.t_usert_order:actualDataNodes: order_ds.t_order
輸出sql:
props:sql-show: true
5.3 測試垂直分片
@Autowired private UserMapper userMapper; ? @Autowired private OrderMapper orderMapper; ? /*** 垂直分片:插入數據測試*/ @Test public void testInsertOrderAndUser(){ ?User user = new User();user.setUname("強哥");userMapper.insert(user); ?Order order = new Order();order.setOrderNo("001");order.setUserId(user.getId());orderMapper.insert(order); ? } ? /*** 垂直分片:查詢數據測試*/ @Test public void testSelectFromOrderAndUser(){User user = userMapper.selectById(1L);Order order = orderMapper.selectById(1L); }
第6章 ShardingSphere-JDBC水平分片
6.1 準備服務器
服務器規劃:使用docker
方式創建如下容器
-
服務器:容器名
server-order0
,端口3320
-
服務器:容器名
server-order1
,端口3321
6.1.1 創建server-order0容器
-
step1:創建容器:
docker run -d \ -p 3320:3306 \ -v /server/order0/conf:/etc/mysql/conf.d \ -v /server/order0/data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=123456 \ --name server-order0 \ mysql:8.0.29
-
step2:登錄MySQL服務器:
#進入容器: docker exec -it server-order0 env LANG=C.UTF-8 /bin/bash #進入容器內的mysql命令行 mysql -uroot -p #修改默認密碼插件 ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
-
step3:創建數據庫:
注意:
水平分片的id需要在業務層實現,不能依賴數據庫的主鍵自增
CREATE DATABASE db_order; USE db_order; CREATE TABLE t_order0 (id BIGINT,order_no VARCHAR(50),user_id BIGINT,PRIMARY KEY(id) ); CREATE TABLE t_order1 (id BIGINT,order_no VARCHAR(50),user_id BIGINT,PRIMARY KEY(id) );
6.1.2 創建server-order1容器
-
step1:創建容器:
docker run -d \ -p 3321:3306 \ -v /server/order1/conf:/etc/mysql/conf.d \ -v /server/order1/data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=123456 \ --name server-order1 \ mysql:8.0.29
-
step2:登錄MySQL服務器:
#進入容器: docker exec -it server-order1 env LANG=C.UTF-8 /bin/bash #進入容器內的mysql命令行 mysql -uroot -p #修改默認密碼插件 ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
-
step3:創建數據庫:和server-order0相同
注意:
水平分片的id需要在業務層實現,不能依賴數據庫的主鍵自增
//數據庫主鍵自增機制(如 MySQL 的?AUTO_INCREMENT
)無法直接滿足分布式環境下的全局唯一性需求,因此通常需要在業務層實現全局唯一 ID 生成策略
?Snowflake 算法及其變種
- 原理:生成 64 位長整型 ID,結構為:
時間戳(41位)+ 機器ID(10位)+ 序列號(12位)
。
CREATE DATABASE db_order; USE db_order; CREATE TABLE t_order0 (id BIGINT,order_no VARCHAR(30),user_id BIGINT,PRIMARY KEY(id) ); CREATE TABLE t_order1 (id BIGINT,order_no VARCHAR(30),user_id BIGINT,PRIMARY KEY(id) );
6.2 水平分片
6.2.1 配置一個分片節點
模式配置
mode:type: Standalonerepository:type: JDBC
數據源配置:
dataSources:user_ds:dataSourceClassName: com.zaxxer.hikari.HikariDataSourcedriverClassName: com.mysql.cj.jdbc.DriverjdbcUrl: jdbc:mysql://192.168.200.130:3301/db_userusername: rootpassword: 123456order_ds_0:dataSourceClassName: com.zaxxer.hikari.HikariDataSourcedriverClassName: com.mysql.cj.jdbc.DriverjdbcUrl: jdbc:mysql://192.168.200.130:3320/db_orderusername: rootpassword: 123456order_ds_1:dataSourceClassName: com.zaxxer.hikari.HikariDataSourcedriverClassName: com.mysql.cj.jdbc.DriverjdbcUrl: jdbc:mysql://192.168.200.130:3321/db_orderusername: rootpassword: 123456
配置一個order分片節點:
rules:- !SHARDINGtables:t_user:actualDataNodes: user_ds.t_usert_order:actualDataNodes: order_ds_0.t_order0
輸出sql:
props:sql-show: true
修改Order實體類的主鍵策略:
//@TableId(type = IdType.AUTO)//依賴數據庫的主鍵自增策略 @TableId(type = IdType.ASSIGN_ID)//分布式id
測試代碼:
/*** 水平分片:插入數據測試*/ @Test public void testInsertOrder(){ ?Order order = new Order();order.setOrderNo("001");order.setUserId(1L);orderMapper.insert(order); }
6.2.2 水平分庫配置
使用行表達式:核心概念 :: ShardingSphere (apache.org)
將數據 分片到order_ds_0和order_ds_1中
actualDataNodes: order_ds_${0..1}.t_order0
分片算法配置
分片規則:order表中user_id
為偶數時,數據插入server-order0服務器
,user_id
為奇數時,數據插入server-order1服務器
。這樣分片的好處是,同一個用戶的訂單數據,一定會被插入到同一臺服務器上,查詢一個用戶的訂單時效率較高。
rules:- !SHARDINGtables:#數據庫分片和表分片t_user:actualDataNodes: user_ds.t_usert_order:actualDataNodes: order_ds_${0..1}.t_order0databaseStrategy: #databaseStrategy:按 user_id 列使用名為 userid_inline 的算法進行分庫。 #tableStrategy:按 id 列使用名為 orderid_inline 的算法進行分表。standard:shardingColumn: user_idshardingAlgorithmName: userid_inline ?shardingAlgorithms:userid_inline:type: INLINEprops:algorithm-expression: order_ds_${user_id % 2}//分片的算法
測試:
/*** 水平分片:分庫插入數據測試*/ @Test public void testInsertOrderDatabaseStrategy(){ ?for (long i = 0; i < 4; i++) {Order order = new Order();order.setOrderNo(" + System.currentTimeMillis());order.setUserId(i + 1);orderMapper.insert(order);} }
6.2.3 水平分表配置
將數據 分片到order_ds_0和order_ds_1的t_order0和t_order1中
actualDataNodes: order_ds_${0..1}.t_order${0..1}
分片算法配置
分片規則:order表中id
為偶數時,數據插入t_order0數據庫
,id
為奇數時,數據插入t_order1數據庫
。
rules:- !SHARDINGtables:t_user:actualDataNodes: user_ds.t_usert_order:actualDataNodes: order_ds_${0..1}.t_order${0..1}databaseStrategy:standard:shardingColumn: user_idshardingAlgorithmName: userid_inlinetableStrategy:standard:shardingColumn: idshardingAlgorithmName: orderid_inline ?shardingAlgorithms:userid_inline:type: INLINEprops:algorithm-expression: order_ds_${user_id % 2}orderid_inline:type: INLINEprops:algorithm-expression: t_order${id % 2}
測試:
/*** 水平分片:分表插入數據測試*/ @Test public void testInsertOrderTableStrategy(){ ?for (long i = 0; i < 4; i++) {Order order = new Order();order.setOrderNo("" + System.currentTimeMillis());order.setUserId(1L);orderMapper.insert(order);} ?for (long i = 0; i < 4; i++) { ?Order order = new Order();order.setOrderNo(System.currentTimeMillis());order.setUserId(2L);orderMapper.insert(order);} }
6.3 多表關聯
6.3.1 創建關聯表
在server-order0、server-order1
服務器中分別創建兩張訂單詳情表t_order_item0、t_order_item1
我們希望同一個用戶的訂單表和訂單詳情表中的數據都在同一個數據源中,避免跨庫關聯
,因此這兩張表我們使用相同的分片策略。
那么在t_order_item
中我們也需要創建order_id
和user_id
這兩個分片鍵
CREATE TABLE t_order_item0(id BIGINT,user_id BIGINT,order_id BIGINT,price DECIMAL(10,2),`count` INT,PRIMARY KEY(id) ); ? CREATE TABLE t_order_item1(id BIGINT,user_id BIGINT,order_id BIGINT,price DECIMAL(10,2),`count` INT,PRIMARY KEY(id) );
6.3.2 創建實體類
? @TableName("t_order_item") @Data public class OrderItem { ?@TableId(type = IdType.ASSIGN_ID) //分布式idprivate Long id;private Long userId;private Long orderId;private BigDecimal price;private Integer count; }
6.3.3 創建Mapper
? @Mapper public interface OrderItemMapper extends BaseMapper<OrderItem> { }
6.3.4 配置關聯表
t_order_item的分片表、分片策略、分布式序列策略和t_order一致
rules:- !SHARDINGtables:t_user:actualDataNodes: user_ds.t_usert_order:actualDataNodes: order_ds_${0..1}.t_order${0..1}databaseStrategy:standard:shardingColumn: user_idshardingAlgorithmName: userid_inlinetableStrategy:standard:shardingColumn: idshardingAlgorithmName: orderid_inlinet_order_item:actualDataNodes: order_ds_${0..1}.t_order_item${0..1}databaseStrategy:standard:shardingColumn: user_idshardingAlgorithmName: userid_inlinetableStrategy:standard:shardingColumn: order_idshardingAlgorithmName: orderid_item_inline ?shardingAlgorithms:userid_inline:type: INLINEprops:algorithm-expression: order_ds_${user_id % 2}orderid_inline:type: INLINEprops:algorithm-expression: t_order${id % 2}orderid_item_inline:type: INLINEprops:algorithm-expression: t_order_item${order_id % 2}
6.3.5 測試插入數據
同一個用戶的訂單表和訂單詳情表中的數據都在同一個數據源中,避免跨庫關聯
? /*** 測試關聯表插入*/@Testpublic void testInsertOrderAndOrderItem(){ ? ?for (long i = 0; i < 2; i++) { ?Order order = new Order();order.setOrderNo("" + System.currentTimeMillis());order.setUserId(1L);orderMapper.insert(order); ?for (long j = 0; j < 2; j++) {OrderItem orderItem = new OrderItem();orderItem.setUserId(1L);orderItem.setOrderId(order.getId());orderItem.setPrice(new BigDecimal(10));orderItem.setCount(2);orderItemMapper.insert(orderItem);}} ?for (long i = 0; i < 2; i++) {Order order = new Order();order.setOrderNo("" + System.currentTimeMillis());order.setUserId(2L);orderMapper.insert(order);for (long j = 0; j < 2; j++) {OrderItem orderItem = new OrderItem();orderItem.setUserId(2L);orderItem.setOrderId(order.getId());orderItem.setPrice(new BigDecimal(5));orderItem.setCount(2);orderItemMapper.insert(orderItem);}}}