1 概述
本文講述瞭如何使用MyBatisPlus
+ShardingSphere
進行讀寫分離,以及利用MySQL
進行一主一從的主從複製。
具體步驟包括:
MySQL
主從複製環境準備(Docker
)- 搭建
ShardingShpere
+MyBatisPlus
+Druid
環境 - 測試
2 環境
OpenJDK 11.0.11
Spring Boot 2.5.1
MyBatis Plus 3.4.3.1
MyBatis Plus Generator 3.5.0
Druid 1.2.6
ShardingSphere 4.1.1
MySQL 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
的版本,叫Pika
tddl
:阿里巴巴自主研發的分佈式數據庫服務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
添加如下兩行:
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
那麼主庫中用於複製的用戶就可以是[email protected]
。
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:
datasource:
url: jdbc:mysql://localhost:3306/test
username: root
password: 123456
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
版: