MySQL主從復制
將主數據庫中的DDL和DML操作通過二進制日志傳輸到從數據庫上,然后將這些日志重新執行(重做)一遍;從而使得從數據庫的數據與主數據庫保持一致。
1、MySQL 主從復制的基本介紹
MySQL支持單向、異步復制,復制過程中一個服務器充當主服務器,而一個或多個其它服務器充當從服務器。
MySQL復制是基於主服務器在二進制日志中跟蹤所有對數據庫的更改。因此,要進行復制,必須在主服務器上啟用二進制日志。每個從服務器從主服務器接收主服務器已經記錄到日志的數據。
當一個從服務器連接主服務器時,它通知主服務器從服務器在日志中讀取的最后一次成功更新的位置。從服務器接收從那時起發生的任何更新,並在本機上執行相同的更新。然后封鎖並等待主服務器通知新的更新。從服務器執行備份不會干擾主服務器,在備份過程中主服務器可以繼續處理更新。
2、什么是主從復制
簡單來說,是使用兩個或兩個以上相同的數據庫,將一個數據庫當做主數據庫,而另一個數據庫當做從數據庫。在主數據庫中進行相應操作時,從數據庫記錄下所有主數據庫的操作,使其二者一模一樣。
MySQL數據庫主從復制主要分為三步:
-
master將改變記錄到二進制日志(binary log)中(這些記錄叫做二進制日志事件,binary log events)。
-
slave的io線程將master的binary log events拷貝到它的中繼日志(relay log)。
-
slave的sql線程解析中繼日志中的事件並在從庫執行,保持與主庫一致。
Binary log:主數據庫的二進制日志。
Relay log:從服務器的中繼日志。
注意:復制過程有一個很重要的限制——復制在slave上是串行化的,也就是說master上的並行更新操作不能在slave上並行操作。
1、從庫准備
- 從庫change master to 時,ip port user password binlog position寫入到master.info進行記錄。
- 從庫 start slave 時,會啟動IO線程和SQL線程。
2、同步的過程
-
從庫的IO線程,讀取master.info信息,獲取主庫信息並連接主庫。
-
主庫接收從庫的鏈接請求后,會生成一個准備binlog DUMP的線程,來響應從庫。
-
主庫一旦有新的日志生成,會發送“信號”給主庫的binlog dump線程,然后binlog dump線程會讀取binlog日志的更新。
-
通過binlog dump線程j將數據傳送給從庫的IO線程。
-
IO線程將收到的日志存儲到了TCP/IP 緩存。
-
寫入TCP/IP緩存后,立即返回ACK消息給主庫 ,此時主庫工作完成。
-
IO線程更新master.info文件、binlog 文件名和postion定位。
-
IO線程將緩存中的數據,存儲到relay-log日志文件,此時io線程工作完成。
-
從庫SQL線程讀取relay-log.info文件,獲取到上次執行到的relay-log的位置,作為起點。
-
從庫SQL線程基於從步驟9中獲取到的起點,去中繼日志relay-log.000001獲取后續操作,在從庫回放relay-log中繼日志之中內從主庫復制過來的數據。
-
SQL線程回放完成之后,會更新relay-log.info文件,把當前操作的位置記入,作為下一次操作的起點。
-
relay-log會有自動清理的功能。
注:在tcp協議中通訊之前都要經過三次握手,請求方發出一個syn信號請求連接,對方收到並接受的時候就會發出ack消息,ack就是回應的意思。
3、主從復制的方式
MySQL的主從復制有兩種復制方式,分別是異步復制和半同步復制。
1、異步復制
我們之前介紹的就是異步復制,即客戶端線程提交一個寫操作,寫入主庫的binlog日志后就立即返回,並不需要等待從庫完成同步操作,而主庫的dump線程會監測binlog日志的變量然后主動將更新推送給從庫。
MySQL 主從復制默認是異步的模式。
1.1、主從復制實現
要實現主從復制,需要如下幾步:
1、在主庫上創建一個用於復制的賬號。
2、修改主庫配置文件,開啟主庫的Binlog,並設置server-id和重啟。
3、導出主庫中所有的數據,先導給從庫
4、修改從庫配置文件並重啟
5、配置主從復制
6、開啟主從復制
- 在主庫上創建一個用於復制的賬號
mysql> grant replication slave on *.* to 'shanhe'@'%' identified by '123456';
mysql> flush privileges;
- 修改主庫配置文件
[root@mysql-1 ~]# cat /etc/my.cnf
[mysqld]
basedir=/usr/local/mysql
datadir=/data
port=3306
socket=/usr/local/mysql/mysql.sock
character-set-server=utf8
log-error=/var/log/mysqld.log
pid-file=/tmp/mysqld.pid
server-id=1
log-bin=/data/log-bin/binlog
sync_binlog=1
binlog_format=row
expire_logs_days=7
max_binlog_size=100m
binlog_cache_size=4m
max_binlog_cache_size=512m
binlog-ignore-db=mysql
auto-increment-offset=1
auto-increment-increment=1
slave-skip-errors=all
[mysql]
socket=/usr/local/mysql/mysql.sock
[client]
socket=/usr/local/mysql/mysql.sock
# 重啟
[root@mysql-1 ~]# systemctl restart mysqld
- 導出主庫中所有的數據,先導給從庫
[root@mysql-1 ~]# mysqldump -uroot -p123456 -A -E -R --triggers --master-data=2 --single-transaction > /tmp/all.sql
- 將數據導入從庫
[root@mysql-1 ~]# scp /tmp/all.sql root@192.168.15.62:/tmp/
root@192.168.15.62''s password:
all.sql 100% 853KB 3.5MB/s 00:00
[root@mysql-2 mysql]# mysql -uroot -p123456 < /tmp/all.sql
- 修改從庫配置文件並重啟
[root@mysql-2 mysql]# vim /etc/my.cnf
[mysqld]
basedir=/usr/local/mysql
datadir=/data
port=3306
socket=/usr/local/mysql/mysql.sock
character-set-server=utf8
log-error=/var/log/mysqld.log
pid-file=/tmp/mysqld.pid
server-id=2
# 中繼日志
relay-log=mysql-relay-bin
replicate-wild-ignore-table=mysql.%
replicate-wild-ignore-table=test.%
replicate-wild-ignore-table=information_schema.%
[mysql]
socket=/usr/local/mysql/mysql.sock
[client]
socket=/usr/local/mysql/mysql.sock
[root@mysql-2 mysql]# systemctl restart mysqld
- 配置主從復制
配置主從復制,首先得在MySQL Master節點查出binlog日志狀態,然后配置主從復制
- 在MySQL Master節點查出binlog日志狀態
mysql> show master status ;
+---------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+---------------+----------+--------------+------------------+-------------------+
| binlog.000001 | 154 | | mysql | |
+---------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
- 在從庫配置主從復制
# 首先測試主庫是否連接正常
[root@mysql-2 mysql]# mysql -uroot -p123456 -h192.168.15.61
[root@slave1 ~]# mysql -uroot -p123456 # 登錄然后執行
change master to
master_host='192.168.15.61', -- 庫服務器的IP
master_port=3306, -- 主庫端口
master_user='shanhe', -- 主庫用於復制的用戶
master_password='123456', -- 密碼
master_log_file='binlog.000001', -- 主庫日志名
master_log_pos=154; -- 主庫日志偏移量,即從何處開始復制
- 查看主從復制結果
2、半同步復制
介於異步復制和全同步復制之間,主庫在執行完客戶端提交的事務后不是立刻返回給客戶端,而是等待至少一個從庫接收到並寫到relay log中才返回給客戶端。相對於異步復制,半同步復制提高了數據的安全性,同時它也造成了一定程度的延遲,這個延遲最少是一個TCP/IP往返的時間。所以,半同步復制最好在低延時的網絡中使用。
半同步復制超時則會切換回異步復制,正常后則切回半同步復制
在半同步復制時,如果主庫的一個事務提交成功了,在推送到從庫的過程當中,從庫宕機了或網絡故障,導致從庫並沒有接收到這個事務的Binlog,此時主庫會等待一段時間(這個時間由rpl_semi_sync_master_timeout的毫秒數決定),如果這個時間過后還無法推送到從庫,那MySQL會自動從半同步復制切換為異步復制,當從庫恢復正常連接到主庫后,主庫又會自動切換回半同步復制。
2.1、部署半同步復制
半同步模式是作為MySQL5.5的一個插件來實現的,主從庫使用的插件不一樣。
- 先確認主從的MySQL服務器是否支持動態增加插件
mysql> select @@have_dynamic_loading;
+------------------------+
| @@have_dynamic_loading |
+------------------------+
| YES |
+------------------------+
1 row in set (0.08 sec)
- 分別在主從庫上安裝對用插件
-- 主庫安裝插件
mysql> install plugin rpl_semi_sync_master soname 'semisync_master.so';
Query OK, 0 rows affected (0.28 sec)
-- 從庫安裝插件
mysql> install plugin rpl_semi_sync_slave soname 'semisync_slave.so';
Query OK, 0 rows affected (0.20 sec)
- 在主庫開啟半同步復制
mysql> set global rpl_semi_sync_master_enabled=1;
Query OK, 0 rows affected (0.09 sec)
mysql> set global rpl_semi_sync_master_timeout=30000;
Query OK, 0 rows affected (0.00 sec)
# 添加到配置文件
[root@mysql-1 ~]# vim /etc/my.cnf
rpl_semi_sync_master_enabled=1
rpl_semi_sync_master_timeout=1000
- 在從庫開啟半同步復制
mysql> set global rpl_semi_sync_slave_enabled=1;
Query OK, 0 rows affected (0.07 sec)
mysql> stop slave io_thread;
Query OK, 0 rows affected (0.09 sec)
mysql> start slave io_thread;
Query OK, 0 rows affected (0.06 sec)
# 添加到配置文件之中
[root@mysql-2 ~]# vim /etc/my.cnf
rpl_semi_sync_slave_enabled =1
- 在主庫上查看半同步復制的狀態
-- 主庫查看
mysql> show status like 'Rpl_semi_sync_master_status';
+-----------------------------+-------+
| Variable_name | Value |
+-----------------------------+-------+
| Rpl_semi_sync_master_status | ON |
+-----------------------------+-------+
1 row in set (0.00 sec)
-- 從庫查看
mysql> show status like 'Rpl_semi_sync_slave_status';
+----------------------------+-------+
| Variable_name | Value |
+----------------------------+-------+
| Rpl_semi_sync_slave_status | ON |
+----------------------------+-------+
1 row in set (0.00 sec)
這兩個變量常用來監控主從是否運行在半同步復制模式下。
3、多主多從
在企業中,數據庫高可用一直是企業的重中之重,中小企業很多都是使用mysql主從方案,一主多從,讀寫分離等,但是單主存在單點故障,從庫切換成主庫需要作改動。因此,如果是雙主或者多主,就會增加mysql入口,增加高可用。不過多主需要考慮自增長ID問題,這個需要特別設置配置文件,比如雙主,可以使用奇偶,總之,主之間設置自增長ID相互不沖突就能完美解決自增長ID沖突問題。
3.1、MySQL雙主(主主)架構方案思路
-
兩台mysql都可讀寫,互為主備,默認只使用一台(masterA)負責數據的寫入,另一台(masterB)備用。
-
masterA是masterB的主庫,masterB又是masterA的主庫,它們互為主從。
-
兩台主庫之間做高可用,可以采用keepalived等方案(使用VIP對外提供服務)。
-
所有提供服務的從服務器與masterB進行主從同步(雙主多從)。
-
建議采用高可用策略的時候,masterA或masterB均不因宕機恢復后而搶占VIP(非搶占模式)。
這樣做可以在一定程度上保證主庫的高可用,在一台主庫down掉之后,可以在極短的時間內切換到另一台主庫上(盡可能減少主庫宕機對業務造成的影響),減少了主從同步給線上主庫帶來的壓力;但是也有幾個不足的地方:
- masterB可能會一直處於空閑狀態。
- 主庫后面提供服務的從庫要等masterB先同步完了數據后才能去masterB上去同步數據,這樣可能會造成一定程度的同步延時。
3.2、修改主節點的配置文件
[mysqld]
basedir=/usr/local/mysql
datadir=/data
port=3306
socket=/usr/local/mysql/mysql.sock
character-set-server=utf8
log-error=/var/log/mysqld.log
pid-file=/tmp/mysqld.pid
# 節點ID,確保唯一
server-id = 1
#開啟mysql的binlog日志功能
log-bin=binlog
#控制數據庫的binlog刷到磁盤上去 , 0 不控制,性能最好,1每次事物提交都會刷到日志文件中,性能最差,最安全
sync_binlog=1
#binlog日志格式
binlog_format=row
#binlog過期清理時間
expire_logs_days=7
#binlog每個日志文件大小
max_binlog_size=100m
#binlog緩存大小
binlog_cache_size=4m
#最大binlog緩存大小
max_binlog_cache_size=512m
#不生成日志文件的數據庫,多個忽略數據庫可以用逗號拼接,或者 復制黏貼下述配置項,寫多行
binlog-ignore-db=mysql
# 表中自增字段每次的偏移量
auto-increment-offset=1
# 表中自增字段每次的自增量
auto-increment-increment=2
#跳過從庫錯誤
slave-skip-errors=all
#將復制事件寫入binlog,一台服務器既做主庫又做從庫此選項必須要開啟
log-slave-updates=true
gtid-mode=on
enforce-gtid-consistency=true
master-info-repository=file
relay-log-info-repository=file
sync-master-info=1
slave-parallel-workers=0
sync_binlog=0
binlog-checksum=CRC32
master-verify-checksum=1
slave-sql-verify-checksum=1
binlog-rows-query-log_events=1
max_binlog_size=1024M
# 忽略同步的數據庫
replicate-ignore-db=mysql
replicate-ignore-db=information_schema
replicate-ignore-db=performance_schema
replicate-ignore-db=sys
max_connections=3000
max_connect_errors=30
#忽略應用程序想要設置的其他字符集
skip-character-set-client-handshake
#連接時執行的SQL
init-connect='SET NAMES utf8'
#服務端默認字符集
character-set-server=utf8
#請求的最大連接時間
wait_timeout=1800
#和上一參數同時修改才會生效
interactive_timeout=1800
#sql模式
sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES
max_allowed_packet=10M
bulk_insert_buffer_size=8M
query_cache_type=1
query_cache_size=128M
query_cache_limit=4M
key_buffer_size=256M
read_buffer_size=16K
# 禁用反向解析
skip-name-resolve
slow_query_log=1
long_query_time=6
slow_query_log_file=slow-query.log
innodb_flush_log_at_trx_commit=2
innodb_log_buffer_size=16M
[mysql]
socket=/usr/local/mysql/mysql.sock
[client]
socket=/usr/local/mysql/mysql.sock
3.3、修改備節點的配置文件
[mysqld]
basedir=/usr/local/mysql
datadir=/data
port=3306
socket=/usr/local/mysql/mysql.sock
character-set-server=utf8
log-error=/var/log/mysqld.log
pid-file=/tmp/mysqld.pid
# 節點ID,確保唯一
server-id=2
#開啟mysql的binlog日志功能
log-bin=binlog
#控制數據庫的binlog刷到磁盤上去 , 0 不控制,性能最好,1每次事物提交都會刷到日志文件中,性能最差,最安全
sync_binlog=1
#binlog日志格式
binlog_format=row
#binlog過期清理時間
expire_logs_days=7
#binlog每個日志文件大小
max_binlog_size=100m
#binlog緩存大小
binlog_cache_size=4m
#最大binlog緩存大小
max_binlog_cache_size=512m
#不生成日志文件的數據庫,多個忽略數據庫可以用逗號拼接,或者 復制黏貼下述配置項,寫多行
binlog-ignore-db=mysql
# 表中自增字段每次的偏移量
auto-increment-offset=2
# 表中自增字段每次的自增量
auto-increment-increment=2
#跳過從庫錯誤
slave-skip-errors=all
#將復制事件寫入binlog,一台服務器既做主庫又做從庫此選項必須要開啟
log-slave-updates=true
gtid-mode=on
enforce-gtid-consistency=true
master-info-repository=file
relay-log-info-repository=file
sync-master-info=1
slave-parallel-workers=0
sync_binlog=0
binlog-checksum=CRC32
master-verify-checksum=1
slave-sql-verify-checksum=1
binlog-rows-query-log_events=1
max_binlog_size=1024M
# 忽略同步的數據庫
replicate-ignore-db=information_schema
replicate-ignore-db=performance_schema
replicate-ignore-db=sys
max_connections=3000
max_connect_errors=30
#忽略應用程序想要設置的其他字符集
skip-character-set-client-handshake
#連接時執行的SQL
init-connect='SET NAMES utf8'
#服務端默認字符集
character-set-server=utf8
#請求的最大連接時間
wait_timeout=1800
#和上一參數同時修改才會生效
interactive_timeout=1800
#sql模式
sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES
max_allowed_packet=10M
bulk_insert_buffer_size=8M
query_cache_type=1
query_cache_size=128M
query_cache_limit=4M
key_buffer_size=256M
read_buffer_size=16K
# 禁用反向解析
skip-name-resolve
slow_query_log=1
long_query_time=6
slow_query_log_file=slow-query.log
innodb_flush_log_at_trx_commit=2
innodb_log_buffer_size=16M
[mysql]
socket=/usr/local/mysql/mysql.sock
[client]
socket=/usr/local/mysql/mysql.sock
3.4、兩個master階段都必須重新初始化數據庫
[root@mysql-1 ~]# mysqld --initialize --user=mysql --basedir=/usr/local/mysql --datadir=/data
[root@mysql-2 ~]# mysqld --initialize --user=mysql --basedir=/usr/local/mysql --datadir=/data
3.5、分別登陸數據庫並創建復制賬號(每個機器上都必須執行)
-- 查看生成的密碼
[root@mysql-2 ~]# grep password /var/log/mysqld.log
-- 使用臨時密碼登陸數據庫
[root@mysql-2 ~]# mysql -uroot -p'_<r,zjMpG6-.'
-- 修改數據庫臨時密碼
mysql> alter user root@localhost identified by 'Test123!';
Query OK, 0 rows affected (0.00 sec)
-- 利用數據庫臨時密碼創建登陸
[root@mysql-2 ~]# mysql -uroot -p'Test123!'
-- 創建遠程連接賬號
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '123456' WITH GRANT OPTION;
Query OK, 0 rows affected, 1 warning (0.08 sec)
-- 刪除其他默認密碼
mysql> delete from mysql.user where host = 'localhost';
Query OK, 3 rows affected (0.29 sec)
-- 刷新權限
mysql> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.09 sec)
-- 重新登陸並創建遠程復制賬號
[root@mysql-2 ~]# mysql -uroot -p123456
mysql> grant replication slave on *.* to 'shanhe'@'%' identified by '123456';
Query OK, 0 rows affected, 1 warning (0.01 sec)
mysql> flush privileges;
Query OK, 0 rows affected (0.00 sec)
# 'masterB節點操作與上述操作一致~~,當然除了初始密碼不一樣。。。'
3.6、開始配置主從復制
-- 1. 先查看master1上的信息
'如果masterB上需要連接,則需要此處的file以及position信息'
mysql> show master status\G
*************************** 1. row ***************************
File: binlog.000002
Position: 1616
Binlog_Do_DB:
Binlog_Ignore_DB: mysql
Executed_Gtid_Set: f6be1c0e-30d0-11ec-afbb-000c29e0e250:1-7
1 row in set (0.00 sec)
-- 2. 接着查看master2上的信息
mysql> show master status\G
*************************** 1. row ***************************
File: binlog.000002
Position: 1459
Binlog_Do_DB:
Binlog_Ignore_DB: mysql
Executed_Gtid_Set: fe9529ac-30d0-11ec-8a3c-000c297a2694:1-6
1 row in set (0.00 sec)
-- 在master2上執行
mysql> change master to
-> master_host='192.168.15.61',
-> master_port=3306,
-> master_user='shanhe',
-> master_password='123456',
-> master_log_file='binlog.000002',
-> master_log_pos=1459;
Query OK, 0 rows affected, 2 warnings (0.00 sec)
-- 在master1上執行
mysql> change master to
-> master_host='192.168.15.62',
-> master_port=3306,
-> master_user='shanhe',
-> master_password='123456',
-> master_log_file='binlog.000002',
-> master_log_pos=1459;
Query OK, 0 rows affected, 2 warnings (0.00 sec)
3.7、查看連接結果
-- 查看masterB的信息
mysql> show slave status\G
'基本上IO線程和SQL線程都出現yes即為ok'
3.8、測試雙主
-- 1. masterA執行如下操作
mysql> create database db01;
Query OK, 1 row affected (0.00 sec)
mysql> use db01;
Database changed
mysql> create table t1(id int primary key);
Query OK, 0 rows affected (0.02 sec)
mysql> insert into t1 values(100);
Query OK, 1 row affected (0.07 sec)
mysql> select * from t1;
+-----+
| id |
+-----+
| 100 |
+-----+
1 row in set (0.00 sec)
-- 2. masterB查看t1表是否有內容
mysql> select * from db01.t1;
+-----+
| id |
+-----+
| 100 |
+-----+
1 row in set (0.00 sec)
-- 3. masterB插入一條新數據
mysql> insert into db01.t1 values(200);
Query OK, 1 row affected (0.01 sec)
-- 4. masterA查看數據是否同步完成
mysql> select * from t1;
+-----+
| id |
+-----+
| 100 |
| 200 |
+-----+
2 rows in set (0.00 sec)
'END'
題解決
#在第八步查看連接結果時,最初顯示狀態,兩個線程的狀態都為NO不可用
1. 錯誤日志並沒有error信息
2. 經過網絡上看,發現大多數是由於數據庫的uuid相同,而導致觸發此異常
3. 而我一檢查,發現我兩台數據庫uuid並不一樣,但是 !!! 本機由於前面的原因,有兩個auto.cnf文件(這兩個文件的uuid一致...)
'test02機器也是一樣的~'
[root@test01 ~]# find / -name auto.cnf
/usr/local/mysql-5.7.35/data/auto.cnf
/data/auto.cnf
4. 對比一下/etc/my.cnf,確定新的auto.cnf文件是在data目錄下
5. 刪除了舊的auto.cnf文件,重啟數據庫再次查看即為yes
3.9、雙主高可用
高可用是使用keepalived實現VIP。從而實現一個IP無感知操作兩個主節點。
1、安裝keepalived高可用軟件(#兩個節點上全都安裝)
[root@mysql-1 ~]# yum install keepalived -y
[root@mysql-2 ~]# yum install keepalived -y
2、修改keepalived的配置文件
[root@mysql-1 ~]# vim /etc/keepalived/keepalived.conf
! Configuration File for keepalived
global_defs {
router_id LVS_DEVEL
}
vrrp_script chk_kubernetes {
script "/etc/keepalived/check_kubernetes.sh"
interval 2
weight -5
fall 3
rise 2
}
vrrp_instance VI_1 {
state MASTER
interface eth0
mcast_src_ip 192.168.15.61 # 所在節點的IP
virtual_router_id 51
priority 100
advert_int 2
authentication {
auth_type PASS
auth_pass K8SHA_KA_AUTH
}
virtual_ipaddress {
192.168.15.60
}
}
# 兩台機器開啟keepalived
systemctl start keepalived
# navicat連接設置的vip進行測試