天天看點

SpringBoot+ShardingSphereJDBC實作讀寫分離!

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​

​主從複制的原理,如下圖所示:

SpringBoot+ShardingSphereJDBC實作讀寫分離!

工作流程如下:

  • 主庫修改資料後,将修改日志寫入

    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 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;      
SpringBoot+ShardingSphereJDBC實作讀寫分離!

需要把​

​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​

​位址,用于給主庫設定同步的使用者:

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 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​

​才算成功:

SpringBoot+ShardingSphereJDBC實作讀寫分離!

4.3 測試

主庫選擇插入一條資料:

INSERT INTO user VALUES(1,"name",3);      

然後從庫就能​

​select​

​到了:

SpringBoot+ShardingSphereJDBC實作讀寫分離!

5 搭建​

​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​

@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​

​,可以看到寫操作在主庫進行:

SpringBoot+ShardingSphereJDBC實作讀寫分離!

繼續閱讀