1 概述
本文講述了如何使用
MyBatisPlus
+
ShardingSphereJDBC
進行讀寫分離,以及利用
MySQL
進行一主一從的主從複制。
具體步驟包括:
-
主從複制環境準備(MySQL
)Docker
- 搭建
+ShardingShpereJDBC
+MyBatisPlus
環境Druid
- 測試
2 環境
-
OpenJDK 17.0.3
-
Spring Boot 2.7.0
-
MyBatis Plus 3.5.1
-
MyBatis Plus Generator 3.5.2
-
Druid 1.2.10
-
ShardingSphereJDBC 5.1.1
-
(MySQL 8.0.29
)Docker
3 一些基礎理論
3.1 讀寫分離
讀寫分離,顧名思義就是讀和寫分開,更具體來說,就是:
- 寫操作在主資料庫進行
- 讀操作在從資料庫進行
使用讀寫分離的根本目的就是為了提高并發性能,如果讀寫都在同一台
MySQL
上實作,相信會不如一台
MySQL
寫,另外兩台
MySQL
讀這樣的配置性能高。另一方面,在很多時候都是讀操作的請求要遠遠高于寫操作,這樣就顯得讀寫分離非常有必要了。
3.2 主從複制
主從複制,顧名思義就是把主庫的資料複制到從庫中,因為讀寫分離之後,寫操作都在主庫進行,但是讀操作是在從庫進行的,也就是說,主庫上的資料如果不能複制到從庫中,那麼從庫就不會讀到主庫中的資料。嚴格意義上說,讀寫分離并不要求主從複制,隻需要在主庫寫從庫讀即可,但是如果沒有了主從複制,讀寫分離将失去了它的意義。是以讀寫分離通常與主從複制配合使用。
因為本示例使用的是
MySQL
,這裡就說一下
MySQL
主從複制的原理,如下圖所示:

工作流程如下:
- 主庫修改資料後,将修改日志寫入
binlog
- 從庫的
線程讀取主庫的I/O
,并拷貝到從庫本地的binlog
中binlog
- 從庫本地的
被binlog
線程讀取,執行其中的内容并同步到從庫中SQL
3.3 資料庫中間件簡介
資料庫中間件可以簡化對讀寫分離以及分庫分表的操作,并隐藏底層實作細節,可以像操作單庫單表那樣操作多庫多表,主流的設計方案主要有兩種:
- 服務端代理:需要獨立部署一個代理服務,該代理服務後面管理多個資料庫執行個體,在應用中通過一個資料源與該代理伺服器建立連接配接,由該代理去操作底層資料庫,并傳回相應結果。優點是支援多語言,對業務透明,缺點是實作複雜,實作難度大,同時代理需要確定自身高可用
- 用戶端代理:在連接配接池或資料庫驅動上進行一層封裝,内部與不同的資料庫建立連接配接,并對
進行必要的操作,比如讀寫分離選擇走主庫還是從庫,分庫分表SQL
後如何聚合結果。優點是實作簡單,天然去中心化,缺點是支援語言較少,版本更新困難select
一些常見的資料庫中間件如下:
-
:阿裡開源的關系型資料庫分布式服務中間件,已停更Cobar
-
:脫胎于DRDS
,全稱Cobar
分布式關系型資料庫服務
-
:開源資料庫中間件,目前更新了MyCat
版本MyCat2
-
:Atlas
公司Qihoo 360
平台部基礎架構團隊開發維護的一個基于Web
協定的資料中間層項目,同時還有一個MySQL
的版本,叫NoSQL
Pika
-
:阿裡巴巴自主研發的分布式資料庫服務tddl
-
:Sharding-JDBC
的一個子産品,一個輕量級ShardingShpere
架構Java
4 MySQL
主從複制環境準備
MySQL
看完了一些基礎理論就可以進行動手了,本小節先準備好
MySQL
主從複制的環境,基于
Docker
+
MySQL
官方文檔搭建。
4.1 主庫操作
4.1.1 拉取鏡像并建立容器運作
docker pull mysql
docker run -itd -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 --name master mysql
docker exec -it master /bin/bash
在主庫中進行更新鏡像源,安裝
vim
以及
net-tools
的操作:
cd /etc/apt
echo deb http://mirrors.aliyun.com/debian/ buster main non-free contrib deb-src http://mirrors.aliyun.com/debian/ buster main non-free contrib deb http://mirrors.aliyun.com/debian-security buster/updates main deb-src http://mirrors.aliyun.com/debian-security buster/updates main deb http://mirrors.aliyun.com/debian/ buster-updates main non-free contrib deb-src http://mirrors.aliyun.com/debian/ buster-updates main non-free contrib deb http://mirrors.aliyun.com/debian/ buster-backports main non-free contrib deb-src http://mirrors.aliyun.com/debian/ buster-backports main non-free contrib > sources.list
apt update && apt upgrade
apt install vim net-tools
4.1.2 修改配置檔案
vim /etc/mysql/my.cnf
添加下面兩行資料:
[mysqld]
server-id=1 # 全局唯一,取值[1,2^32-1],預設為1
binlog-do-db=test # 表示需要複制的是哪個庫
修改完成後重新開機。
4.1.3 準備資料源
CREATE DATABASE test;
USE test;
CREATE TABLE user(
id BIGINT PRIMARY KEY,
name VARCHAR(30) NOT NULL,
);
4.1.4 建立一個複制操作的使用者(可選但推薦)
注意建立使用者需要加上
mysql_native_password
,否則會導緻從庫一直處于連接配接狀态:
CREATE USER 'repl'@'172.17.0.3' IDENTIFIED WITH mysql_native_password BY '123456';
GRANT REPLICATION slave ON *.* TO 'repl'@'172.17.0.3';
具體的位址請根據從庫的位址修改,可以先看後面的從庫配置部分。
4.1.5 資料備份(可選)
如果原來的主庫中是有資料的,那麼這部分資料需要手動同步到從庫中:
FLUSH TABLES WITH READ LOCK;
開啟主庫的另一個終端,使用
mysqldump
導出:
mysqldump -u root -p --all-databases --master-data > dbdump.db
導出完成後,解除讀鎖:
UNLOCK TABLES;
4.1.6 檢視主庫狀态
SHOW MASTER STATUS;
需要把
File
以及
Position
記錄下來,後面從庫的配置需要用到。
4.2 從庫操作
4.2.1 拉取鏡像并建立容器運作
docker pull mysql
docker run -itd -p 3307:3306 -e MYSQL_ROOT_PASSWORD=123456 --name slave mysql
docker exec -it slave /bin/bash
進入容器後,像主庫一樣更新源然後安裝
vim
和
net-tools
:
cd /etc/apt
echo deb http://mirrors.aliyun.com/debian/ buster main non-free contrib deb-src http://mirrors.aliyun.com/debian/ buster main non-free contrib deb http://mirrors.aliyun.com/debian-security buster/updates main deb-src http://mirrors.aliyun.com/debian-security buster/updates main deb http://mirrors.aliyun.com/debian/ buster-updates main non-free contrib deb-src http://mirrors.aliyun.com/debian/ buster-updates main non-free contrib deb http://mirrors.aliyun.com/debian/ buster-backports main non-free contrib deb-src http://mirrors.aliyun.com/debian/ buster-backports main non-free contrib > sources.list
apt update && apt upgrade
apt install vim net-tools
4.2.2 修改配置檔案
vim /etc/mysql/my.cnf
添加如下兩行:
[mysqld]
server-id=2 # 全局唯一,不能與主庫相同
replicate-do-db=test # 與主庫相同,表示對該庫進行複制
修改完成後重新開機。
4.2.3 檢視 ip
位址
ip
檢視從庫的
ip
位址,用于給主庫設定同步的使用者:
ifconfig
輸出:
inet 172.17.0.3 netmask 255.255.0.0 broadcast 172.17.255.255
那麼主庫中用于複制的使用者就可以是
。
4.2.4 導入資料(可選)
如果主庫有資料可以先導入到從庫:
mysqldump -u root -p --all-databases < dbdump.db
4.2.5 準備資料源
CREATE DATABASE test;
USE test;
CREATE TABLE user(
id BIGINT PRIMARY KEY,
name VARCHAR(30) NOT NULL,
);
4.2.6 設定主庫
可以使用
change master to
/
change replication source to
(
8.0.23+
)指令:
CHANGE REPLICATION SOURCE TO
source_host='172.17.0.2', # 可以使用ifconfig檢視主庫ip
source_user='repl', # 之前主庫建立的使用者
source_password='123456', # 密碼
source_log_file='binlog.000003', # 之前在主庫上使用show master status檢視的日志檔案
source_log_pos=594; # 同樣使用show master status檢視
4.2.7 開啟從庫
START SLAVE;
SHOW SLAVE STATUS\G
新版本(
8.0.22+
)可使用:
START REPLICA;
SHOW REPLICA STATUS\G
需要
IO
和
SQL
線程顯示
Yes
才算成功:
4.3 測試
主庫選擇插入一條資料:
INSERT INTO user VALUES(1,"name",3);
然後從庫就能
select
到了:
5 搭建 Spring Boot
環境
Spring Boot
5.1 建立項目并引入依賴
建立
Spring Boot
項目,并引入如下依賴:
implementation 'com.alibaba:druid:1.2.10'
implementation 'com.baomidou:mybatis-plus-boot-starter:3.5.1'
implementation 'org.freemarker:freemarker:2.3.31'
implementation 'com.baomidou:mybatis-plus-generator:3.5.2'
implementation 'org.apache.shardingsphere:shardingsphere-jdbc-core-spring-boot-starter:5.1.1'
Maven
版本:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.10</version>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>5.1.1</version>
</dependency>
5.2 使用生成器
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
public class Generator {
public static void main(String[] args){
FastAutoGenerator.create("jdbc:mysql://localhost:3306/test", "root", "123456")
.globalConfig(builder ->
builder.author("author").outputDir(System.getProperty("user.dir") + "/src/main/java").build())
.packageConfig(builder ->
builder.parent("com.example.demo").moduleName("user").build())
.strategyConfig(builder ->
builder.addInclude("user").entityBuilder().enableLombok().disableSerialVersionUID().build())
.templateEngine(new FreemarkerTemplateEngine())
.execute();
}
}
直接運作
main
方法即可生成代碼,配置請根據個人需要進行更改。
5.3 配置檔案
spring:
shardingsphere:
mode:
type: Memory # 記憶體模式,中繼資料儲存在目前程序中
datasource:
names: master,slave # 資料源名稱,這裡有兩個
master: # 跟上面的資料源對應
type: com.alibaba.druid.pool.DruidDataSource # 連接配接池
url: jdbc:mysql://127.0.0.1:3306/test # 連接配接url
username: root
password: 123456
slave: # 跟上面的資料源對應
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://127.0.0.1:3306/test
username: root
password: 123456
rules:
readwrite-splitting: # 讀寫分離規則
data-sources: # 資料源配置
random: # 這個名字随便起
type: Static # 靜态類型
load-balancer-name: round_robin # 負載均衡算法名字
props:
write-data-source-name: master # 寫資料源
read-data-source-names: slave # 讀資料源
load-balancers: # 負載均衡配置
round_robin: # 跟上面負載均衡算法的名字對應
type: ROUND_ROBIN # 負載均衡算法
props:
sql-show: true # 列印SQL
因為配置檔案的内容比較多,以下進行分開說明。
5.3.1 模式
spring.shardingsphere.mode.type
,模式有三種:
-
:記憶體模式,初始化配置或執行Memory
等操作均在目前程序生效SQL
-
:單機模式,可以将資料源和規則等中繼資料資訊持久化,但是這些中繼資料不會在叢集中同步Standalone
-
:叢集模式,提供了多個Cluster
執行個體之間中繼資料共享以及分布式場景下的狀态協調的能力,也提供水準擴充以及高可用的能力Apache ShardingSphere
這裡使用記憶體模式,如果想将中繼資料等資訊持久化,請使用單機模式,單機模式需要配置以下屬性:
-
:設定單機模式spring.shardingsphere.mode.type=Standalone
-
:持久化倉庫的類型,單機模式适用類型為spring.shardingsphere.mode.repository.type=
File
-
:中繼資料存儲路徑,預設spring.shardingsphere.mode.repository.props.path=
.shardingsphere
-
:是否覆寫spring.shardingsphere.mode.overwrite=
而采用叢集模式,需要配置以下屬性:
-
:設定叢集模式spring.shardingsphere.mode.type=Cluster
-
:持久化倉庫類型,叢集模式支援spring.shardingsphere.mode.repository.type=
以及ZooKeeper
持久化Etcd
-
:注冊中心命名空間spring.shardingsphere.mode.repository.props.namespace=
-
:注冊中心伺服器清單spring.shardingsphere.mode.repository.props.server-lists=
-
:是否覆寫spring.shardingsphere.mode.overwrite=
-
:注冊中心的屬性配置,對于spring.shardingsphere.mode.repository.props.<key>=
,可以配置ZooKeeper
(重試間隔毫秒)、retryIntervalMilliseconds
(用戶端連接配接最大重試數)、maxRetries
(臨時資料存活秒數)、timeToLiveSeconds
(用戶端操作逾時毫秒數)、operationTimeoutMilliseconds
(登入密碼),對于digest
,可以配置Etcd
(臨時資料存活秒數)、timeToLiveSeconds
(連接配接逾時秒數)connectionTimeout
5.3.2 資料源配置
spring.shardingsphere.datasource.names
,後面接資料源的名稱,使用
,
分隔,比如此處有兩個資料源:
-
master
-
slave
然後每個資料源可以配置:
-
:資料庫連接配接池類型,這裡使用的是type
Druid
-
:使用者名username
-
:密碼password
-
:連接配接jdbc-url
,注意,對于此處使用的url
連接配接池,需要使用Druid
而不是url
jdbc-url
5.3.3 讀寫分離規則配置
spring.shardingsphere.rules.readwrite-splitting
,需要配置其中的資料源以及負載均衡類型:
-
spring.shardingsphere.rules.readwrite-splitting.data-sources
-
spring.shardingsphere.rules.readwrite-splitting.load-balancers
5.3.3.1 資料源配置
資料源配置首先需要添加一個資料源的名字,随便起一個,比如這裡是
random
,然後需要配置三個屬性:
-
:讀寫分離的類型,可選值為spring.shardingsphere.rules.readwrite-splitting.data-sources.random.type
與Static
,這裡選擇Dynamic
,如果選擇Static
,也就是動态資料源,請配合Dynamic
使用dynamic-datasource-spring-boot-starter
-
:寫資料源spring.shardingsphere.rules.readwrite-splitting.data-sources.random.props.write-data-source-name
-
:讀資料源spring.shardingsphere.rules.readwrite-splitting.data-sources.random.props.read-data-source-name
-
:負載均衡算法的名稱,這裡寫的是spring.shardingsphere.rules.readwrite-splitting.data-sources.random.load-balancer-name
round_robin
5.3.3.2 負載均衡配置
負載均衡配置需要與上面的
spring.shardingsphere.rules.readwrite-splitting.data-sources.random.load-balancer-name
屬性對應,比如這裡是
round_robin
,那麼需要配置的就是
spring.shardingsphere.rules.readwrite-splitting.load-balancers.round_robin
。然後下一步就是配置具體的負載均衡算法。
内置的負載均衡算法有三個:
- 輪詢算法:
,配置ROUND_ROBIN
即可,也就是type=ROUND_ROBIN
spring.shardingsphere.rules.readwrite-splitting.load-balancers.round_robin.type=ROUND_ROBIN
- 随機通路算法:
,配置RANDOM
type=RANDOM
- 權重通路算法:
,配置WEIGHT
,同時需要配置type=WEIGHT
,在其中配置各個讀節點的權重props
5.3.4 屬性配置
屬性的話這裡隻配置了一個
spring.shardingsphere.props.sql-show=true
,也就是列印
SQL
,其他支援的屬性有:
-
:是否列印簡單風格的spring.shardingsphere.props.sql-simple
,預設為SQL
false
-
:設定任務處理線程池大小,預設為spring.shardingsphere.props.kernel-exector-size
infinite
-
:每次查詢所能使用的最多資料庫連接配接數,預設為spring.shardingsphere.props.max-connections-size-per-query
1
-
:啟動時是否檢查分片中繼資料的一緻性,預設為spring.shardingsphere.props.check-table-metadata-enabled
false
-
:啟動時是否檢查重複表,預設為spring.shardingsphere.props.check-duplicate-table-enabled
false
-
:是否開啟聯邦查詢,預設為spring.shardingsphere.props.sql-federation-enabled
false
5.4 準備 Controller
Controller
@RestController
@RequestMapping("/user")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class UserController {
private final UserServiceImpl userService;
@GetMapping("/select")
public User select(){
return userService.getById(1);
}
@GetMapping("/insert")
public boolean insert(){
return userService.saveOrUpdate(User.builder().id(3L).name("name3").build());
}
}
6 測試
通路
http://localhost:8080/user/insert
,可以看到寫操作在主庫進行: