天天看點

MySQL + Keepalived 雙主熱備搭建

什麼是雙主複制

在傳統的主從複制架構中,從庫僅僅是作為主庫資料的備份,當主庫發生故障時,資料庫将停止對外提供服務,并且主庫故障後手動進行主從切換的過程也較為繁瑣。為了解決這個問題,可以采用 MySQL 雙主模式,其中一台主庫提供服務,另一台作為熱備。結合 keepalived 使用虛拟 IP 對外提供服務,一旦主庫發生故障,備庫可以在很短的時間内接管服務。

MySQL + Keepalived 雙主熱備搭建

機器規劃

主機名 IP位址 端口号 角色
mysql-master 192.168.1.36 3308 master(主庫A)
mysql-slave 192.168.1.37 slave(主庫B)
192.168.1.38 虛拟 IP

搭建 MySQL 雙主同步

準備工作

建立相關目錄

#建立使用者
userdel -r mysql
groupadd mysql 
useradd -r -g mysql -s /bin/false mysql 
#建立目錄
# /mysql/app/                                   MySQL 資料庫軟體根目錄
# /mysql/data/3308/data/                        MySQL 資料檔案目錄
# /mysql/log/3308/binlog                        MySQL 二進制日志目錄
# /mysql/log/3308/relaylog                      MySQL 中繼日志目錄
# /mysql/backup/3308/xtrabackup/target_dir      MySQL xtrabackup 實體備份目錄
# /mysql/backup/3308/mysqldump                  MySQL mysqldump 邏輯備份目錄
# /mysql/script                                 MySQL 常用腳本存放目錄
mkdir -p /mysql/app/            
mkdir -p /mysql/data/3308/data/                                 
mkdir -p /mysql/log/3308/binlog                                 
mkdir -p /mysql/log/3308/relaylog                               
mkdir -p /mysql/backup/3308/xtrabackup/target_dir               
mkdir -p /mysql/backup/3308/mysqldump    
mkdir -p /mysql/script                                                                      
#給目錄授權
chown -R mysql:mysql /mysql      

下載下傳并解壓 MySQL 安裝包

MySQL 壓縮包下載下傳位址:

https://dev.mysql.com/downloads/mysql/5.7.html
#解壓壓縮包
tar zxvf mysql-5.7.29-linux-glibc2.12-x86_64.tar.gz -C /mysql/app
mv /mysql/app/mysql-5.7.29-linux-glibc2.12-x86_64 /mysql/app/mysql
chown -R mysql:mysql /mysql      

配置環境變量

##将MySQL目錄添加環境變量##
cat >> ~/.bash_profile <<-EOF
export PATH=$PATH:/mysql/app/mysql/bin
EOF
source ~/.bash_profile      

初始化主庫 A

主庫 A 重要配置如下:

  • 開啟 binlog:

    log_bin=binlog 目錄

  • 設定 server_id:

    server_id = 1

    ,主庫 A 的 server_id 和主庫 B 要不一樣。
  • 針對 GTIP 的方式同步有兩個參數必須設定:
    • gtid_mode=on

    • enforce_gtid_consistency=on

  • 防止主鍵沖突:
    • 設定自增主鍵步長,通常有幾個主庫 就寫幾,避免主鍵沖突:

      auto_increment_increment=2

    • 設定自增主鍵起始值,第一個主庫為 1,第二個主庫為 2,以此類推:

      auto_increment_offset=1

關于防止主鍵沖突的兩個參數的詳解可以參考這篇部落格(https://www.cnblogs.com/kerrycode/p/11150782.html)。

#主機名和端口号作為目錄名的一部分
HostName=`hostname`
MySql_Port=3308
#IP位址
Ip=192.168.1.36
#master server_id 要和 slave 不一樣
Server_Id=1
cat > /mysql/data/$MySql_Port/my.cnf <<-EOF
#------------------------------------ 
#用戶端設定
#------------------------------------
[client]
port=$MySql_Port
socket =/mysql/data/$MySql_Port/mysql.sock
default-character-set=utf8 
#------------------------------------ 
#mysql連接配接工具設定
#------------------------------------
[mysql]
prompt="\\u@\\h \\d \\r:\\m:\\s>" #登入時顯示登入的使用者名、伺服器位址、預設資料庫名、目前時間
auto-rehash #讀取表資訊和列資訊,可以在連上終端後開啟tab補齊功能。
default-character-set=utf8 #預設字元集
#------------------------------------ 
#基本設定
#------------------------------------
[mysqld]
bind_address=0.0.0.0  #監聽本地所有位址
port=$MySql_Port  #端口号
user=mysql  #使用者
basedir=/mysql/app/mysql  #安裝路徑
datadir=/mysql/data/$MySql_Port/data  #MySQL資料目錄
socket=/mysql/data/$MySql_Port/mysql.sock #用于本地連接配接的socket檔案目錄
pid-file=/mysql/data/$MySql_Port/mysql.pid #程序ID檔案的目錄。
character-set-server=utf8 #預設字元集
#------------------------------------ 
#log setting 日志設定
#------------------------------------
long_query_time=10 #慢查詢時間,超過 10 秒則認為是慢查詢
slow_query_log=ON #啟用慢查詢日志
slow_query_log_file=/mysql/log/$MySql_Port/${HostName}-query.log #慢查詢日志目錄
log_queries_not_using_indexes=1 #記錄未使用索引的語句
log_slow_admin_statements=1 #慢查詢也記錄那些慢的optimize table,analyze table和alter table語句
log-error=/mysql/log/$MySql_Port/${HostName}-error.log #錯誤日志目錄
#------------------------------------ 
#master modify parameter 主庫A複制更改參數
#------------------------------------
server_id=$Server_Id #master和slave server_id 需要不同
#------------------------------------ 
#slave parameter 主庫B參數
#------------------------------------
relay_log=/mysql/log/$MySql_Port/relaylog/${HostName}-relaylog #中繼日志目錄
relay-log-index=/mysql/log/$MySql_Port/relaylog/${HostName}-relay.index  #中繼日志索引目錄
log_slave_updates=1 #主庫B從主庫A複制的資料會寫入主庫B binlog 日志檔案裡,預設是不寫入
read_only=0  #主庫B讀寫權限
relay_log_purge=1 #自動清空不再需要中繼日志
#二進制日志參數配置
log_bin=/mysql/log/$MySql_Port/binlog/${HostName}-binlog  #binlog目錄
log_bin_index=/mysql/log/$MySql_Port/binlog/${HostName}-binlog.index  #指定索引檔案的位置
binlog_format=row #行模式複制,預設是 row
binlog_rows_query_log_events=on #在 row 模式下,開啟該參數,可以将把 sql 語句列印到 binlog 日志裡面,友善檢視
binlog_cache_size=1M #事務能夠使用的最大 binlog 緩存空間。
max_binlog_size=2048M #binlog 檔案最大空間,達到該大小時切分檔案
expire_logs_days=7 #設定自動删除 binlog 檔案的天數。
sync_binlog=1 #表示每次事務的 binlog 都會fsync持久化到磁盤,MySQL 5.7.7 之後預設為1,之前的版本預設為0
innodb_flush_log_at_trx_commit=1 #表示每次事務的 redo log 都直接持久化到磁盤,預設值為1
#------------------------------------ 
#GTID Settings GTID 同步複制設定
#------------------------------------
gtid_mode=on  #開啟GTID同步
enforce_gtid_consistency=on #強制事務一緻,確定 GTID 的安全,在事務中就不能建立和删除臨時表
binlog_gtid_simple_recovery=1 #這個變量用于在 MySQL 重新開機或啟動的時候尋找 GTIDs 過程中,控制 binlog 如何周遊的算法
#------------------------------------ 
#避免主鍵沖突設定
#------------------------------------
auto_increment_increment=2  #自增主鍵步長,通常有幾個主庫A就寫幾,避免主鍵沖突
auto_increment_offset=1  #設定自增主鍵起始值,第一個主庫A為1,第二個主庫A為2,以此類推
EOF      

初始化主庫A:

mysqld \
--defaults-file=/mysql/data/3308/my.cnf \
--initialize --user=mysql \
--basedir=/mysql/app/mysql \
--datadir=/mysql/data/3308/data      

配置 MySQL 啟動腳本:

cp /mysql/app/mysql/support-files/mysql.server /etc/init.d/mysql_3308
ln -sf /etc/init.d/mysql_3308 /usr/lib/systemd/system/mysql_3308
#修改啟動腳本##
vim /etc/init.d/mysql_3308
basedir=/mysql/app/mysql
datadir=/mysql/data/3308/data
mysqld_pid_file_path=/mysql/data/3308/mysql.pid
#在$bindir/mysqld_safe 後面添加,注意 --defaults-file 要放在第一個
--defaults-file="/mysql/data/3308/my.cnf" 
systemctl daemon-reload      
MySQL + Keepalived 雙主熱備搭建

啟動 MySQL,修改密碼,運作遠端登入:

#啟動、MySQL服務
systemctl start mysql_3308
#擷取MySQL臨時密碼
Passwd=`cat /mysql/log/3308/*-error.log |grep "root@localhost:"|awk -F ' ' '{print $11}'`
echo $Passwd
#通過本地 socket 登入、修改密碼
mysql -uroot -p$Passwd -S /mysql/data/3308/mysql.sock
alter user 'root'@'localhost' identified by  "123456";
#允許遠端登入
grant all privileges on *.* to root@'%' identified by '123456';
#重新整理權限
flush privileges;      

初始化主庫B

主庫 B 配置檔案,主要是 ip 位址,server_id 以及 auto_increment_offset 的配置和主庫 A 不一樣,其餘配置和主庫 A 一樣。

#主機名和端口号作為目錄名的一部分
HostName=`hostname`
MySql_Port=3308
#IP位址
Ip=192.168.1.37
#master server_id 要和 slave 不一樣
Server_Id=2
cat > /mysql/data/$MySql_Port/my.cnf <<-EOF
#------------------------------------ 
#用戶端設定
#------------------------------------
[client]
port=$MySql_Port
socket =/mysql/data/$MySql_Port/mysql.sock
default-character-set=utf8 
#------------------------------------ 
#mysql連接配接工具設定
#------------------------------------
[mysql]
prompt="\\u@\\h : \\d\\r:\\m:\\s>" #登入時顯示登入的使用者名、伺服器位址、預設資料庫名、目前時間
auto-rehash #讀取表資訊和列資訊,可以在連上終端後開啟tab補齊功能。
default-character-set=utf8 #預設字元集
#------------------------------------ 
#基本設定
#------------------------------------
[mysqld]
bind_address=0.0.0.0  #監聽本地所有位址
port=$MySql_Port  #端口号
user=mysql  #使用者
basedir=/mysql/app/mysql  #安裝路徑
datadir=/mysql/data/$MySql_Port/data  #MySQL資料目錄
socket=/mysql/data/$MySql_Port/mysql.sock #用于本地連接配接的socket檔案目錄
pid-file=/mysql/data/$MySql_Port/mysql.pid #程序ID檔案的目錄。
character-set-server=utf8 #預設字元集
#------------------------------------ 
#log setting 日志設定
#------------------------------------
long_query_time=10 #慢查詢時間,超過 10 秒則認為是慢查詢
slow_query_log=ON #啟用慢查詢日志
slow_query_log_file=/mysql/log/$MySql_Port/${HostName}-query.log #慢查詢日志目錄
log_queries_not_using_indexes=1 #記錄未使用索引的語句
log_slow_admin_statements=1 #慢查詢也記錄那些慢的optimize table,analyze table和alter table語句
log-error=/mysql/log/$MySql_Port/${HostName}-error.log #錯誤日志目錄
#------------------------------------ 
#master modify parameter 主庫A複制更改參數
#------------------------------------
server_id=$Server_Id #master和slave server_id 需要不同
#二進制日志參數配置
log_bin=/mysql/log/$MySql_Port/binlog/${HostName}-binlog  #binlog目錄
log_bin_index=/mysql/log/$MySql_Port/binlog/${HostName}-binlog.index  #指定索引檔案的位置
binlog_format=row #行模式複制,預設是 row
binlog_rows_query_log_events=on #在 row 模式下,開啟該參數,可以将把 sql 語句列印到 binlog 日志裡面,友善檢視
binlog_cache_size=1M #事務能夠使用的最大 binlog 緩存空間。
max_binlog_size=2048M #binlog 檔案最大空間,達到該大小時切分檔案
expire_logs_days=7 #設定自動删除 binlog 檔案的天數。
sync_binlog=1 #表示每次事務的 binlog 都會fsync持久化到磁盤,MySQL 5.7.7 之後預設為1,之前的版本預設為0
innodb_flush_log_at_trx_commit=1 #表示每次事務的 redo log 都直接持久化到磁盤,預設值為1
#------------------------------------ 
#slave parameter 主庫B參數
#------------------------------------
relay_log=/mysql/log/$MySql_Port/relaylog/${HostName}-relaylog #中繼日志目錄
relay-log-index=/mysql/log/$MySql_Port/relaylog/${HostName}-relay.index  #中繼日志索引目錄
log_slave_updates=1 #主庫B從主庫A複制的資料會寫入主庫B binlog 日志檔案裡,預設是不寫入
read_only=0  #主庫B讀寫權限
relay_log_purge=1 #自動清空不再需要中繼日志
# 并行複制參數
#主庫A上面怎麼并行,主庫B上面就怎麼回放,基于邏輯時鐘的概念
#binlog 會記錄組送出的資訊,從回放的時候就可以知道哪些事務是一組裡面的,
#一組裡面的就丢到不同線程去回放,不是一組裡的就等待,以此來提升并行度
slave-parallel-type=LOGICAL_CLOCK
#多線程複制
slave-parallel-workers=4
#slave 上commit 的順序保持一緻,否則可能會有間隙鎖産生
slave-preserve-commit_order=1
master_info_repository=TABLE #預設每接收到10000個事件,寫一次master-info,預設是寫在檔案中的
#修改 relay_log_info_repository 的好處
#1.relay.info 明文存儲不安全,把 relay.info 中的資訊記錄在 table 中相對安全。
#2.可以避免 relay.info 更新不及時,slave 重新開機後導緻的主從複制出錯。
relay_log_info_repository=TABLE #将回放資訊記錄在 slave_relay_log_info 表中,預設是記錄在 relay-info.log 檔案中
relay_log_recovery=1  #當slave重新開機時,将所有 relay log 删除,通過 sql 線程重放的位置點去重新拉日志
#------------------------------------ 
#Replication Filter 主庫B複制過濾參數
#------------------------------------
#(過濾某個資料庫、資料庫.表)
#replicate_do_db=yzjtestdb
#replicate_wild_do_table=yzjtestdb.%
#replicate_do_table=yzjtestdb.yzjtest_yg
#replicate_wild_do_table=yzjtestdb.yzjtest_yg
#------------------------------------ 
#GTID Settings GTID 同步複制設定
#------------------------------------
gtid_mode=on  #開啟GTID同步
enforce_gtid_consistency=on #強制事務一緻,確定 GTID 的安全,在事務中就不能建立和删除臨時表
binlog_gtid_simple_recovery=1 #這個變量用于在 MySQL 重新開機或啟動的時候尋找 GTIDs 過程中,控制 binlog 如何周遊的算法
#------------------------------------ 
#避免主鍵沖突設定
#------------------------------------
auto_increment_increment=2  #自增主鍵步長,通常有幾個主庫A就寫幾,避免主鍵沖突
auto_increment_offset=2  #設定自增主鍵起始值,第一個主庫A為1,第二個主庫A為2,以此類推
EOF      

初始化主庫B:

mysqld \
--defaults-file=/mysql/data/3308/my.cnf \
--initialize --user=mysql \
--basedir=/mysql/app/mysql \
--datadir=/mysql/data/3308/data      
cp /mysql/app/mysql/support-files/mysql.server /etc/init.d/mysql_3308
ln -sf /etc/init.d/mysql_3308 /usr/lib/systemd/system/mysql_3308
#修改啟動腳本##
vi /etc/init.d/mysql_3308
basedir=/mysql/app/mysql
datadir=/mysql/data/3308/data
mysqld_pid_file_path=/mysql/data/3308/mysql.pid
#在$bindir/mysqld_safe 後面添加,注意 --defaults-file 要放在第一個
--defaults-file="/mysql/data/3308/my.cnf" 
systemctl daemon-reload      
#啟動、MySQL服務
systemctl start mysql_3308
#擷取MySQL臨時密碼
Passwd=`cat /mysql/log/3308/*-error.log |grep "root@localhost:"|awk -F ' ' '{print $11}'`
echo $Passwd
#通過本地 socket 登入、修改密碼
mysql -uroot -p$Passwd -S /mysql/data/3308/mysql.sock
alter user 'root'@'localhost' identified by  "123456";
#允許遠端登入
grant all privileges on *.* to root@'%' identified by '123456';
#重新整理權限
flush privileges;      

建立複制使用者

分别在主庫A 和主庫 B 上建立一個用于資料複制的使用者。

grant replication slave on *.* 
to 'repuser'@'%' identified by 'repuser123';      

建立主從關系

主庫 A 和主庫 B 都先清除下 binlog。

reset master;      

主庫 A 配置主從,指向主庫 B。

stop slave;
change master to
    master_host='192.168.1.36',
    master_port=3308,
    master_user='repuser',
    master_password='repuser123',
    master_auto_position=1;
start slave;      

主庫 B 配置主從,指向主庫 A。

stop slave;
change master to
    master_host='192.168.1.37',
    master_port=3308,
    master_user='repuser',
    master_password='repuser123',
    master_auto_position=1;
start slave;      

使用

show slave status\G

指令檢視主從同步狀态,IO 線程和 SQL 線程都為 YES 表示同步正常,主庫 A 和主庫 B 互為主從。

MySQL + Keepalived 雙主熱備搭建

部署 Keepalived

下載下傳并解壓安裝包

wget https://www.keepalived.org/software/keepalived-2.2.4.tar.gz
tar -xzvf keepalived-2.2.4.tar.gz      

安裝相關依賴

yum install kernel-devel openssl-devel popt-devel -y      

安裝 keepalived,設定開機自動啟動

mkdir /software/keepalived
cd keepalived-2.2.4
./configure --prefix=/software/keepalived
make && make install
systemctl enable keepalived
mkdir /etc/keepalived      

配置 Keepalived

主庫A 配置 Keepalived

主庫 A keepalived 配置檔案,編輯 /etc/keepalived/keepalived.conf 檔案:

global_defs { 
 router_id keep_mysql_repl_g1     # 負載均衡辨別,在區域網路内應該是唯一的
 
}
# vrrp_script 級别和 vrrp_instance 一樣
vrrp_script chk_mysql {        # 配置虛拟腳本 chk_mysql
 script "/etc/keepalived/check_mysql.sh"   # 執行腳本,檢查 mysql 服務是否存活
 interval 3          # 腳本執行間隔:秒
}
# vrrp_instance 
vrrp_instance v_mysql_1 { 
 state BACKUP          # 指定該 keepalived 節點的初始狀态(MASTER|BACKUP)
 interface ens192         # VRRP 執行個體綁定的網口,用于發送 VRRP 包
 virtual_router_id 200        # 路由 ID,範圍是 0-255,主備都一樣
 priority 100          # 指定優先級,優先級高的将成為 MASTER
 advert_int 1          # 指定發送 VRRP 廣播的間隔。機關是秒
 nopreempt           # 設定為不搶占。預設是搶占的
         
authentication {          # 身份驗證
 auth_type PASS                                  # 指定認證方式
 auth_pass mysql                                 # 指定認證所使用的密碼 mysql ,主備都一樣
}
track_script {           # 調用"vrrp_script"的腳本
 chk_mysql           # 增加一個跟蹤腳本到網口上
}
virtual_ipaddress {         # 虛拟 IP
 192.168.1.38/24 
 } 
}      

主庫 A 檢查腳本,編輯 /etc/keepalived/check_mysql.sh 檔案:

#!/bin/bash
#/etc/keepalived/check_mysql.sh
#chmod u+x /etc/keepalived/check_mysql.sh
#Linux 7 使用,如果是配置Linux 6 需要修改腳本
# MySQL賬号密碼
mysql_user="root"
mysql_pass="123456"
# MySQL錯誤日志輸出
mysql_err="/mysql/log/3308/check_mysql_err.log"
# MySQL殺程序腳本
mysql_kill_session="/tmp/kill.sql"
# MySQL連接配接字元串
mysql_con="mysql -u${mysql_user} -p${mysql_pass} -S /mysql/data/3308/mysql.sock"
source ~/.bash_profile
if [ `ps -ef|grep -w "$0"|grep "/bin/sh*"|grep "?"|grep "?"|grep -v "grep"|wc -l` -gt 2 ];then  #
    exit 0
fi
function excute_query {
    $mysql_con -e "select 1 from dual;" 2>> $mysql_err
}
function service_error {
    echo -e "`date "+%F  %H:%M:%S"`    -----mysql service error,now stop keepalived-----" >> $mysql_err
    echo -e "\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n" >> $mysql_err
}
function query_error {
    echo -e "`date "+%F  %H:%M:%S"`    -----query error, but mysql service ok, retry after 30s-----" >> $mysql_err
    sleep 30
    excute_query
    if [ $? -ne 0 ];then
        echo -e "`date "+%F  %H:%M:%S"`    -----still can't execute query-----" >> $mysql_err
        echo -e "`date "+%F  %H:%M:%S"`    -----set read_only = 1 on DB1-----" >> $mysql_err
        $mysql_con -e "set global read_only = 1;" 2>> $mysql_err
        echo -e "`date "+%F  %H:%M:%S"`    -----kill current client thread-----" >> $mysql_err
        rm -f $mysql_kill_session &>/dev/null
        $mysql_con -NB -e 'select concat("kill ",id,";") from  information_schema.PROCESSLIST where command="Query" or command="Execute"' > $mysql_kill_session
        $mysql_con -e "source $mysql_kill_session"
        sleep 2 
        echo -e "`date "+%F  %H:%M:%S"`    -----stop keepalived-----" >> $mysql_err
        systemctl stop keepalived &>> $mysql_err
        echo -e "\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n" >> $mysql_err
    else
        echo -e "`date "+%F  %H:%M:%S"`    -----query ok after 30s-----" >> $mysql_err
        echo -e "\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n" >> $mysql_err
    fi
}
excute_query
if [ $? -ne 0 ];then
    systemctl status mysql &>/dev/null
    if [ $? -ne 0 ];then
        service_error
    else
        query_error
    fi
fi      

給檢查腳本賦與執行權限:

chmod u+x /etc/keepalived/check_mysql.sh      

主庫 B 配置 Keepalived

主庫 B keepalived 配置檔案,編輯 /etc/keepalived/keepalived.conf 檔案:

global_defs { 
 router_id keep_mysql_repl_g1        # 負載均衡辨別,在區域網路内應該是唯一的
 
}
# vrrp_instance 
vrrp_instance v_mysql_1 {          
 state BACKUP            # 指定該 keepalived 節點的初始狀态(MASTER|BACKUP)   
 interface ens192                                         # VRRP 執行個體綁定的網口,用于發送 VRRP 包
 virtual_router_id 200                                   # 路由ID,範圍是0-255,主備都一樣
 priority 90                                             # 指定優先級,優先級高的将成為 MASTER
 advert_int 1                                            # 指定發送VRRP廣播的間隔。機關是秒
 nopreempt                                               # 設定為不搶占。預設是搶占的
 
authentication {            # 身份驗證
 auth_type PASS                                          # 指定認證方式
 auth_pass mysql                                         # 指定認證所使用的密碼 mysql ,主備都一樣
}
notify_master /etc/keepalived/notify_master_mysql.sh  # 轉換成 master 時,執行的腳本
virtual_ipaddress { 
 192.168.1.38/24 
 } 
}      

主庫 B 腳本,當發生主從切換時,會執行該腳本。編輯 /etc/keepalived/notify_master_mysql.sh 檔案:

#!/bin/bash
#/etc/keepalived/notify_master_mysql.sh
#chmod u+x /etc/keepalived/notify_master_mysql.sh
# MySQL賬号密碼
mysql_user="root"
mysql_pass="123456"
# 配置更變日志
change_log="/mysql/log/3308/state_change.log"
# 主庫B狀态日志
slave_status_log="/mysql/log/3308/slave_status_log.log"
# MySQL連接配接字元串
mysql_conn="mysql -u${mysql_user} -p${mysql_pass} -S /mysql/data/3308/mysql.sock"
source ~/.bash_profile
echo -e "`date "+%F  %H:%M:%S"`   -----keepalived change to MASTER-----" >> $change_log
echo -e "`date "+%F  %H:%M:%S"`   ----------" >> $slave_status_log
$mysql_conn -e "show slave status\G;" >> $slave_status_log
Slave_IO_Running=`$mysql_conn -e "show slave status\G;"|egrep -w "Slave_IO_Running|Slave_SQL_Running"|awk 'NR==1{print}' | awk '{print $2}'`
Slave_SQL_Running=`$mysql_conn -e "show slave status\G;"|egrep -w "Slave_IO_Running|Slave_SQL_Running"|awk 'NR==2{print}' | awk '{print $2}'`
Master_Log_File=`$mysql_conn -e "show slave status\G;" |egrep -w "Master_Log_File|Read_Master_Log_Pos|Exec_Master_Log_Pos"|awk 'NR==1{print}' | awk '{print $2}'`
Read_Master_Log_Pos=`$mysql_conn -e "show slave status\G;" |egrep -w "Master_Log_File|Read_Master_Log_Pos|Exec_Master_Log_Pos"|awk 'NR==2{print}' | awk '{print $2}'`
Exec_Master_Log_Pos=`$mysql_conn -e "show slave status\G;" |egrep -w "Master_Log_File|Read_Master_Log_Pos|Exec_Master_Log_Pos"|awk 'NR==3{print}' | awk '{print $2}'`
action() {
    echo -e "`date "+%F  %H:%M:%S"`    -----set read_only = 0 on `hostname`-slave-----" >> $change_log
    $mysql_conn -e "set global read_only = 0;" 2>> $change_log
        $mysql_conn -e "stop slave;" 2>> $change_log
    echo "`hostname`-slave keepalived 轉為 MASTER 狀态,線上資料庫切換至`hostname`-slave" >> $change_log
    echo -e "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n" >> $change_log
}
if [ "$Slave_IO_Running" = "Yes" -a "$Slave_SQL_Running" = "Yes" ];then
        if [ $Read_Master_Log_Pos = $Exec_Master_Log_Pos ];then
            echo -e "`date "+%F  %H:%M:%S"`    -----Master_Log_File=$Master_Log_File . Exec_Master_Log_Pos($Exec_Master_Log_Pos) is equal Read_Master_Log_Pos($Read_Master_Log_Pos)" >> $change_log
                        action
                        $mysql_conn -e "reset slave all;" 2>> $change_log
                        else
                    echo -e "`date "+%F  %H:%M:%S"`    -----Master_Log_File=$Master_Log_File . Exec_Master_Log_Pos($Exec_Master_Log_Pos) is behind Read_Master_Log_Pos($Read_Master_Log_Pos), The waits time is more than 10s,now force change." >> $change_log
                    sleep 10
                        action
                        $mysql_conn -e "reset slave all;" 2>> $change_log
            exit 0
        fi
action 
else
    echo -e "`hostname`-slave's slave status is wrong,now force change. Master_Log_File=$Master_Log_File Read_Master_Log_Pos=$Read_Master_Log_Pos  Exec_Master_Log_Pos=$Exec_Master_Log_Pos" >> $change_log
  action
fi      

給腳本賦與執行權限:

chmod u+x /etc/keepalived/notify_master_mysql.sh      

啟動 Keepalived

在主庫 A 和主庫 B 上分别啟動 keepalived。

systemctl start keepalived      

檢視 keepalived 狀态。

[root@mysql-master keepalived-2.2.4]# systemctl status keepalived.service 
● keepalived.service - LVS and VRRP High Availability Monitor
   Loaded: loaded (/usr/lib/systemd/system/keepalived.service; enabled; vendor preset: disabled)
   Active: active (running) since 五 2021-09-10 21:13:37 CST; 18s ago
     Docs: man:keepalived(8)
           man:keepalived.conf(5)
           man:genhash(1)
           https://keepalived.org
  Process: 8184 ExecStart=/software/keepalived/sbin/keepalived $KEEPALIVED_OPTIONS (code=exited, status=0/SUCCESS)
 Main PID: 8186 (keepalived)
   Memory: 680.0K
   CGroup: /system.slice/keepalived.service
           ├─8186 /software/keepalived/sbin/keepalived -D
           └─8187 /software/keepalived/sbin/keepalived -D
9月 10 21:13:41 mysql-master Keepalived_vrrp[8187]: Sending gratuitous ARP on ens192 for 192.168.1.38
9月 10 21:13:41 mysql-master Keepalived_vrrp[8187]: Sending gratuitous ARP on ens192 for 192.168.1.38
9月 10 21:13:41 mysql-master Keepalived_vrrp[8187]: Sending gratuitous ARP on ens192 for 192.168.1.38
9月 10 21:13:41 mysql-master Keepalived_vrrp[8187]: Sending gratuitous ARP on ens192 for 192.168.1.38
9月 10 21:13:46 mysql-master Keepalived_vrrp[8187]: (v_mysql_1) Sending/queueing gratuitous ARPs on ens192 for 192.168.1.38
9月 10 21:13:46 mysql-master Keepalived_vrrp[8187]: Sending gratuitous ARP on ens192 for 192.168.1.38
9月 10 21:13:46 mysql-master Keepalived_vrrp[8187]: Sending gratuitous ARP on ens192 for 192.168.1.38
9月 10 21:13:46 mysql-master Keepalived_vrrp[8187]: Sending gratuitous ARP on ens192 for 192.168.1.38
9月 10 21:13:46 mysql-master Keepalived_vrrp[8187]: Sending gratuitous ARP on ens192 for 192.168.1.38
9月 10 21:13:46 mysql-master Keepalived_vrrp[8187]: Sending gratuitous ARP on ens192 for 192.168.1.38      

檢視網卡位址,此時虛拟 IP 在主庫 A 上。

[root@mysql-master keepalived-2.2.4]# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: ens192: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP qlen 1000
    link/ether 00:50:56:8b:1b:ca brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.36/24 brd 192.168.1.255 scope global ens192
       valid_lft forever preferred_lft forever
    #虛拟 IP
    inet 192.168.1.38/24 scope global secondary ens192
       valid_lft forever preferred_lft forever
    inet6 fe80::2556:f369:b4e7:fb64/64 scope link tentative dadfailed 
       valid_lft forever preferred_lft forever
    inet6 fe80::6b8d:29f7:a5fe:dbee/64 scope link tentative dadfailed 
       valid_lft forever preferred_lft forever
    inet6 fe80::f387:57a3:4975:d8f2/64 scope link tentative dadfailed 
       valid_lft forever preferred_lft forever      

驗證高可用

用戶端通過虛拟 IP 192.168.1.38 連接配接資料庫,通過

select @@hostname

指令可以看到目前連接配接的為主庫 A。

❯ mysql -uroot -h 192.168.1.38 -P 3308  -p123456;
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 268
Server version: 5.7.29-log MySQL Community Server (GPL)
Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
[email protected] (none) 09:11:13>select @@hostname;
+--------------+
| @@hostname   |
+--------------+
| mysql-master |
+--------------+
1 row in set (0.01 sec)
[email protected] (none) 09:11:26>      

用戶端建立表并插入資料。

create database testdb;
create table testdb.data01( 
id int not null primary key auto_increment, 
name varchar(60), 
age int); 
insert into testdb.data01 (name,age) values
('tom',18),
('jack',17),
('rock',16),
('james',15),
('cris',20);      

此時分别登入主庫 A 和主庫 B 檢視 testdb.data01 表中的資料,可以确定主庫 A 和主庫 B 目前資料是同步的。并且檢視表中的内容可以發現主鍵是以 2 為間隔遞增的,這是為了防止主從切換時插入資料産生主鍵沖突。主庫 A 的主鍵會以 1,3,5,7,9 的序号遞增。假如在序号為 9 時發生主從切換,新的主庫(主庫 A)的主鍵會以 10,12,14,16,18 的序号遞

MySQL + Keepalived 雙主熱備搭建

停止主庫A,模拟故障切換

[root@mysql-master ~]# systemctl stop mysql_3308.service      

在主庫 A 的機器上檢視 keepalived 狀态,可以看到 keepalived 的優先級被設定為 0,此時虛拟 IP 将會飄到主庫 B 的機器上。

[root@mysql-master tmp]# systemctl status keepalived.service 
● keepalived.service - LVS and VRRP High Availability Monitor
   Loaded: loaded (/usr/lib/systemd/system/keepalived.service; enabled; vendor preset: disabled)
   Active: active (running) since 五 2021-09-10 21:42:11 CST; 3min 59s ago
     Docs: man:keepalived(8)
           man:keepalived.conf(5)
           man:genhash(1)
           https://keepalived.org
  Process: 13365 ExecStart=/software/keepalived/sbin/keepalived $KEEPALIVED_OPTIONS (code=exited, status=0/SUCCESS)
 Main PID: 13367 (keepalived)
   Memory: 1.0M
   CGroup: /system.slice/keepalived.service
           ├─13367 /software/keepalived/sbin/keepalived -D
           ├─13368 /software/keepalived/sbin/keepalived -D
           ├─14761 /bin/bash /etc/keepalived/check_mysql.sh
           └─14778 sleep 30
9月 10 21:42:20 mysql-master Keepalived_vrrp[13368]: Sending gratuitous ARP on ens192 for 192.168.1.38
9月 10 21:42:20 mysql-master Keepalived_vrrp[13368]: Sending gratuitous ARP on ens192 for 192.168.1.38
9月 10 21:42:20 mysql-master Keepalived_vrrp[13368]: Sending gratuitous ARP on ens192 for 192.168.1.38
9月 10 21:42:20 mysql-master Keepalived_vrrp[13368]: Sending gratuitous ARP on ens192 for 192.168.1.38
9月 10 21:42:20 mysql-master Keepalived_vrrp[13368]: Sending gratuitous ARP on ens192 for 192.168.1.38
9月 10 21:45:47 mysql-master Keepalived_vrrp[13368]: Track script chk_mysql is already running, expect idle - skipping run
9月 10 21:45:47 mysql-master Keepalived_vrrp[13368]: VRRP_Script(chk_mysql) timed_out
9月 10 21:45:47 mysql-master Keepalived_vrrp[13368]: (v_mysql_1) Entering FAULT STATE
9月 10 21:45:47 mysql-master Keepalived_vrrp[13368]: (v_mysql_1) sent 0 priority
9月 10 21:45:47 mysql-master Keepalived_vrrp[13368]: (v_mysql_1) removing VIPs.      

檢視主庫 B 機器網卡的位址,發現虛拟 IP 已經切換到主庫 B 上了。當發生主從切換時,主庫 B 的腳本會執行

reset slave all

,停止向主庫 A 的同步,防止原主庫 A 恢複後資料意外同步。

[root@mysql-slave keepalived]# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: ens192: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP qlen 1000
    link/ether 00:50:56:8b:71:df brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.37/24 brd 192.168.1.255 scope global ens192
    #虛拟 IP
       valid_lft forever preferred_lft forever
    inet 192.168.1.38/24 scope global secondary ens192
       valid_lft forever preferred_lft forever
    inet6 fe80::2556:f369:b4e7:fb64/64 scope link tentative dadfailed 
       valid_lft forever preferred_lft forever
    inet6 fe80::6b8d:29f7:a5fe:dbee/64 scope link tentative dadfailed 
       valid_lft forever preferred_lft forever
    inet6 fe80::f387:57a3:4975:d8f2/64 scope link tentative dadfailed 
       valid_lft forever preferred_lft forever      

用戶端發生了重連,通過

select @@hostname

檢視可以看到此時連接配接的是主庫 B。

[email protected] (none) 09:42:14>select @@hostname;
#重連
ERROR 2006 (HY000): MySQL server has gone away
No connection. Trying to reconnect...
Connection id:    19
Current database: *** NONE ***
+-------------+
| @@hostname  |
+-------------+
| mysql-slave |
+-------------+
1 row in set (0.04 sec)      

用戶端插入幾條資料:

insert into testdb.data01 (name,age) values
('peter',28),
('mark',27),
('marry',26),
('hule',25),
('handson',20);      

查詢資料,可以看到在原主庫 B 上插入的資料主鍵會以 10,12,14,16,18 的序号遞增。

[email protected] (none) 09:44:19>select * from testdb.data01;
+----+---------+------+
| id | name    | age  |
+----+---------+------+
|  1 | tom     |   18 |
|  3 | jack    |   17 |
|  5 | rock    |   16 |
|  7 | james   |   15 |
|  9 | cris    |   20 |
| 10 | peter   |   28 |
| 12 | mark    |   27 |
| 14 | marry   |   26 |
| 16 | hule    |   25 |
| 18 | handson |   20 |
+----+---------+------+
10 rows in set (0.01 sec)      

重新啟動主庫 A,觀察資料同步

由于我們關閉了搶占模式,當主庫 A 重新啟動時,主從不會發送切換。

[root@mysql-master]# systemctl start mysql_3308.service      

主庫 A 的資料可以和主庫 B 同步。

MySQL + Keepalived 雙主熱備搭建

參考資料