天天看點

Springboot + Mysql8 讀寫分離實戰

在實際的生産環境中,為了確定資料庫的穩定性,我們一般會給資料庫配置雙機熱備機制,這樣在master資料庫崩潰後,slave資料庫可以立即切換成主資料庫,通過主從複制的方式将資料從主庫同步至從庫,在業務代碼中編寫代碼實作讀寫分離(讓主資料庫處理 事務性增、改、删操作,而從資料庫處理查詢操作)來提升資料庫的并發負載能力。

Springboot + Mysql8 讀寫分離實戰
下面我們使用最新版本的Mysql資料庫(8.0.16)結合SpringBoot實作這一完整步驟(一主一從)。

安裝配置mysql

  • 準備兩台虛拟機用作安裝mysql,并将下載下傳後的檔案mysql-8.0.16-linux-glibc2.12-x86_64.tar.xz上傳至伺服器/app/mysql
    • 192.168.249.131 CENTOS7 主
    • 192.168.249.129 CENTOS7 從
  • 檢視防火牆狀态,如果啟動需要先關閉防火牆
service firewalld status  ## 檢視防火牆狀态
service firewalld stop   ## 關閉防火牆      
  • 使用如下指令将xz檔案解壓成tar檔案

    xz -d mysql-8.0.16-linux-glibc2.12-x86_64.tar.xz

  • 解壓安裝包

    tar -xvf mysql-8.0.16-linux-gl-ibc2.12-x86_64.tar

  • 在/app/mysql下建立data檔案夾,用于存放資料
  • 建立mysql使用者組和mysql使用者
groupadd mysql                                  ## 建立使用者組
useradd -g mysql -d /app/mysql mysql    ## 在使用者組下建立mysql使用者并授權相關目錄
groupdel mysql                                  ## 删除使用者組名(若報已存在相關使用者組)
userdel mysql                                   ## 删除使用者(若報已存在相關使用者)      
  • 初始化安裝mysql資料庫

    ./mysql-8.0.16-linux-glibc2.12-x86_64/bin/mysqld --user=mysql --basedir=/app/mysql --datadir=/app/mysql/data --initialize

2019-07-01T02:05:52.681626Z 0 [Warning] [MY-011070] [Server] 'Disabling symbolic links using --skip-symbolic-links (or equivalent) is the default. Consider not using this option as it' is deprecated and will be removed in a future release.
2019-07-01T02:05:52.681694Z 0 [System] [MY-013169] [Server] /app/mysql/mysql-8.0.16-linux-glibc2.12-x86_64/bin/mysqld (mysqld 8.0.16) initializing of server in progress as process 1479
2019-07-01T02:05:52.681726Z 0 [ERROR] [MY-010338] [Server] Can't find error-message file '/app/mysql/share/errmsg.sys'. Check error-message file location and 'lc-messages-dir' configuration directive.
2019-07-01T02:05:55.713747Z 5 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: xa6(H>rK/r<E
2019-07-01T02:05:57.303240Z 0 [System] [MY-013170] [Server] /app/mysql/mysql-8.0.16-linux-glibc2.12-x86_64/bin/mysqld (mysqld 8.0.16) initializing of server has completed      

注意,此時mysql會生成一個預設的臨時密碼,如上圖所示,需要先儲存下來然後修改

  • 建立mysql服務并增加執行權限

    cp mysql-8.0.16-linux-glibc2.12-x86_64/support-files/mysql.server /etc/init.d/mysqld

  • 修改mysql配置檔案 vi /etc/my.cnf 增加如下配置
[mysqld]
port=3306
basedir=/app/mysql/mysql-8.0.16-linux-glibc2.12-x86_64
datadir=/app/mysql/data
socket=/tmp/mysql.sock
symbolic-links=0

[mysqld_safe]
log-error=/app/mysql/data/log/error.log
pid-file=/app/mysql/data/mysql.pid
user=mysql
tmpdir=/tmp
character_set_server=utf8
default-storage-engine=INNODB
init_connect='SET NAMES utf8'

!includedir /etc/my.cnf.d
      

如果報日志權限相關錯誤,請先建立對應日志檔案,并給mysql使用者授權

chown -R mysql:mysql /app/mysql/data/log/error.log

  • 啟動mysql服務

    service mysqld start

  • 建立mysql用戶端軟連接配接

    ln -s /app/mysql/mysql-8.0.16-linux-glibc2.12-x86_64/bin/mysql /usr/local/bin/mysql

  • 登入mysql修改密碼
mysql -uroot -p密碼       ## 登入 
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '000000';      
  • 設定遠端登入
use mysql;
update user set host='%' where user='root' limit 1;
flush privileges;      

配置mysql主從同步(binlog)

複制原理

  • Master将資料改變記錄到二進制日志(binary log)中,也就是配置檔案log-bin指定的檔案,這些記錄叫做二進制日志事件(binary log events)
  • Slave通過I/O線程讀取Master中的binary log events并寫入到它的中繼日志(relay log)
  • Slave重做中繼日志中的事件,把中繼日志中的事件資訊一條一條的在本地執行一次,完成資料在本地的存儲,進而實作将改變反映到它自己的資料(資料重放)

複制要求

  • 主從伺服器作業系統版本和位數一緻
  • Master和Slave資料庫的版本要一緻
  • Master和Slave資料庫中的資料要一緻
  • Master開啟二進制日志,Master和Slave的server_id在區域網路内必須唯一

配置步驟

主資料庫(192.168.249.131)

  • 建立同步使用者并授權
CREATE USER 'slave'@'192.168.249.129' IDENTIFIED WITH 'mysql_native_password' BY '000000';
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'slave'@'192.168.249.129';
FLUSH PRIVILEGES;      

注意這裡建立使用者時需要選用

mysql_native_password

加密方式插件,否則預設會使用

caching_sha2_password

加密方式,這樣在同步的時候需要使用SSL的身份進行驗證,為了友善簡單,我們直接采用

mysql_native_password

方式

  • 修改配置/etc/my.cnf,新增如下配置,開啟binlog,并重新開機mysql服務
[mysqld]
# 開啟二進制日志功能
log-bin=mysql-bin
# 設定server_id,,注意在網段内要唯一
server-id=131
#(可選配置)要同步的資料庫名,要同步多個資料庫,就多加幾個replicate-db-db=資料庫名
binlog-do-db=mydb
#(可選配置)要忽略的資料庫
binlog-ignore-db=mysql
      
  • 檢視主伺服器狀态

    show master status

Springboot + Mysql8 讀寫分離實戰

注意看裡面的參數,特别前面兩個File和Position,在從伺服器(Slave)配置主從關系會有用到的。

從資料庫(192.168.249.129)

  • 修改/etc/my.cnf,新增如下配置,并重新開機服務
[mysqld]
server-id=129
log-bin=mysql-bin
replicate-do-db=mydb
replicate-ignore-db=mysql      
  • 在slave中設定master資訊,指定同步位置
stop slave;
change master to master_host='192.168.249.131',master_user='slave',master_password='000000',master_log_file='mysql-bin.000001',master_log_pos=155;
start slave;      

參數說明:

master_host=‘192.168.249.131’ ## Master的IP位址

master_user=‘slave’ ## 用于同步資料的使用者(在Master中授權的使用者)

master_password=‘000000’ ## 同步資料使用者的密碼

master_port=3306 ## Master資料庫服務的端口

masterlogfile=‘mysql-bin.000001’ ##指定Slave從哪個日志檔案開始讀複制資料(Master上執行指令的結果的File字段)

masterlogpos=155 ## 從哪個POSITION号開始讀(Master上執行指令的結果的Position字段)

masterconnectretry=30 ##當重建立立主從連接配接時,如果連接配接建立失敗,間隔多久後重試。機關為秒,預設設定為60秒,同步延遲調優參數。

  • 檢視從伺服器狀态

    show slave status\G;

Springboot + Mysql8 讀寫分離實戰

至此資料庫層面主從配置完成。

SpringBoot中配置主從讀寫分離

在主從模式下請遵守如下規則:

主資料庫 隻執行

INSERT

,

UPDATE

DELETE

操作

從資料庫 隻執行

SELECT

我們這裡使用開源項目[dynamic-datasource-spring-boot-starter](

https://gitee.com/baomidou/dynamic-datasource-spring-boot-starter/wikis/

)作為讀寫分離的工具包

使用方法

  1. 在mydb主資料庫中建立一個簡單資料表user,建好後從資料庫會自動同步
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `account` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `position` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;      
  1. 引入相關依賴
<dependencies>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter</artifactId>
       </dependency>

       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-web</artifactId>
       </dependency>

       <dependency>
           <groupId>org.mybatis.spring.boot</groupId>
           <artifactId>mybatis-spring-boot-starter</artifactId>
           <version>2.0.1</version>
       </dependency>

       <dependency>
           <groupId>com.baomidou</groupId>
           <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
           <version>2.5.5</version>
       </dependency>

       <dependency>
           <groupId>mysql</groupId>
           <artifactId>mysql-connector-java</artifactId>
           <version>8.0.15</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>
       </dependency>

   </dependencies>      
  1. 配置資料源
spring:
  datasource:
    dynamic:
      primary: master #設定預設的資料源或者資料源組,預設值即為master
      strict: false #設定嚴格模式,預設false不啟動. 啟動後再為比對到指定資料源時候回抛出異常,不啟動會使用預設資料源.
      datasource:
        master:
          type: com.zaxxer.hikari.HikariDataSource
          url: jdbc:mysql://192.168.249.131:3306/mydb?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false
          username: root
          password: '000000'
          driver-class-name: com.mysql.cj.jdbc.Driver
        slave_1:
          type: com.zaxxer.hikari.HikariDataSource
          url: jdbc:mysql://192.168.249.129:3306/mydb?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false
          username: root
          password: '000000'
          driver-class-name: com.mysql.cj.jdbc.Driver      
  1. 在啟動類入口加入mybatis掃描包
@SpringBootApplication@MapperScan("com.jianzh5.dynamic.mapper")
public class DynamicDatsourceBootstrap {    
    public static void main(String[] args) {        
        SpringApplication.run(DynamicDatsourceBootstrap.class, args);
    }
}      
  1. 建立實體類User
@Data
public class User {
    private int id;
    private String account;
    private String name;
    private String position;
}      

6.建立mapper接口檔案,新增兩個方法

addUser(User user)

getById(int id)

public interface UserDao {
    @Insert("INSERT INTO user(account, name, position) VALUES(#{account}, #{name}, #{position})")
    @Options(useGeneratedKeys = true,keyProperty = "id")
    int addUser(User user);

    @Select("SELECT * FROM user WHERE id = #{id}")
    User getById(int id);
}      
  1. 建立Service相關實作
public interface UserService {
    int addUser(User user);

    User getById(int id);
}

@Service
public class UserServiceImpl implements UserService {
    @Resource
    private UserDao userDao;

    @Override
    public int addUser(User user) {
        return userDao.addUser(user);
    }


    @DS("slave")
    @Override
    public User getById(int id) {
        return userDao.getById(id);
    }
}      

由于在資料源中配置了

primary: master

,預設操作都會從主庫執行,使用注解

@DS

切換資料源,此注解也可直接用于類檔案上,同時存在方法注解優先于類上注解。

  1. 編寫單元測試進行測試
public class UserServiceTest extends DynamicDatsourceBootstrapTests {
    @Autowired
    private UserService userService;

    @Test
    public void testAddUser(){
        User user = new User();
        user.setName("李四");
        user.setAccount("sili");
        user.setPosition("JAVA開發工程師");
        int i = userService.addUser(user);
        System.out.println(user);
    }


    @Test
    public void testGetById(){
       int id = 4;
       User user = userService.getById(id);
        Assert.assertEquals("sanzhang",user.getAccount());
    }

}      
  1. 通過觀察執行日志,發現讀寫資料庫會根據@DS注解進行切換,至此Springboot內建資料庫主從讀寫分離完成。