ShardingSphere5入門到實戰
網際網路業務興起之後,海量使用者加上海量資料的特點,單個資料庫伺服器已經難以滿足業務需要,必須考慮資料庫叢集的方式來提升效能。高效能資料庫叢集的第一種方式是「讀寫分離」
,第二種方式是「資料庫分片」
。
讀寫分離原理:讀寫分離的基本原理是將資料庫讀寫操作分散到不同的節點上,下面是其基本架構圖:
讀寫分離的基本實現:
主庫負責處理事務性的增刪改操作,從庫負責處理查詢操作
,能夠有效的避免由資料更新導致的行鎖,使得整個系統的查詢效能得到極大的改善。根據 SQL 語意的分析
,將讀操作和寫操作分別路由至主庫與從庫
。一主多從
的設定方式,可以將查詢請求均勻的分散到多個資料副本,能夠進一步的提升系統的處理能力。多主多從
的方式,不但能夠提升系統的吞吐量,還能夠提升系統的可用性,可以達到在任何一個資料庫宕機,甚至磁碟物理損壞的情況下仍然不影響系統的正常執行。下圖展示了根據業務需要,將使用者表的寫操作和讀操路由到不同的資料庫的方案:
CAP 理論:
CAP 定理(CAP theorem)又被稱作布魯爾定理(Brewer's theorem),是加州大學伯克利分校的電腦科學家埃裡克·布魯爾(Eric Brewer)在 2000 年的 ACM PODC 上提出的一個猜想。對於設計分散式系統的架構師來說,CAP 是必須掌握的理論。
在一個分散式系統中
,當涉及讀寫操作時,只能保證一致性(Consistence)、可用性(Availability)、分割區容錯性(Partition Tolerance)三者中的兩個,另外一個必須被犧牲。
(不是錯誤和超時的響應)
(可能是丟包,也可能是連線中斷,還可能是擁塞)
,系統能夠繼續「履行職責」CAP特點:
在實際設計過程中,每個系統不可能只處理一種資料,而是包含多種型別的資料,有的資料必須選擇 CP,有的資料必須選擇 AP,分散式系統理論上不可能選擇 CA 架構。
為了保證一致性
,當發生分割區現象後,N1 節點上的資料已經更新到 y,但由於 N1 和 N2 之間的複製通道中斷,資料 y 無法同步到 N2,N2 節點上的資料還是 x。這時使用者端 C 存取 N2 時,N2 需要返回 Error,提示使用者端 C「系統現在發生了錯誤」,
這種處理方式違背了可用性
(Availability)的要求,因此 CAP 三者只能滿足 CP。為了保證可用性
,當發生分割區現象後,N1 節點上的資料已經更新到 y,但由於 N1 和 N2 之間的複製通道中斷,資料 y 無法同步到 N2,N2 節點上的資料還是 x。這時使用者端 C 存取 N2 時,N2 將當前自己擁有的資料 x 返回給使用者端 C 了
,而實際上當前最新的資料已經是 y 了,這就不滿足一致性
(Consistency)的要求了,因此 CAP 三者只能滿足 AP。注意:這裡 N2 節點返回 x,雖然不是一個「正確」的結果,但是一個「合理」的結果,因為 x 是舊的資料,並不是一個錯亂的值,只是不是最新的資料而已。CAP 理論中的 C 在實踐中是不可能完美實現的
,在資料複製的過程中,節點N1 和節點 N2 的資料並不一致(強一致性)。即使無法做到強一致性
,但應用可以採用適合的方式達到最終一致性
。具有如下特點:
最終一致性(Eventual Consistency):系統中的所有資料副本經過一定時間後,最終能夠達到一致的狀態。
讀寫分離的問題:
讀寫分離分散了資料庫讀寫操作的壓力,但沒有分散儲存壓力,為了滿足業務資料儲存的需求,就需要將儲存分散到多臺資料庫伺服器上
。
資料分片:
將存放在單一資料庫中的資料分散地存放至多個資料庫或表中,以達到提升效能瓶頸以及可用性的效果。 資料分片的有效手段是對關係型資料庫進行分庫和分表
。資料分片的拆分方式又分為垂直分片和水平分片
。
垂直分庫:
按照業務拆分的方式稱為垂直分片,又稱為縱向拆分
,它的核心理念是專庫專用。 在拆分之前,一個資料庫由多個資料表構成,每個表對應著不同的業務。而拆分之後,則是按照業務將表進行歸類,分佈到不同的資料庫中,從而將壓力分散至不同的資料庫。
下圖展示了根據業務需要,將使用者表和訂單表垂直分片到不同的資料庫的方案:
垂直拆分可以緩解資料量和存取量帶來的問題,但無法根治。如果垂直拆分之後,表中的資料量依然超過單節點所能承載的閾值,則需要水平分片來進一步處理。
垂直分表:
垂直分表適合將表中某些不常用的列,或者是佔了大量空間的列拆分出去。
假設我們是一個婚戀網站,使用者在篩選其他使用者的時候,主要是用 age 和 sex 兩個欄位進行查詢,而 nickname 和 description 兩個欄位主要用於展示,一般不會在業務查詢中用到。description 本身又比較長,因此我們可以將這兩個欄位獨立到另外一張表中,這樣在查詢 age 和 sex 時,就能帶來一定的效能提升。
垂直分表引入的複雜性主要體現在表操作的數量要增加。例如,原來只要一次查詢就可以獲取 name、age、sex、nickname、description,現在需要兩次查詢,一次查詢獲取 name、age、sex,另外一次查詢獲取 nickname、description。
水平分表適合錶行數特別大的表,水平分表屬於水平分片
。
水平分片又稱為橫向拆分。
相對於垂直分片,它不再將資料根據業務邏輯分類,而是通過某個欄位(或某幾個欄位),根據某種規則將資料分散至多個庫或表中,每個分片僅包含資料的一部分。 例如:根據主鍵分片,偶數主鍵的記錄放入 0 庫(或表),奇數主鍵的記錄放入 1 庫(或表),如下圖所示。
單表進行切分後,是否將多個表分散在不同的資料庫伺服器中,可以根據實際的切分效果來確定。
水平分表:單表切分為多表後,新的表即使在同一個資料庫伺服器中,也可能帶來可觀的效能提升,如果效能能夠滿足業務要求,可以不拆分到多臺資料庫伺服器,畢竟業務分庫也會引入很多複雜性;
水平分庫:如果單表拆分為多表後,單臺伺服器依然無法滿足效能要求,那就需要將多個表分散在不同的資料庫伺服器中。
阿里巴巴Java開發手冊:
【推薦】單錶行數超過 500 萬行或者單表容量超過 2GB,才推薦進行分庫分表。
說明:如果預計三年後的資料量根本達不到這個級別,
請不要在建立表時就分庫分表
。
下圖展現了將資料分片與讀寫分離一同使用時,應用程式與資料庫叢集之間的複雜拓撲關係。
讀寫分離和資料分片具體的實現方式一般有兩種: 程式程式碼封裝
和中介軟體封裝
。
程式程式碼封裝指在程式碼中抽象一個資料存取層(或中間層封裝)
,實現讀寫操作分離和資料庫伺服器連線的管理。
其基本架構是:以讀寫分離為例
中介軟體封裝指的是獨立一套系統出來
,實現讀寫操作分離和資料庫伺服器連線的管理。對於業務伺服器來說,存取中介軟體和存取資料庫沒有區別,在業務伺服器看來,中介軟體就是一個資料庫伺服器。
基本架構是:以讀寫分離為例
Apache ShardingSphere(程式級別和中介軟體級別)
MyCat(資料庫中介軟體)
官網:https://shardingsphere.apache.org/index_zh.html
檔案:https://shardingsphere.apache.org/document/5.1.1/cn/overview/
Apache ShardingSphere 由 JDBC、Proxy 和 Sidecar(規劃中)這 3 款既能夠獨立部署,又支援混合部署配合使用的產品組成。
程式程式碼封裝
定位為輕量級 Java 框架,在 Java 的 JDBC 層提供的額外服務
。 它使用使用者端直連資料庫,以 jar 包形式提供服務
,無需額外部署和依賴,可理解為增強版的 JDBC 驅動,完全相容 JDBC 和各種 ORM 框架。
中介軟體封裝
定位為透明化的資料庫代理端
,提供封裝了資料庫二進位制協定的伺服器端版本,用於完成對異構語言的支援。 目前提供 MySQL 和 PostgreSQL版本,它可以使用任何相容 MySQL/PostgreSQL 協定的存取使用者端(如:MySQL Command Client, MySQL Workbench, Navicat 等)運算元據,對 DBA 更加友好。
基本原理:
slave會從master讀取binlog來進行資料同步
具體步驟:
step1:
master將資料改變記錄到二進位制紀錄檔(binary log)
中。step2:
當slave上執行 start slave
命令之後,slave會建立一個 IO 執行緒
用來連線master,請求master中的binlog。step3:
當slave連線master時,master會建立一個 log dump 執行緒
,用於傳送 binlog 的內容。在讀取 binlog 的內容的操作中,會對主節點上的 binlog 加鎖,當讀取完成並行送給從伺服器後解鎖。step4:
IO 執行緒接收主節點 binlog dump 程序發來的更新之後,儲存到 中繼紀錄檔(relay log)
中。step5:
slave的SQL執行緒
,讀取relay log紀錄檔,並解析成具體操作,從而實現主從操作一致,最終資料一致。伺服器規劃:使用docker
方式建立,主從伺服器IP一致,埠號不一致
atguigu-mysql-master
,埠3306
atguigu-mysql-slave1
,埠3307
atguigu-mysql-slave2
,埠3308
注意:如果此時防火牆是開啟的,則先關閉防火牆,並重啟docker
,否則後續安裝的MySQL無法啟動
#關閉docker
systemctl stop docker
#關閉防火牆
systemctl stop firewalld
#啟動docker
systemctl start docker
埠3306
docker run -d \
-p 3306:3306 \
-v /atguigu/mysql/master/conf:/etc/mysql/conf.d \
-v /atguigu/mysql/master/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
--name atguigu-mysql-master \
mysql:8.0.29
預設情況下MySQL的binlog紀錄檔是自動開啟的,可以通過如下設定定義一些可選設定
vim /atguigu/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=mysql
#binlog-ignore-db=infomation_schema
重啟MySQL容器
docker restart atguigu-mysql-master
binlog格式說明:
寫指令
,效能高,但是now()之類的函數以及獲取系統引數的操作會出現主從資料不同步的問題。寫後的資料
,批次操作時效能較差,解決now()或者 user()或者 @@hostname 等操作在主從機器上不一致的問題。binlog-ignore-db和binlog-do-db的優先順序問題:
#進入容器:env LANG=C.UTF-8 避免容器中顯示中文亂碼
docker exec -it atguigu-mysql-master env LANG=C.UTF-8 /bin/bash
#進入容器內的mysql命令列
mysql -uroot -p
#修改預設密碼校驗方式
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
-- 建立slave使用者
CREATE USER 'atguigu_slave'@'%';
-- 設定密碼
ALTER USER 'atguigu_slave'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
-- 授予複製許可權
GRANT REPLICATION SLAVE ON *.* TO 'atguigu_slave'@'%';
-- 重新整理許可權
FLUSH PRIVILEGES;
執行完此步驟後不要再操作主伺服器MYSQL
,防止主伺服器狀態值變化
SHOW MASTER STATUS;
記下File
和Position
的值。執行完此步驟後不要再操作主伺服器MYSQL,防止主伺服器狀態值變化。
可以設定多臺從機slave1、slave2...,這裡以設定slave1為例,請參考slave1獨立完成slave2的設定
埠3307
docker run -d \
-p 3307:3306 \
-v /atguigu/mysql/slave1/conf:/etc/mysql/conf.d \
-v /atguigu/mysql/slave1/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
--name atguigu-mysql-slave1 \
mysql:8.0.29
vim /atguigu/mysql/slave1/conf/my.cnf
設定如下內容:
[mysqld]
# 伺服器唯一id,每臺伺服器的id必須不同,如果設定其他從機,注意修改id
server-id=2
# 中繼紀錄檔名,預設xxxxxxxxxxxx-relay-bin
#relay-log=relay-bin
重啟MySQL容器
docker restart atguigu-mysql-slave1
#進入容器:
docker exec -it atguigu-mysql-slave1 env LANG=C.UTF-8 /bin/bash
#進入容器內的mysql命令列
mysql -uroot -p
#修改預設密碼校驗方式
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
在從機上執行以下SQL操作
CHANGE MASTER TO MASTER_HOST='192.168.100.201',
MASTER_USER='atguigu_slave',MASTER_PASSWORD='123456', MASTER_PORT=3306,
MASTER_LOG_FILE='binlog.000003',MASTER_LOG_POS=1357;
啟動從機的複製功能,執行SQL:
START SLAVE;
-- 檢視狀態(不需要分號)
SHOW SLAVE STATUS\G
兩個關鍵程序:下面兩個引數都是Yes,則說明主從設定成功!
在主機中執行以下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);
需要的時候,可以使用如下SQL語句
-- 在從機上執行。功能說明:停止I/O 執行緒和SQL執行緒的操作。
stop slave;
-- 在從機上執行。功能說明:用於刪除SLAVE資料庫的relaylog紀錄檔檔案,並重新啟用新的relaylog檔案。
reset slave;
-- 在主機上執行。功能說明:刪除所有的binglog紀錄檔檔案,並將紀錄檔索引檔案清空,重新開始所有新的紀錄檔檔案。
-- 用於第一次進行搭建主從庫時,進行主庫binlog初始化工作;
reset master;
啟動主從同步後,常見錯誤是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
SLAVE STOP;
-- 在主機檢視mater狀態
SHOW MASTER STATUS;
-- 在主機重新整理紀錄檔
FLUSH LOGS;
-- 再次在主機檢視mater狀態(會發現File和Position發生了變化)
SHOW MASTER STATUS;
-- 修改從機連線主機的SQL,並重新連線即可
啟動docker容器後提示 WARNING: IPv4 forwarding is disabled. Networking will not work.
此錯誤,雖然不影響主從同步的搭建,但是如果想從遠端使用者端通過以下方式連線docker中的MySQL則沒法連線
C:\Users\administrator>mysql -h 192.168.100.201 -P 3306 -u root -p
解決方案:
#修改組態檔:
vim /usr/lib/sysctl.d/00-system.conf
#追加
net.ipv4.ip_forward=1
#接著重啟網路
systemctl restart network
專案型別:Spring Initializr
SpringBoot腳手架:http://start.aliyun.com
專案名:sharding-jdbc-demo
SpringBoot版本:2.3.7.RELEASE
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.1.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
package com.atguigu.shardingjdbcdemo.entity;
@TableName("t_user")
@Data
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String uname;
}
package com.atguigu.shardingjdbcdemo.mapper;
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
application.properties:
# 應用名稱
spring.application.name=sharging-jdbc-demo
# 開發環境設定
spring.profiles.active=dev
# 記憶體模式
spring.shardingsphere.mode.type=Memory
# 設定真實資料來源
spring.shardingsphere.datasource.names=master,slave1,slave2
# 設定第 1 個資料來源
spring.shardingsphere.datasource.master.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.master.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.master.jdbc-url=jdbc:mysql://192.168.100.201:3306/db_user
spring.shardingsphere.datasource.master.username=root
spring.shardingsphere.datasource.master.password=123456
# 設定第 2 個資料來源
spring.shardingsphere.datasource.slave1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.slave1.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.slave1.jdbc-url=jdbc:mysql://192.168.100.201:3307/db_user
spring.shardingsphere.datasource.slave1.username=root
spring.shardingsphere.datasource.slave1.password=123456
# 設定第 3 個資料來源
spring.shardingsphere.datasource.slave2.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.slave2.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.slave2.jdbc-url=jdbc:mysql://192.168.100.201:3308/db_user
spring.shardingsphere.datasource.slave2.username=root
spring.shardingsphere.datasource.slave2.password=123456
# 讀寫分離型別,如: Static,Dynamic
spring.shardingsphere.rules.readwrite-splitting.data-sources.myds.type=Static
# 寫資料來源名稱
spring.shardingsphere.rules.readwrite-splitting.data-sources.myds.props.write-data-source-name=master
# 讀資料來源名稱,多個從資料來源用逗號分隔
spring.shardingsphere.rules.readwrite-splitting.data-sources.myds.props.read-data-source-names=slave1,slave2
# 負載均衡演演算法名稱
spring.shardingsphere.rules.readwrite-splitting.data-sources.myds.load-balancer-name=alg_round
# 負載均衡演演算法設定
# 負載均衡演演算法型別
spring.shardingsphere.rules.readwrite-splitting.load-balancers.alg_round.type=ROUND_ROBIN
spring.shardingsphere.rules.readwrite-splitting.load-balancers.alg_random.type=RANDOM
spring.shardingsphere.rules.readwrite-splitting.load-balancers.alg_weight.type=WEIGHT
spring.shardingsphere.rules.readwrite-splitting.load-balancers.alg_weight.props.slave1=1
spring.shardingsphere.rules.readwrite-splitting.load-balancers.alg_weight.props.slave2=2
# 列印SQl
spring.shardingsphere.props.sql-show=true
package com.atguigu.shardingjdbcdemo;
@SpringBootTest
class ReadwriteTest {
@Autowired
private UserMapper userMapper;
/**
* 寫入資料的測試
*/
@Test
public void testInsert(){
User user = new User();
user.setUname("張三丰");
userMapper.insert(user);
}
}
為了保證主從庫間的事務一致性,避免跨服務的分散式事務,ShardingSphere-JDBC的主從模型中,事務中的資料讀寫均用主庫
。
/**
* 事務測試
*/
@Transactional//開啟事務
@Test
public void testTrans(){
User user = new User();
user.setUname("鐵錘");
userMapper.insert(user);
List<User> users = userMapper.selectList(null);
}
/**
* 讀資料測試
*/
@Test
public void testSelectAll(){
List<User> users1 = userMapper.selectList(null);
List<User> users2 = userMapper.selectList(null);//執行第二次測試負載均衡
}
也可以在web請求中測試負載均衡
package com.atguigu.shardingjdbcdemo.controller;
@RestController
@RequestMapping("/userController")
public class UserController {
@Autowired
private UserMapper userMapper;
/**
* 測試負載均衡策略
*/
@GetMapping("selectAll")
public void selectAll(){
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}
}
ShardingSphere-JDBC遠端連線的方式預設的密碼加密規則是:mysql_native_password
因此需要在伺服器端修改伺服器的密碼加密規則,如下:
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
伺服器規劃:使用docker
方式建立如下容器
伺服器:容器名server-user
,埠 3301
伺服器:容器名server-order
,埠3302
docker run -d \
-p 3301:3306 \
-v /atguigu/server/user/conf:/etc/mysql/conf.d \
-v /atguigu/server/user/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
--name server-user \
mysql:8.0.29
#進入容器:
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';
CREATE DATABASE db_user;
USE db_user;
CREATE TABLE t_user (
id BIGINT AUTO_INCREMENT,
uname VARCHAR(30),
PRIMARY KEY (id)
);
docker run -d \
-p 3302:3306 \
-v /atguigu/server/order/conf:/etc/mysql/conf.d \
-v /atguigu/server/order/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
--name server-order \
mysql:8.0.29
#進入容器:
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';
CREATE DATABASE db_order;
USE db_order;
CREATE TABLE t_order (
id BIGINT AUTO_INCREMENT,
order_no VARCHAR(30),
user_id BIGINT,
amount DECIMAL(10,2),
PRIMARY KEY(id)
);
package com.atguigu.shardingjdbcdemo.entity;
@TableName("t_order")
@Data
public class Order {
@TableId(type = IdType.AUTO)
private Long id;
private String orderNo;
private Long userId;
private BigDecimal amount;
}
package com.atguigu.shardingjdbcdemo.mapper;
@Mapper
public interface OrderMapper extends BaseMapper<Order> {
}
# 應用名稱
spring.application.name=sharding-jdbc-demo
# 環境設定
spring.profiles.active=dev
# 記憶體模式
spring.shardingsphere.mode.type=Memory
# 設定真實資料來源
spring.shardingsphere.datasource.names=server-user,server-order
# 設定第 1 個資料來源
spring.shardingsphere.datasource.server-user.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.server-user.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.server-user.jdbc-url=jdbc:mysql://192.168.100.201:3301/db_user
spring.shardingsphere.datasource.server-user.username=root
spring.shardingsphere.datasource.server-user.password=123456
# 設定第 2 個資料來源
spring.shardingsphere.datasource.server-order.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.server-order.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.server-order.jdbc-url=jdbc:mysql://192.168.100.201:3302/db_order
spring.shardingsphere.datasource.server-order.username=root
spring.shardingsphere.datasource.server-order.password=123456
# 標準分片表設定(資料節點)
# spring.shardingsphere.rules.sharding.tables.<table-name>.actual-data-nodes=值
# 值由資料來源名 + 表名組成,以小數點分隔。
# <table-name>:邏輯表名
spring.shardingsphere.rules.sharding.tables.t_user.actual-data-nodes=server-user.t_user
spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=server-order.t_order
# 列印SQL
spring.shardingsphere.props.sql-show=true
package com.atguigu.shardingjdbcdemo;
@SpringBootTest
public class ShardingTest {
@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("ATGUIGU001");
order.setUserId(user.getId());
order.setAmount(new BigDecimal(100));
orderMapper.insert(order);
}
/**
* 垂直分片:查詢資料測試
*/
@Test
public void testSelectFromOrderAndUser(){
User user = userMapper.selectById(1L);
Order order = orderMapper.selectById(1L);
}
}
伺服器規劃:使用docker
方式建立如下容器
伺服器:容器名server-order0
,埠3310
伺服器:容器名server-order1
,埠3311
docker run -d \
-p 3310:3306 \
-v /atguigu/server/order0/conf:/etc/mysql/conf.d \
-v /atguigu/server/order0/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
--name server-order0 \
mysql:8.0.29
#進入容器:
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';
注意:
水平分片的id需要在業務層實現,不能依賴資料庫的主鍵自增
CREATE DATABASE db_order;
USE db_order;
CREATE TABLE t_order0 (
id BIGINT,
order_no VARCHAR(30),
user_id BIGINT,
amount DECIMAL(10,2),
PRIMARY KEY(id)
);
CREATE TABLE t_order1 (
id BIGINT,
order_no VARCHAR(30),
user_id BIGINT,
amount DECIMAL(10,2),
PRIMARY KEY(id)
);
docker run -d \
-p 3311:3306 \
-v /atguigu/server/order1/conf:/etc/mysql/conf.d \
-v /atguigu/server/order1/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
--name server-order1 \
mysql:8.0.29
#進入容器:
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';
注意:
水平分片的id需要在業務層實現,不能依賴資料庫的主鍵自增
CREATE DATABASE db_order;
USE db_order;
CREATE TABLE t_order0 (
id BIGINT,
order_no VARCHAR(30),
user_id BIGINT,
amount DECIMAL(10,2),
PRIMARY KEY(id)
);
CREATE TABLE t_order1 (
id BIGINT,
order_no VARCHAR(30),
user_id BIGINT,
amount DECIMAL(10,2),
PRIMARY KEY(id)
);
#========================基本設定
# 應用名稱
spring.application.name=sharging-jdbc-demo
# 開發環境設定
spring.profiles.active=dev
# 記憶體模式
spring.shardingsphere.mode.type=Memory
# 列印SQl
spring.shardingsphere.props.sql-show=true
#========================資料來源設定
# 設定真實資料來源
spring.shardingsphere.datasource.names=server-user,server-order0,server-order1
# 設定第 1 個資料來源
spring.shardingsphere.datasource.server-user.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.server-user.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.server-user.jdbc-url=jdbc:mysql://192.168.100.201:3301/db_user
spring.shardingsphere.datasource.server-user.username=root
spring.shardingsphere.datasource.server-user.password=123456
# 設定第 2 個資料來源
spring.shardingsphere.datasource.server-order0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.server-order0.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.server-order0.jdbc-url=jdbc:mysql://192.168.100.201:3310/db_order
spring.shardingsphere.datasource.server-order0.username=root
spring.shardingsphere.datasource.server-order0.password=123456
# 設定第 3 個資料來源
spring.shardingsphere.datasource.server-order1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.server-order1.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.server-order1.jdbc-url=jdbc:mysql://192.168.100.201:3311/db_order
spring.shardingsphere.datasource.server-order1.username=root
spring.shardingsphere.datasource.server-order1.password=123456
#========================標準分片表設定(資料節點設定)
# spring.shardingsphere.rules.sharding.tables.<table-name>.actual-data-nodes=值
# 值由資料來源名 + 表名組成,以小數點分隔。多個表以逗號分隔,支援 inline 表示式。
# <table-name>:邏輯表名
spring.shardingsphere.rules.sharding.tables.t_user.actual-data-nodes=server-user.t_user
spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=server-order0.t_order0,server-order0.t_order1,server-order1.t_order0,server-order1.t_order1
修改Order實體類的主鍵策略:
//@TableId(type = IdType.AUTO)//依賴資料庫的主鍵自增策略
@TableId(type = IdType.ASSIGN_ID)//分散式id
測試:保留上面設定中的一個分片表節點分別進行測試,檢查每個分片節點是否可用
spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=server-order0.t_order0
spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=server-order0.t_order1
spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=server-order1.t_order0
spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=server-order1.t_order1
測試程式碼:
/**
* 水平分片:插入資料測試
*/
@Test
public void testInsertOrder(){
Order order = new Order();
order.setOrderNo("ATGUIGU001");
order.setUserId(1L);
order.setAmount(new BigDecimal(100));
orderMapper.insert(order);
}
優化上一步的分片表設定
https://shardingsphere.apache.org/document/5.1.1/cn/features/sharding/concept/inline-expression/
#========================標準分片表設定(資料節點設定)
# spring.shardingsphere.rules.sharding.tables.<table-name>.actual-data-nodes=值
# 值由資料來源名 + 表名組成,以小數點分隔。多個表以逗號分隔,支援 inline 表示式。
# <table-name>:邏輯表名
spring.shardingsphere.rules.sharding.tables.t_user.actual-data-nodes=server-user.t_user
spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=server-order$->{0..1}.t_order$->{0..1}
水平分庫:
分片規則:order表中user_id
為偶數時,資料插入server-order0伺服器
,user_id
為奇數時,資料插入server-order1伺服器
。這樣分片的好處是,同一個使用者的訂單資料,一定會被插入到同一臺伺服器上,查詢一個使用者的訂單時效率較高。
#------------------------分庫策略
# 分片列名稱
spring.shardingsphere.rules.sharding.tables.t_order.database-strategy.standard.sharding-column=user_id
# 分片演演算法名稱
spring.shardingsphere.rules.sharding.tables.t_order.database-strategy.standard.sharding-algorithm-name=alg_inline_userid
#------------------------分片演演算法設定
# 行表示式分片演演算法
# 分片演演算法型別
spring.shardingsphere.rules.sharding.sharding-algorithms.alg_inline_userid.type=INLINE
# 分片演演算法屬性設定
spring.shardingsphere.rules.sharding.sharding-algorithms.alg_inline_userid.props.algorithm-expression=server-order$->{user_id % 2}
為了方便測試,先設定只在 t_order0
表上進行測試
spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=server-order$->{0..1}.t_order0
測試:可以分別測試行表示式分片演演算法和取模分片演演算法
/**
* 水平分片:分庫插入資料測試
*/
@Test
public void testInsertOrderDatabaseStrategy(){
for (long i = 0; i < 4; i++) {
Order order = new Order();
order.setOrderNo("ATGUIGU001");
order.setUserId(i + 1);
order.setAmount(new BigDecimal(100));
orderMapper.insert(order);
}
}
水平分表:
分片規則:order表中order_no的雜湊值為偶數時
,資料插入對應伺服器的t_order0表
,order_no的雜湊值為奇數時
,資料插入對應伺服器的t_order1表
。因為order_no是字串形式,因此不能直接取模。
#------------------------分表策略
# 分片列名稱
spring.shardingsphere.rules.sharding.tables.t_order.table-strategy.standard.sharding-column=order_no
# 分片演演算法名稱
spring.shardingsphere.rules.sharding.tables.t_order.table-strategy.standard.sharding-algorithm-name=alg_hash_mod
#------------------------分片演演算法設定
# 雜湊取模分片演演算法
# 分片演演算法型別
spring.shardingsphere.rules.sharding.sharding-algorithms.alg_hash_mod.type=HASH_MOD
# 分片演演算法屬性設定
spring.shardingsphere.rules.sharding.sharding-algorithms.alg_hash_mod.props.sharding-count=2
測試前不要忘記將如下節點改回原來的狀態
spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=server-order$->{0..1}.t_order$->{0..1}
測試:
/**
* 水平分片:分表插入資料測試
*/
@Test
public void testInsertOrderTableStrategy(){
for (long i = 1; i < 5; i++) {
Order order = new Order();
order.setOrderNo("ATGUIGU" + i);
order.setUserId(1L);
order.setAmount(new BigDecimal(100));
orderMapper.insert(order);
}
for (long i = 5; i < 9; i++) {
Order order = new Order();
order.setOrderNo("ATGUIGU" + i);
order.setUserId(2L);
order.setAmount(new BigDecimal(100));
orderMapper.insert(order);
}
}
/**
* 測試雜湊取模
*/
@Test
public void testHash(){
//注意hash取模的結果是整個字串hash後再取模,和數值字尾是奇數還是偶數無關
System.out.println("ATGUIGU001".hashCode() % 2);
System.out.println("ATGUIGU0011".hashCode() % 2);
}
查詢測試:
/**
* 水平分片:查詢所有記錄
* 查詢了兩個資料來源,每個資料來源中使用UNION ALL連線兩個表
*/
@Test
public void testShardingSelectAll(){
List<Order> orders = orderMapper.selectList(null);
orders.forEach(System.out::println);
}
/**
* 水平分片:根據user_id查詢記錄
* 查詢了一個資料來源,每個資料來源中使用UNION ALL連線兩個表
*/
@Test
public void testShardingSelectByUserId(){
QueryWrapper<Order> orderQueryWrapper = new QueryWrapper<>();
orderQueryWrapper.eq("user_id", 1L);
List<Order> orders = orderMapper.selectList(orderQueryWrapper);
orders.forEach(System.out::println);
}
雪花演演算法:
https://shardingsphere.apache.org/document/5.1.1/cn/features/sharding/concept/key-generator/
水平分片需要關注全域性序列,因為不能簡單的使用基於資料庫的主鍵自增。
這裡有兩種方案:一種是基於MyBatisPlus的id策略;一種是ShardingSphere-JDBC的全域性序列設定。
基於MyBatisPlus的id策略:
將Order類的id設定成如下形式
@TableId(type = IdType.ASSIGN_ID)
private Long id;
基於ShardingSphere-JDBC的全域性序列設定
:和前面的MyBatisPlus的策略二選一
#------------------------分散式序列策略設定
# 分散式序列列名稱
spring.shardingsphere.rules.sharding.tables.t_order.key-generate-strategy.column=id
# 分散式序列演演算法名稱
spring.shardingsphere.rules.sharding.tables.t_order.key-generate-strategy.key-generator-name=alg_snowflake
# 分散式序列演演算法設定
# 分散式序列演演算法型別
spring.shardingsphere.rules.sharding.key-generators.alg_snowflake.type=SNOWFLAKE
# 分散式序列演演算法屬性設定
#spring.shardingsphere.rules.sharding.key-generators.alg_snowflake.props.xxx=
此時,需要將實體類中的id策略修改成以下形式:
//當設定了shardingsphere-jdbc的分散式序列時,自動使用shardingsphere-jdbc的分散式序列
//當沒有設定shardingsphere-jdbc的分散式序列時,自動依賴資料庫的主鍵自增策略
@TableId(type = IdType.AUTO)
在server-order0、server-order1
伺服器中分別建立兩張訂單詳情表t_order_item0、t_order_item1
我們希望同一個使用者的訂單表和訂單詳情表中的資料都在同一個資料來源中,避免跨庫關聯
,因此這兩張表我們使用相同的分片策略。
那麼在t_order_item
中我們也需要建立order_no
和user_id
這兩個分片鍵
CREATE TABLE t_order_item0(
id BIGINT,
order_no VARCHAR(30),
user_id BIGINT,
price DECIMAL(10,2),
`count` INT,
PRIMARY KEY(id)
);
CREATE TABLE t_order_item1(
id BIGINT,
order_no VARCHAR(30),
user_id BIGINT,
price DECIMAL(10,2),
`count` INT,
PRIMARY KEY(id)
);
package com.atguigu.shardingjdbcdemo.entity;
@TableName("t_order_item")
@Data
public class OrderItem {
//當設定了shardingsphere-jdbc的分散式序列時,自動使用shardingsphere-jdbc的分散式序列
@TableId(type = IdType.AUTO)
private Long id;
private String orderNo;
private Long userId;
private BigDecimal price;
private Integer count;
}
package com.atguigu.shargingjdbcdemo.mapper;
@Mapper
public interface OrderItemMapper extends BaseMapper<OrderItem> {
}
t_order_item的分片表、分片策略、分散式序列策略和t_order一致
#------------------------標準分片表設定(資料節點設定)
spring.shardingsphere.rules.sharding.tables.t_order_item.actual-data-nodes=server-order$->{0..1}.t_order_item$->{0..1}
#------------------------分庫策略
# 分片列名稱
spring.shardingsphere.rules.sharding.tables.t_order_item.database-strategy.standard.sharding-column=user_id
# 分片演演算法名稱
spring.shardingsphere.rules.sharding.tables.t_order_item.database-strategy.standard.sharding-algorithm-name=alg_inline_userid
#------------------------分表策略
# 分片列名稱
spring.shardingsphere.rules.sharding.tables.t_order_item.table-strategy.standard.sharding-column=order_no
# 分片演演算法名稱
spring.shardingsphere.rules.sharding.tables.t_order_item.table-strategy.standard.sharding-algorithm-name=alg_hash_mod
#------------------------分散式序列策略設定
# 分散式序列列名稱
spring.shardingsphere.rules.sharding.tables.t_order_item.key-generate-strategy.column=id
# 分散式序列演演算法名稱
spring.shardingsphere.rules.sharding.tables.t_order_item.key-generate-strategy.key-generator-name=alg_snowflake
同一個使用者的訂單表和訂單詳情表中的資料都在同一個資料來源中,避免跨庫關聯
/**
* 測試關聯表插入
*/
@Test
public void testInsertOrderAndOrderItem(){
for (long i = 1; i < 3; i++) {
Order order = new Order();
order.setOrderNo("ATGUIGU" + i);
order.setUserId(1L);
orderMapper.insert(order);
for (long j = 1; j < 3; j++) {
OrderItem orderItem = new OrderItem();
orderItem.setOrderNo("ATGUIGU" + i);
orderItem.setUserId(1L);
orderItem.setPrice(new BigDecimal(10));
orderItem.setCount(2);
orderItemMapper.insert(orderItem);
}
}
for (long i = 5; i < 7; i++) {
Order order = new Order();
order.setOrderNo("ATGUIGU" + i);
order.setUserId(2L);
orderMapper.insert(order);
for (long j = 1; j < 3; j++) {
OrderItem orderItem = new OrderItem();
orderItem.setOrderNo("ATGUIGU" + i);
orderItem.setUserId(2L);
orderItem.setPrice(new BigDecimal(1));
orderItem.setCount(3);
orderItemMapper.insert(orderItem);
}
}
}
需求:查詢每個訂單的訂單號和總訂單金額
package com.atguigu.shardingjdbcdemo.entity;
@Data
public class OrderVo {
private String orderNo;
private BigDecimal amount;
}
package com.atguigu.shardingjdbcdemo.mapper;
@Mapper
public interface OrderMapper extends BaseMapper<Order> {
@Select({"SELECT o.order_no, SUM(i.price * i.count) AS amount",
"FROM t_order o JOIN t_order_item i ON o.order_no = i.order_no",
"GROUP BY o.order_no"})
List<OrderVo> getOrderAmount();
}
/**
* 測試關聯表查詢
*/
@Test
public void testGetOrderAmount(){
List<OrderVo> orderAmountList = orderMapper.getOrderAmount();
orderAmountList.forEach(System.out::println);
}
在原來水平分片設定的基礎上新增如下設定:
#------------------------繫結表
spring.shardingsphere.rules.sharding.binding-tables[0]=t_order,t_order_item
設定完繫結表後再次進行關聯查詢的測試:
如果不設定繫結表:測試的結果為8個SQL。多表關聯查詢會出現笛卡爾積關聯。
如果設定繫結表:測試的結果為4個SQL。 多表關聯查詢不會出現笛卡爾積關聯,關聯查詢效率將大大提升。
繫結表:
指分片規則一致的一組分片表。 使用繫結表進行多表關聯查詢時,必須使用分片鍵進行關聯,否則會出現笛卡爾積關聯或跨庫關聯,從而影響查詢效率。
指所有的分片資料來源中都存在的表,表結構及其資料在每個資料庫中均完全一致。 適用於資料量不大且需要與海量資料的表進行關聯查詢的場景,例如:字典表。
廣播具有以下特性:
(1)插入、更新操作會實時在所有節點上執行,保持各個分片的資料一致性
(2)查詢操作,只從一個節點獲取
(3)可以跟任何一個表進行 JOIN 操作
在server-order0、server-order1和server-user伺服器中分別建立t_dict表
CREATE TABLE t_dict(
id BIGINT,
dict_type VARCHAR(200),
PRIMARY KEY(id)
);
package com.atguigu.shardingjdbcdemo.entity;
@TableName("t_dict")
@Data
public class Dict {
//可以使用MyBatisPlus的雪花演演算法
@TableId(type = IdType.ASSIGN_ID)
private Long id;
private String dictType;
}
package com.atguigu.shardingjdbcdemo.mapper;
@Mapper
public interface DictMapper extends BaseMapper<Dict> {
}
#資料節點可不設定,預設情況下,向所有資料來源廣播
spring.shardingsphere.rules.sharding.tables.t_dict.actual-data-nodes=server-user.t_dict,server-order$->{0..1}.t_dict
# 廣播表
spring.shardingsphere.rules.sharding.broadcast-tables[0]=t_dict
@Autowired
private DictMapper dictMapper;
/**
* 廣播表:每個伺服器中的t_dict同時新增了新資料
*/
@Test
public void testBroadcast(){
Dict dict = new Dict();
dict.setDictType("type1");
dictMapper.insert(dict);
}
/**
* 查詢操作,只從一個節點獲取資料
* 隨機負載均衡規則
*/
@Test
public void testSelectBroadcast(){
List<Dict> dicts = dictMapper.selectList(null);
dicts.forEach(System.out::println);
}
本文來自部落格園,作者:自律即自由-,轉載請註明原文連結:https://www.cnblogs.com/deyo/p/17515677.html