使用docker搭建mysql 的一主雙從模式,並使用springboot+mybatis實現動態數據源切換,從而實現簡單的讀寫分離(解決mybatis集成多數據源會遇到的bug)

1. docker

docker的安裝有很多方式,可以參考:

https://www.runoob.com/docker/centos-docker-install.html

還需要配置鏡像加速

2. docker下安裝mysql

打開docker hub,然後搜索mysql ,我們安裝5.7 版本的mysql

docker pull mysql:5.7

3.做一些啓動前的準備

我這裏就直接在一臺機器上啓動3個mysql容器了,就不分3臺機器了。

主要是將容器中的mysql 存放數據的文件夾映射到主機上,否則,容器一刪除,數據就全沒了。

在主機上添加爲3個數據庫分別添加3個文件夾:

mkdir /usr/local/docker-mysql-data
mkdir /usr/local/docker-mysql-data/master
mkdir /usr/local/docker-mysql-data/slaver1
mkdir /usr/local/docker-mysql-data/slaver2

4. 啓動mysql容器

啓動master:

docker run -p 3339:3306 --name master -v /usr/local/docker-mysql-data/master:/var/lib/mysql  -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7

啓動兩個slaver:

docker run -p 3340:3306 --name slaver1 -v /usr/local/docker-mysql-data/slaver1:/var/lib/mysql  -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7

docker run -p 3341:3306 --name slaver2 -v /usr/local/docker-mysql-data/slaver2:/var/lib/mysql  -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7

參數說明:

都拿master進行舉例說明

  • -p 映射端口:將容器中的3306端口映射到主機的3339端口,供外部訪問
  • --name : 給容器取的名字,可以自定義,越容易記越好,之後對容器進行操作時可直接使用名字,而不用使用容器id了
  • -v: 將容器中的文件夾掛載到主機的文件夾上,這樣的話,當容器被刪除,數據也能夠保存下來
  • -e: 外部參數,MYSQL_ROOT_PASSWORD=123456的意思是給root用戶設置密碼爲123456,且通過命令啓動容器後會自動爲root用戶賦予所有的權限,不需要手動賦予權限
  • -d :表示讓容器後臺運行
  • mysql:5.7  指定運行的mysql的版本,我們只下載了5.7版本的鏡像,所以只能指定5.7版本

5. 使用nacicat進行連接3臺數據庫

在連接前需要開放端口:

  • 如果使用的雲服務器,那麼去開放安全組的端口
  • 開放linux端口,iptables -A INPUT -ptcp --dport 端口號 -j ACCEPT

然後開始連接,拿master舉例:

端口號使用映射的那個主機端口號即可,賬號爲root,密碼是剛剛設置的12456

6. 配置Master(主)

通過命令進入到Master容器內部:

docker exec -it master /bin/bash

進入容器後,切換到/etc/mysql目錄下

cd /etc/mysql

然後使用命令對my.cnf進行編輯

vi my.cnf

此時會報出bash: vi: command not found,需要我們在docker容器內部自行安裝vim。一次執行以下命令即可安裝成功

apt-get update
apt-get install vim

然後再次使用vim編輯my.cnf,在my.cnf中添加如下配置:

[mysqld]
## 同一局域網內注意要唯一,所以設置爲主機的mysql 端口地址就可以了
server-id=3339
## 開啓二進制日誌功能,可以隨便取(關鍵)
log-bin=mysql-bin

配置完成之後,需要重啓master容器

#先退出容器內部
exit
#重啓master容器
docker restart master

在Master數據庫創建數據同步用戶,授予用戶 slave REPLICATION SLAVE權限和REPLICATION CLIENT權限,用於在主從庫之間同步數據。做法:使用navicat連接master數據庫,然後新建一個查詢,輸入語句:

CREATE USER 'slave'@'%' IDENTIFIED BY '123456';

GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'slave'@'%';

至此,master數據庫配置完成

7. 配置slaver(從)

和配置Master(主)一樣,在Slave配置文件my.cnf中添加如下配置:

[mysqld]
## 設置server_id,注意要唯一
server-id=3340 
## 開啓二進制日誌功能,以備Slave作爲其它Slave的Master時使用
log-bin=mysql-slave-bin   
## relay_log配置中繼日誌
relay_log=edu-mysql-relay-bin  

配置完成後也需要重啓slaver1,slaver2容器。

8. 鏈接Master(主)和Slave(從)

8.1 查詢master的一些狀態

使用navicat連接master數據庫,並輸入查詢語句:

show master status;

結果:

 File和Position字段的值後面將會用到,在後面的操作完成之前,需要保證Master庫不能做任何操作,否則將會引起狀態變化,File和Position字段的值變化。

8.2 操作從數據庫

使用navicat連接兩個slaver數據庫,並執行以下sql語句:

change master to master_host='172.17.0.2', master_user='slave', master_password='123456', master_port=3306, master_log_file='mysql-bin.000001', master_log_pos= 2830, master_connect_retry=30;

命令說明:

master_host :Master的地址,指的是容器的獨立ip,可以通過docker inspect --format='{{.NetworkSettings.IPAddress}}' 容器名稱|容器id查詢容器的ip

master_port:Master的端口號,指的是容器的端口號

master_user:用於數據同步的用戶,即我們上面在master數據庫中創建的slaver用戶,該用戶可以同步主從數據庫數據

master_password:用於同步的用戶的密碼

master_log_file:指定 Slave 從哪個日誌文件開始複製數據,即上文中提到的 File 字段的值

master_log_pos:從哪個 Position 開始讀,即上文中提到的 Position 字段的值

master_connect_retry:如果連接失敗,重試的時間間隔,單位是秒,默認是60秒

8.3 查看slaver狀態

在slaver終端上輸入sql語句

show slave status

 結果:slave-io-running 和slave-sql-running 都爲NO

 正常情況下,SlaveIORunning 和 SlaveSQLRunning 都是No,因爲我們還沒有開啓主從複製過程。

8.4 開啓主從複製

在slaver數據庫上運行

start slave

然後再次查詢主從同步狀態

會發現SlaveIORunning 和 SlaveSQLRunning 變爲了YES,即表示主從複製鏈接成功

 

9. 測試主從複製

只需要在master上添加一個數據庫,然後查看兩個從數據庫是否也存在,如果存在,那麼說明覆製成功

10. 其他 

10.1 停止主從複製

如果你不想從數據庫去監聽主數據庫的變化了,那麼就需要停止主從複製

在slaver數據庫上執行sql

stop slave;

如果你想重置slaver數據庫讀取主數據變化數據的位置(slaver都是通過讀取master的二進制文件來進行數據庫的數據變更)到主數據庫最原始的狀態,那麼使用以下sql命令:

reset slave;

但是使用了這個指令後,一般都會導致 slaver重新連接master進行主從複製 失敗,只能重新關聯以下master。

先停止以下slaver的主從複製並且重置

stop slave;
reset slave;

再去master數據庫中查看下狀態,即file和position:

show master status;

 然後再次再slaver中進行關聯:

change master to master_host='172.17.0.2', master_user='slave', master_password='123456', master_port=3306, master_log_file='mysql-bin.000002', master_log_pos= 344, master_connect_retry=30;

最後重新啓動slaver的主從複製

start slave

查看slaver狀態

show slave status 

發現SlaveIORunning 和 SlaveSQLRunning 又變爲了YES,說明成功了。

但是 你在停止主從複製這個時間段的數據已經同步不過來了。

如果你還是想同步過來,那你只能去master中的二進制文件中查看上次停止執行的指針位置,然後重新關聯主從時指定一下

11 spring boot+mybatis實現動態數據源 

現在我們已經有一主雙從的數據庫了,那麼接下來就需要實現讀寫分離了。怎麼實現呢?我們先用簡單的動態數據源切換的方式來做

源碼位置:https://gitee.com/tfp-study/dynamic-data-source

我只做簡單的一些說明:

結構大致是這樣的,說明一下config包中的類:

  • MapperConfig:這個類中需要定義3個數據源(一主雙從),sqlSession等相關bean
  • DynamicDataSource: 這個類主要是繼承AbstractRoutingDataSource類,重寫determineCurrentLookupKey方法,通過這個方法來指定某次的數據庫操作需要使用哪個數據源(調用ThreadLocalDataSource中的方法獲取)
  • ThreadLocalDataSource: 通過ThreadLocal來存儲某次數據庫操作需要使用的數據源

當我們需要切換數據源時:

通過向ThreadLocal中設置值來決定使用哪個數據源。

集成時遇到的坑:

問題:

一開始我在application.yml中的配置:

mybatis:
  mapper-locations: mybatis/mappers/UserMapper.xml
  type-aliases-package: cn.tanfp.dynamicdatasource.pojo
  configuration:
    map-underscore-to-camel-case: true
    call-setters-on-nulls: true

 並且我的mapper.xml是放在resource文件夾中的。

然後我每次訪問接口都會報錯:

 Invalid bound statement (not found): cn.tanfp.dynamicdatasource.mapper.UserMapper.xxxxxx。

分析:

我一開始以爲可能是xml或掃描包什麼的路徑有問題,結果核對了一遍,發現並沒有錯,並且target中class文件中,也存在xml文件。然後我就很疑惑,通過查看源碼,終於發現了原因:

首先看下源碼:

通過源碼就能發現,由於我們在MapperConfig類中,創建了3個DataSource Bean,導致Mybatis的自動配置類沒有加載成功,從而使得application.yml中有關mybatis的配置屬性全部失效了。

解決辦法:

在我們自己寫的MapperConfig中,通過設置sqlSessionFactoryBean對象的屬性來指定

@Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean() throws IOException {
        SqlSessionFactoryBean sqlSessionFactoryBean=new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dynamicDataSource());
        // 設置mapper.xml掃描
        // (一定不能在application.yml中配置,因爲我們現在是用的自己的mybatis配置,application.yml中的配置是失效狀態,要配置屬性,只能在這個配置類中配置)
        sqlSessionFactoryBean.setMapperLocations(
                new PathMatchingResourcePatternResolver().
                        getResources("classpath:mybatis/mappers/*.xml"));
        // 設置返回值pojo的別名包
        sqlSessionFactoryBean.setTypeAliasesPackage("cn.tanfp.dynamicdatasource.pojo");
        return sqlSessionFactoryBean;
    }

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章