1 概述
本文講述了如何使用MyBatisPlus+ShardingSphere進行讀寫分離,以及利用MySQL進行一主一從的主從復制。
具體步驟包括:
MySQL主從復制環境准備(Docker)- 搭建
ShardingShpere+MyBatisPlus+Druid環境 - 測試
2 環境
OpenJDK 11.0.11Spring Boot 2.5.1MyBatis Plus 3.4.3.1MyBatis Plus Generator 3.5.0Druid 1.2.6ShardingSphere 4.1.1MySQL 8.0.25
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的版本,叫Pikatddl:阿里巴巴自主研發的分布式數據庫服務Sharding-JDBC:ShardingShpere的一個子產品,一個輕量級Java框架
4 MySQL主從復制環境准備
看完了一些基礎理論就可以進行動手了,本小節先准備好MySQL主從復制的環境,基於Docker+MySQL官方文檔搭建。
4.1 主庫操作
4.1.1 拉取鏡像並創建容器運行
docker pull mysql
docker run -itd -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 --name mysql-master mysql
docker exec -it mysql-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 int primary key auto_increment,
name varchar(30) not null,
age int 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 -p 33061:33060 -e MYSQL_ROOT_PASSWORD=123456 --name mysql-slave mysql
docker exec -it mysql-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地址,用於給主庫設置同步的用戶:
ifconfig
輸出:
inet 172.17.0.3 netmask 255.255.0.0 broadcast 172.17.255.255
那么主庫中用於復制的用戶就可以是repl@172.17.0.3。
4.2.4 導入數據(可選)
如果主庫有數據可以先導入到從庫:
mysqldump -u root -p --all-databases < dbdump.db
4.2.5 准備數據源
create database test;
use test;
create table user(
id int primary key auto_increment,
name varchar(30) not null,
age int 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環境
5.1 新建項目並引入依賴
新建Spring Boot項目,並引入如下依賴:
implementation 'com.baomidou:mybatis-plus-boot-starter:3.4.3.1'
implementation 'com.baomidou:mybatis-plus-generator:3.5.0'
implementation 'org.apache.velocity:velocity-engine-core:2.3'
implementation 'org.realityforge.org.jetbrains.annotations:org.jetbrains.annotations:1.7.0'
implementation 'com.alibaba:druid:1.2.6' # 注意不能使用druid的starter依賴,會出現模板找不到的問題
implementation 'org.apache.shardingsphere:sharding-jdbc-spring-boot-starter:4.1.1'
Maven版本:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3.1</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
<dependency>
<groupId>org.realityforge.org.jetbrains.annotations</groupId>
<artifactId>org.jetbrains.annotations</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.6</version>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.1.1</version>
</dependency>
5.2 使用生成器
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.*;
public class MyBatisPlusGenerator {
public static void main(String[] args) {
DataSourceConfig dataSourceConfig = new DataSourceConfig.Builder("jdbc:mysql://localhost:3306/test","root","123456").build();
String projectPath = System.getProperty("user.dir");
GlobalConfig globalConfig = new GlobalConfig.Builder().outputDir(projectPath+"/src/main/java").openDir(false).build();
PackageConfig packageConfig = new PackageConfig.Builder().moduleName("test").parent("com.example.demo").build();
AutoGenerator autoGenerator = new AutoGenerator(dataSourceConfig);
autoGenerator.global(globalConfig).packageInfo(packageConfig);
autoGenerator.execute();
}
}
直接運行main方法即可生成代碼,配置請根據個人需要進行更改,更詳細的配置可以參考筆者的另一篇文章。
5.3 配置文件
spring:
shardingsphere:
datasource:
names: master,slave # 數據源名字
master:
type: com.alibaba.druid.pool.DruidDataSource # 連接池
url: jdbc:mysql://127.0.0.1:3306/test # 主庫地址
username: root # 主庫用戶名
password: 123456 # 主庫密碼
slave:
type: com.alibaba.druid.pool.DruidDataSource # 連接池
url: jdbc:mysql://127.0.0.1:3307/test # 從庫地址
username: root
password: 123456
masterslave:
load-balance-algorithm-type: round_robin # 負載均衡算法,
name: ms
master-data-source-name: master # 主庫數據源名字
slave-data-source-names: slave # 從庫數據源名字
props:
sql:
show: true # 打印SQL
關於負載均衡算法,目前只支持兩種:

5.4 准備Controller
@RestController
@RequestMapping("/test/user")
@AllArgsConstructor(onConstructor = @__(@Autowired))
public class UserController {
private final UserServiceImpl userService;
@GetMapping("/write")
public boolean write(){
return userService.save(User.builder().age(3).name("234").build());
}
@GetMapping("/read")
public User read(){
return userService.getById(1);
}
}
6 測試
訪問http://localhost:8080/test/user/write,可以看到寫操作在主庫進行:

訪問http://localhost:8080/test/user/read,可以看到讀操作在從庫進行:

這樣讀寫分離就算是可以了。
7 參考源碼
Java版:
Kotlin版:
8 參考
如果覺得文章好看,歡迎點贊。
同時歡迎關注微信公眾號:氷泠之路。

