深刻MySQL複製(一)

本文很是詳細地介紹MySQL複製相關的內容,包括基本概念、複製原理、如何配置不一樣類型的複製(傳統複製)等等。在此文章以後,還有幾篇文章分別介紹GTID複製、半同步複製、實現MySQL的動靜分離,以及MySQL 5.7.17引入的革命性功能:組複製(MGR)。html

本文是MySQL Replication的基礎,但卻很是重要。對於MySQL複製,如何搭建它不是重點(由於簡單,網上資源很是多),如何維護它纔是重點(網上資源不集中)。如下幾個知識點是掌握MySQL複製所必備的:mysql

  1. 複製的原理
  2. 將master上已存在的數據恢復到slave上做爲基準數據
  3. 獲取正確的binlog座標
  4. 深刻理解show slave status中的一些狀態信息

本文對以上內容都作了很是詳細的說明。但願對各位初學、深刻MySQL複製有所幫助。算法

mysql replication官方手冊:https://dev.mysql.com/doc/refman/5.7/en/replication.html。sql

1.複製的基本概念和原理

mysql複製是指從一個mysql服務器(MASTER)將數據經過日誌的方式通過網絡傳送到另外一臺或多臺mysql服務器(SLAVE),而後在slave上重放(replay或redo)傳送過來的日誌,以達到和master數據同步的目的。shell

它的工做原理很簡單。首先確保master數據庫上開啓了二進制日誌,這是複製的前提數據庫

  • 在slave準備開始複製時,首先要執行change master to語句設置鏈接到master服務器的鏈接參數,在執行該語句的時候要提供一些信息,包括如何鏈接和要從哪複製binlog,這些信息在鏈接的時候會記錄到slave的datadir下的master.info文件中,之後再鏈接master的時候將不用再提供這新信息而是直接讀取該文件進行鏈接。
  • 在slave上有兩種線程,分別是IO線程和SQL線程
    • IO線程用於鏈接master,監控和接受master的binlog。當啓動IO線程成功鏈接master時,master會同時啓動一個dump線程,該線程將slave請求要複製的binlog給dump出來,以後IO線程負責監控並接收master上dump出來的二進制日誌,當master上binlog有變化的時候,IO線程就將其複製過來並寫入到本身的中繼日誌(relay log)文件中。
    • slave上的另外一個線程SQL線程用於監控、讀取並重放relay log中的日誌,將數據寫入到本身的數據庫中。以下圖所示。

站在slave的角度上看,過程以下:緩存

站在master的角度上看,過程以下(默認的異步複製模式,前提是設置了sync_binlog=1,不然binlog刷盤時間由操做系統決定):安全

因此,能夠認爲複製大體有三個步驟:服務器

  1. 數據修改寫入master數據庫的binlog中。
  2. slave的IO線程複製這些變更的binlog到本身的relay log中。
  3. slave的SQL線程讀取並從新應用relay log到本身的數據庫上,讓其和master數據庫保持一致。

從複製的機制上能夠知道,在複製進行前,slave上必須具備master上部分完整內容做爲複製基準數據。例如,master上有數據庫A,二進制日誌已經寫到了pos1位置,那麼在複製進行前,slave上必需要有數據庫A,且若是要從pos1位置開始複製的話,還必須有和master上pos1以前徹底一致的數據。若是不知足這樣的一致性條件,那麼在replay中繼日誌的時候將不知道如何進行應用而致使數據混亂。也就是說,複製是基於binlog的position進行的,複製以前必須保證position一致。(注:這是傳統的複製方式所要求的)網絡

能夠選擇對哪些數據庫甚至數據庫中的哪些表進行復制。默認狀況下,MySQL的複製是異步的。slave能夠不用一直連着master,即便中間斷開了也能從斷開的position處繼續進行復制。

MySQL 5.6對比MySQL 5.5在複製上進行了很大的改進,主要包括支持GTID(Global Transaction ID,全局事務ID)複製和多SQL線程並行重放。GTID的複製方式和傳統的複製方式不同,經過全局事務ID,它不要求複製前slave有基準數據,也不要求binlog的position一致。

MySQL 5.7.17則提出了組複製(MySQL Group Replication,MGR)的概念。像數據庫這樣的產品,必需要儘量完美地設計一致性問題,特別是在集羣、分佈式環境下。Galera就是一個MySQL集羣產品,它支持多主模型(多個master),可是當MySQL 5.7.17引入了MGR功能後,Galera的優點再也不明顯,甚至MGR能夠取而代之。MGR爲MySQL集羣中多主複製的不少問題提供了很好的方案,可謂是一項革命性的功能。

複製和二進制日誌息息相關,因此學習本章必須先有二進制日誌的相關知識。

2.複製的好處

圍繞下面的拓撲圖來分析:

主要有如下幾點好處:

1.提供了讀寫分離的能力。

replication讓全部的slave都和master保持數據一致,所以外界客戶端能夠從各個slave中讀取數據,而寫數據則從master上操做。也就是實現了讀寫分離。

須要注意的是,爲了保證數據一致性,寫操做必須在master上進行

一般說到讀寫分離這個詞,馬上就能意識到它會分散壓力、提升性能。

2.爲MySQL服務器提供了良好的伸縮(scale-out)能力。

因爲各個slave服務器上只提供數據檢索而沒有寫操做,所以"隨意地"增長slave服務器數量來提高整個MySQL羣的性能,而不會對當前業務產生任何影響。

之因此"隨意地"要加上雙引號,是由於每一個slave都要和master創建鏈接,傳輸數據。若是slave數量巨多,master的壓力就會增大,網絡帶寬的壓力也會增大。

3.數據庫備份時,對業務影響降到最低。

因爲MySQL服務器羣中全部數據都是一致的(至少幾乎是一致的),因此在須要備份數據庫的時候能夠任意中止某一臺slave的複製功能(甚至中止整個mysql服務),而後從這臺主機上進行備份,這樣幾乎不會影響整個業務(除非只有一臺slave,但既然只有一臺slave,說明業務壓力並不大,短時間內將這個壓力分配給master也不會有什麼影響)。

4.能提高數據的安全性。

這是顯然的,任意一臺mysql服務器斷開,都不會丟失數據。即便是master宕機,也只是丟失了那部分尚未傳送的數據(異步複製時纔會丟失這部分數據)。

5.數據分析再也不影響業務。

須要進行數據分析的時候,直接劃分一臺或多臺slave出來專門用於數據分析。這樣OLTP和OLAP能夠共存,且幾乎不會影響業務處理性能。

3.複製分類和它們的特性

MySQL支持兩種不一樣的複製方法:傳統的複製方式和GTID複製。MySQL 5.7.17以後還支持組複製(MGR)。

  • (1).傳統的複製方法要求複製以前,slave上必須有基準數據,且binlog的position一致。
  • (2).GTID複製方法不要求基準數據和binlog的position一致性。GTID複製時,master上只要一提交,就會當即應用到slave上。這極大地簡化了複製的複雜性,且更好地保證master上和各slave上的數據一致性。

從數據同步方式的角度考慮,MySQL支持4種不一樣的同步方式:同步(synchronous)、半同步(semisynchronous)、異步(asynchronous)、延遲(delayed)。因此對於複製來講,就分爲同步複製、半同步複製、異步複製和延遲複製。

3.1 同步複製

客戶端發送DDL/DML語句給master,master執行完畢後還須要等待全部的slave都寫完了relay log才認爲這次DDL/DML成功,而後纔會返回成功信息給客戶端。同步複製的問題是master必須等待,因此延遲較大,在MySQL中不使用這種複製方式。

例如上圖中描述的,只有3個slave全都寫完relay log並返回ACK給master後,master纔會判斷這次DDL/DML成功。

3.2 半同步複製

客戶端發送DDL/DML語句給master,master執行完畢後還要等待一個slave寫完relay log並返回確認信息給master,master才認爲這次DDL/DML語句是成功的,而後纔會發送成功信息給客戶端。半同步複製只需等待一個slave的迴應,且等待的超時時間能夠設置,超時後會自動降級爲異步複製,因此在局域網內(網絡延遲很小)使用半同步複製是可行的。

例如上圖中,只有第一個slave返回成功,master就判斷這次DDL/DML成功,其餘的slave不管複製進行到哪個階段都可有可無。

3.3 異步複製

客戶端發送DDL/DML語句給master,master執行完畢當即返回成功信息給客戶端,而無論slave是否已經開始複製。這樣的複製方式致使的問題是,當master寫完了binlog,而slave尚未開始複製或者複製還沒完成時,slave上和master上的數據暫時不一致,且此時master忽然宕機,slave將會丟失一部分數據。若是此時把slave提高爲新的master,那麼整個數據庫就永久丟失這部分數據。

3.4 延遲複製

顧名思義,延遲複製就是故意讓slave延遲一段時間再從master上進行復制。

4.配置一主一從

此處先配置默認的異步複製模式。因爲複製和binlog息息相關,若是對binlog還不熟悉,請先了解binlog,見:詳細分析二進制日誌

mysql支持一主一從和一主多從。可是每一個slave必須只能是一個master的從,不然從多個master接受二進制日誌後重放將會致使數據混亂的問題。

如下是一主一從的結構圖:

在開始傳統的複製(非GTID複製)前,須要完成如下幾個關鍵點,這幾個關鍵點指導後續複製的全部步驟

  1. 爲master和slave設定不一樣的server-id,這是主從複製結構中很是關鍵的標識號。到了MySQL 5.7,彷佛不設置server id就沒法開啓binlog。設置server id須要重啓MySQL實例。
  2. 開啓master的binlog。剛安裝並初始化的MySQL默認未開啓binlog,建議手動設置binlog且爲其設定文件名,不然默認以主機名爲基名時修改主機名後會找不到日誌文件。
  3. 最好設置master上的變量sync_binlog=1(MySQL 5.7.7以後默認爲1,以前的版本默認爲0),這樣每寫一次二進制日誌都將其刷新到磁盤,讓slave服務器能夠儘快地複製。防止萬一master的二進制日誌還在緩存中就宕機時,slave沒法複製這部分丟失的數據。
  4. 最好設置master上的redo log的刷盤變量innodb_flush_log_at_trx_commit=1(默認值爲1),這樣每次提交事務都會當即將事務刷盤保證持久性和一致性。
  5. 在slave上開啓中繼日誌relay log。這個是默認開啓的,一樣建議手動設置其文件名。
  6. 建議在master上專門建立一個用於複製的用戶,它只須要有複製權限replication slave用來讀取binlog。
  7. 確保slave上的數據和master上的數據在"複製的起始position以前"是徹底一致的。若是master和slave上數據不一致,複製會失敗。
  8. 記下master開始複製前binlog的position,由於在slave鏈接master時須要指定從master的哪一個position開始複製。
  9. 考慮是否將slave設置爲只讀,也就是開啓read_only選項。這種狀況下,除了具備super權限(mysql 5.7.16還提供了super_read_only禁止super的寫操做)和SQL線程能寫數據庫,其餘用戶都不能進行寫操做。這種禁寫對於slave來講,絕大多數場景都很是適合。

4.1 一主一從

一主一從是最簡單的主從複製結構。本節實驗環境以下:

  1. 配置master和slave的配置文件。
[mysqld]          # master
datadir=/data
socket=/data/mysql.sock
log-bin=master-bin
sync-binlog=1
server-id=100
[mysqld]       # slave
datadir=/data
socket=/data/mysql.sock
relay-log=slave-bin
server-id=111
  1. 重啓master和slave上的MySQL實例。
service mysqld restart
  1. 在master上建立複製專用的用戶。
create user 'repl'@'192.168.100.%' identified by 'P@ssword1!';
grant REPLICATION SLAVE on *.* to 'repl'@'192.168.100.%';
  1. 將slave恢復到master上指定的座標。
    這是備份恢復的內容,此處用一個小節來簡述操做過程。詳細內容見MySQL備份和恢復(一)、(二)、(三)

4.2 將slave恢復到master指定的座標

對於複製而言,有幾種狀況:

  • (1).待複製的master沒有新增數據,例如新安裝的mysql實例。這種狀況下,能夠跳過恢復這個過程。
  • (2).待複製的master上已有數據。這時須要將這些已有數據也應用到slave上,並獲取master上binlog當前的座標。只有slave和master的數據能匹配上,slave重放relay log時纔不會出錯。

第一種狀況此處不贅述。第二種狀況有幾種方法,例如使用mysqldump、冷備份、xtrabackup等工具,這其中又須要考慮是MyISAM表仍是InnoDB表。


在實驗開始以前,首先在master上新增一些測試數據,以innodb和myisam的數值輔助表爲例。

DROP DATABASE IF EXISTS backuptest;
CREATE DATABASE backuptest;
USE backuptest;

# 建立myisam類型的數值輔助表和插入數據的存儲過程
CREATE TABLE num_isam (n INT NOT NULL PRIMARY KEY) ENGINE = MYISAM ;

DROP PROCEDURE IF EXISTS proc_num1;
DELIMITER $$
CREATE PROCEDURE proc_num1 (num INT) 
BEGIN
    DECLARE rn INT DEFAULT 1 ;
    TRUNCATE TABLE backuptest.num_isam ;
    INSERT INTO backuptest.num_isam VALUES(1) ;
    dd: WHILE rn * 2 < num DO 
        BEGIN
            INSERT INTO backuptest.num_isam 
            SELECT rn + n FROM backuptest.num_isam;
            SET rn = rn * 2 ;
        END ;
    END WHILE dd;
    INSERT INTO backuptest.num_isam 
    SELECT n + rn 
    FROM backuptest.num_isam 
    WHERE n + rn <= num;
END ;
$$
DELIMITER ;

# 建立innodb類型的數值輔助表和插入數據的存儲過程
CREATE TABLE num_innodb (n INT NOT NULL PRIMARY KEY) ENGINE = INNODB ;

DROP PROCEDURE IF EXISTS proc_num2;
DELIMITER $$
CREATE PROCEDURE proc_num2 (num INT) 
BEGIN
    DECLARE rn INT DEFAULT 1 ;
    TRUNCATE TABLE backuptest.num_innodb ;
    INSERT INTO backuptest.num_innodb VALUES(1) ;
    dd: WHILE rn * 2 < num DO 
        BEGIN
            INSERT INTO backuptest.num_innodb 
            SELECT rn + n FROM backuptest.num_innodb;
            SET rn = rn * 2 ;
        END ;
    END WHILE dd;
    INSERT INTO backuptest.num_innodb 
    SELECT n + rn 
    FROM backuptest.num_innodb 
    WHERE n + rn <= num ;
END ;
$$
DELIMITER ;

# 分別向兩個數值輔助表中插入100W條數據
CALL proc_num1 (1000000) ;
CALL proc_num2 (1000000) ;

所謂數值輔助表是隻有一列的表,且這個字段的值全是數值,從1開始增加。例如上面的是從1到100W的數值輔助表。

mysql> select * from backuptest.num_isam limit 10;
+----+
| n  |
+----+
|  1 |
|  2 |
|  3 |
|  4 |
|  5 |
|  6 |
|  7 |
|  8 |
|  9 |
| 10 |
+----+

4.2.1 獲取master binlog的座標

若是master是全新的數據庫實例,或者在此以前沒有開啓過binlog,那麼它的座標位置是position=4。之因此是4而非0,是由於binlog的前4個記錄單元是每一個binlog文件的頭部信息。

若是master已有數據,或者說master之前就開啓了binlog並寫過數據庫,那麼須要手動獲取position。爲了安全以及沒有後續寫操做,必須先鎖表。

mysql> flush tables with read lock;

注意,此次的鎖表會致使寫阻塞以及innodb的commit操做。

而後查看binlog的座標。

mysql> show master status;   # 爲了排版,簡化了輸出結果
+-------------------+----------+--------------+--------+--------+
| File              | Position | Binlog_Do_DB | ...... | ...... |
+-------------------+----------+--------------+--------+--------+
| master-bin.000001 |      623 |              |        |        |
+-------------------+----------+--------------+--------+--------+

記住master-bin.000001和623。

4.2.2 備份master數據到slave上

下面給出3種備份方式以及對應slave的恢復方法。建議備份全部庫到slave上,若是要篩選一部分數據庫或表進行復制,應該在slave上篩選(篩選方式見後文篩選要複製的庫和表),而不該該在master的備份過程當中指定。

  • 方式一:冷備份直接cp。這種狀況只適用於沒有新寫入操做。嚴謹一點,只適合拷貝完成前master不能有寫入操做。
  1. 若是要複製全部庫,那麼直接拷貝整個datadir。
  2. 若是要複製的是某個或某幾個庫,直接拷貝相關目錄便可。但注意,這種冷備份的方式只適合MyISAM表和開啓了innodb_file_per_table=ON的InnoDB表。若是沒有開啓該變量,innodb表使用公共表空間,沒法直接冷備份。
  3. 若是要冷備份innodb表,最安全的方法是先關閉master上的mysql,而不是經過表鎖。

因此,若是沒有涉及到innodb表,那麼在鎖表以後,能夠直接冷拷貝。最後釋放鎖。

mysql> flush tables with read lock;
mysql> show master status;   # 爲了排版,簡化了輸出結果
+-------------------+----------+--------------+--------+--------+
| File              | Position | Binlog_Do_DB | ...... | ...... |
+-------------------+----------+--------------+--------+--------+
| master-bin.000001 |      623 |              |        |        |
+-------------------+----------+--------------+--------+--------+
shell> rsync -avz /data 192.168.100.150:/
mysql> unlock tables;

此處實驗,假設要備份的是整個實例,由於涉及到了innodb表,因此建議關閉MySQL。由於是冷備份,因此slave上也應該關閉MySQL。

# master和slave上都執行
shell> mysqladmin -uroot -p shutdown

而後將整個datadir拷貝到slave上(固然,有些文件是不用拷貝的,好比master上的binlog、mysql庫等)。

# 將master的datadir(/data)拷貝到slave的datadir(/data)
shell> rsync -avz /data 192.168.100.150:/

須要注意,在冷備份的時候,須要將備份到目標主機上的DATADIR/auto.conf刪除,這個文件中記錄的是mysql server的UUID,而master和slave的UUID必須不能一致。

而後重啓master和slave。由於重啓了master,因此binlog已經滾動了,不過此次不用再查看binlog座標,由於重啓形成的binlog日誌移動不會影響slave。

  • 方式二:使用mysqldump進行備份恢復。

這種方式簡單的多,並且對於innodb表很適用,可是slave上恢復時速度慢,由於恢復時數據全是經過insert插入的。由於mysqldump能夠進行定時點恢復甚至記住binlog的座標,因此無需再手動獲取binlog的座標。

shell> mysqldump -uroot -p --all-databases --master-data=2 >dump.db

注意,--master-data選項將再dump.db中加入change master to相關的語句,值爲2時,change master to語句是註釋掉的,值爲1或者沒有提供值時,這些語句是直接激活的。同時,--master-data會鎖定全部表(若是同時使用了--single-transaction,則不是鎖全部表,詳細內容請參見mysqldump)。

所以,能夠直接從dump.db中獲取到binlog的座標。記住這個座標。

[root@xuexi ~]# grep -i -m 1 'change master to' dump.db 
-- CHANGE MASTER TO MASTER_LOG_FILE='master-bin.000002', MASTER_LOG_POS=154;

而後將dump.db拷貝到slave上,使用mysql執行dump.db腳本便可。也能夠直接在master上遠程鏈接到slave上執行。例如:

shell> mysql -uroot -p -h 192.168.100.150 -e 'source dump.db'
  • 方式三:使用xtrabackup進行備份恢復。

這是三種方式中最佳的方式,安全性高、速度快。由於xtrabackup備份的時候會記錄master的binlog的座標,所以也無需手動獲取binlog座標。

xtrabackup詳細的備份方法見:xtrabackup

注意:master和slave上都安裝percona-xtrabackup。

以全備份爲例:

innobackupex -u root -p /backup

備份完成後,在/backup下生成一個以時間爲名稱的目錄。其內文件以下:

[root@xuexi ~]# ll /backup/2018-05-29_04-12-15
total 77872
-rw-r----- 1 root root      489 May 29 04:12 backup-my.cnf
drwxr-x--- 2 root root     4096 May 29 04:12 backuptest
-rw-r----- 1 root root     1560 May 29 04:12 ib_buffer_pool
-rw-r----- 1 root root 79691776 May 29 04:12 ibdata1
drwxr-x--- 2 root root     4096 May 29 04:12 mysql
drwxr-x--- 2 root root     4096 May 29 04:12 performance_schema
drwxr-x--- 2 root root    12288 May 29 04:12 sys
-rw-r----- 1 root root       22 May 29 04:12 xtrabackup_binlog_info
-rw-r----- 1 root root      115 May 29 04:12 xtrabackup_checkpoints
-rw-r----- 1 root root      461 May 29 04:12 xtrabackup_info
-rw-r----- 1 root root     2560 May 29 04:12 xtrabackup_logfile

其中xtrabackup_binlog_info中記錄了binlog的座標。記住這個座標。

[root@xuexi ~]# cat /backup/2018-05-29_04-12-15/xtrabackup_binlog_info 
master-bin.000002       154

而後將備份的數據執行"準備"階段。這個階段不要求鏈接mysql,所以不用給鏈接選項。

innobackupex --apply-log /backup/2018-05-29_04-12-15

最後,將/backup目錄拷貝到slave上進行恢復。恢復的階段就是向MySQL的datadir拷貝。但注意,xtrabackup恢復階段要求datadir必須爲空目錄。不然報錯:

[root@xuexi ~]# innobackupex --copy-back /backup/2018-05-29_04-12-15/
180529 23:54:27 innobackupex: Starting the copy-back operation

IMPORTANT: Please check that the copy-back run completes successfully.
           At the end of a successful copy-back run innobackupex
           prints "completed OK!".

innobackupex version 2.4.11 based on MySQL server 5.7.19 Linux (x86_64) (revision id: b4e0db5)
Original data directory /data is not empty!

因此,中止slave的mysql並清空datadir。

service mysqld stop
rm -rf /data/*

恢復時使用的模式是"--copy-back",選項後指定要恢復的源備份目錄。恢復時由於不須要鏈接數據庫,因此不用指定鏈接選項。

[root@xuexi ~]# innobackupex --copy-back /backup/2018-05-29_04-12-15/
180529 23:55:53 completed OK!

恢復完成後,MySQL的datadir的文件的全部者和屬組是innobackupex的調用者,因此須要改回mysql.mysql。

shell> chown -R mysql.mysql /data

啓動slave,並查看恢復是否成功。

shell> service mysqld start
shell> mysql -uroot -p -e 'select * from backuptest.num_isam limit 10;'
+----+
| n  |
+----+
|  1 |
|  2 |
|  3 |
|  4 |
|  5 |
|  6 |
|  7 |
|  8 |
|  9 |
| 10 |
+----+

4.3 slave開啓複製

通過前面的一番折騰,總算是把該準備的數據都準備到slave上,也獲取到master上binlog的座標(154)。如今還欠東風:鏈接master。

鏈接master時,須要使用change master to提供鏈接到master的鏈接選項,包括user、port、password、binlog、position等。

mysql> change master to 
        master_host='192.168.100.20',
        master_port=3306,
        master_user='repl',
        master_password='P@ssword1!',
        master_log_file='master-bin.000002',
        master_log_pos=154;

完整的change master to語法以下:

CHANGE MASTER TO option [, option] ...
option:
  | MASTER_HOST = 'host_name'
  | MASTER_USER = 'user_name'
  | MASTER_PASSWORD = 'password'
  | MASTER_PORT = port_num
  | MASTER_LOG_FILE = 'master_log_name'
  | MASTER_LOG_POS = master_log_pos
  | MASTER_AUTO_POSITION = {0|1}
  | RELAY_LOG_FILE = 'relay_log_name'
  | RELAY_LOG_POS = relay_log_pos
  | MASTER_SSL = {0|1}
  | MASTER_SSL_CA = 'ca_file_name'
  | MASTER_SSL_CAPATH = 'ca_directory_name'
  | MASTER_SSL_CERT = 'cert_file_name'
  | MASTER_SSL_CRL = 'crl_file_name'
  | MASTER_SSL_CRLPATH = 'crl_directory_name'
  | MASTER_SSL_KEY = 'key_file_name'
  | MASTER_SSL_CIPHER = 'cipher_list'
  | MASTER_SSL_VERIFY_SERVER_CERT = {0|1}

而後,啓動IO線程和SQL線程。能夠一次性啓動兩個,也能夠分開啓動。

# 一次性啓動、關閉
start slave;
stop slave;

# 單獨啓動
start slave io_thread;
start slave sql_thread;

至此,複製就已經能夠開始工做了。當master寫入數據,slave就會從master處進行復制。

例如,在master上新建一個表,而後去slave上查看是否有該表。由於是DDL語句,它會寫二進制日誌,因此它也會複製到slave上。

4.4 查看slave的信息

change master to後,在slave的datadir下就會生成master.info文件和relay-log.info文件,這兩個文件隨着複製的進行,其內數據會隨之更新。

4.4.1 master.info

master.info文件記錄的是IO線程相關的信息,也就是鏈接master以及讀取master binlog的信息。經過這個文件,下次鏈接master時就不須要再提供鏈接選項。

如下是master.info的內容,每一行的意義見官方手冊

[root@xuexi ~]# cat /data/master.info 
25                        # 本文件的行數
master-bin.000002         # IO線程正從哪一個master binlog讀取日誌
154                       # IO線程讀取到master binlog的位置
192.168.100.20            # master_host
repl                      # master_user
P@ssword1!                # master_password
3306                      # master_port
60                        # master_retry,slave重連master的超時時間(單位秒)
0





0
30.000

0

86400


0

4.4.2 relay-log.info

relay-log.info文件中記錄的是SQL線程相關的信息。如下是relay-log.info文件的內容,每一行的意義見官方手冊

[root@xuexi ~]# cat /data/relay-log.info 
7                   # 本文件的行數
./slave-bin.000001  # 當前SQL線程正在讀取的relay-log文件
4                   # SQL線程已執行到的relay log位置
master-bin.000002   # SQL線程最近執行的操做對應的是哪一個master binlog
154                 # SQL線程最近執行的操做對應的是master binlog的哪一個位置
0                   # slave上必須落後於master多長時間
0                   # 正在運行的SQL線程數
1                   # 一種用於內部信息交流的ID,目前值老是1

4.4.3 show slave status

在slave上執行show slave status能夠查看slave的狀態信息。信息很是多,每一個字段的詳細意義可參見官方手冊

mysql> show slave status\G
*************************** 1. row ***************************
               Slave_IO_State:        # slave上IO線程的狀態,來源於show processlist
                  Master_Host: 192.168.100.20
                  Master_User: repl
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: master-bin.000002
          Read_Master_Log_Pos: 154
               Relay_Log_File: slave-bin.000001
                Relay_Log_Pos: 4
        Relay_Master_Log_File: master-bin.000002
             Slave_IO_Running: No          # IO線程的狀態,此處爲未運行且未鏈接狀態
            Slave_SQL_Running: No          # SQL線程的狀態,此處爲未運行狀態
              Replicate_Do_DB:             # 顯式指定要複製的數據庫
          Replicate_Ignore_DB:             # 顯式指定要忽略的數據庫
           Replicate_Do_Table: 
       Replicate_Ignore_Table: 
      Replicate_Wild_Do_Table:          # 以通配符方式指定要複製的表
  Replicate_Wild_Ignore_Table: 
                   Last_Errno: 0
                   Last_Error: 
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 154
              Relay_Log_Space: 154
              Until_Condition: None     # start slave語句中指定的until條件,
                                        # 例如,讀取到哪一個binlog位置就中止
               Until_Log_File: 
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File: 
           Master_SSL_CA_Path: 
              Master_SSL_Cert: 
            Master_SSL_Cipher: 
               Master_SSL_Key: 
        Seconds_Behind_Master: NULL    # SQL線程執行過的位置比IO線程慢多少
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error: 
               Last_SQL_Errno: 0
               Last_SQL_Error: 
  Replicate_Ignore_Server_Ids: 
             Master_Server_Id: 0      # master的server id
                  Master_UUID: 
             Master_Info_File: /data/master.info
                    SQL_Delay: 0
          SQL_Remaining_Delay: NULL
      Slave_SQL_Running_State:             # slave SQL線程的狀態
           Master_Retry_Count: 86400
                  Master_Bind: 
      Last_IO_Error_Timestamp: 
     Last_SQL_Error_Timestamp: 
               Master_SSL_Crl: 
           Master_SSL_Crlpath: 
           Retrieved_Gtid_Set: 
            Executed_Gtid_Set: 
                Auto_Position: 0
         Replicate_Rewrite_DB: 
                 Channel_Name: 
           Master_TLS_Version: 
1 row in set (0.01 sec)

由於太長,後面再列出show slave status時,將裁剪一些意義不大的行。

再次回到上面show slave status的信息。除了那些描述IO線程、SQL線程狀態的行,還有幾個log_file和pos相關的行,以下所列。

Master_Log_File: master-bin.000002
  Read_Master_Log_Pos: 154
       Relay_Log_File: slave-bin.000001
        Relay_Log_Pos: 4
Relay_Master_Log_File: master-bin.000002
  Exec_Master_Log_Pos: 154

理解這幾行的意義相當重要,前面由於排版限制,描述看上去有些重複。因此這裏完整地描述它們:

  • Master_Log_File:IO線程正在讀取的master binlog;
  • Read_Master_Log_Pos:IO線程已經讀取到master binlog的哪一個位置;
  • Relay_Log_File:SQL線程正在讀取和執行的relay log;
  • Relay_Log_Pos:SQL線程已經讀取和執行到relay log的哪一個位置;
  • Relay_Master_Log_File:SQL線程最近執行的操做對應的是哪一個master binlog;
  • Exec_Master_Log_Pos:SQL線程最近執行的操做對應的是master binlog的哪一個位置。

因此,(Relay_Master_Log_File, Exec_Master_log_Pos)構成一個座標,這個座標表示slave上已經將master上的哪些數據重放到本身的實例中,它能夠用於下一次change master to時指定的binlog座標。

與這個座標相對應的是slave上SQL線程的relay log座標(Relay_Log_File, Relay_Log_Pos)。這兩個座標位置不一樣,但它們對應的數據是一致的。

最後還有一個延遲參數Seconds_Behind_Master須要說明一下,它的本質意義是SQL線程比IO線程慢多少。若是master和slave之間的網絡情況優良,那麼slave的IO線程讀速度和master寫binlog的速度基本一致,因此這個參數也用來描述"SQL線程比master慢多少",也就是說slave比master少多少數據,只不過衡量的單位是秒。但須要注意,這個參數的描述並不標準,只有在網速很好的時候作個大概估計,不少種狀況下它的值都是0,即便SQL線程比IO線程慢了不少也是如此。

4.4.4 slave信息彙總

上面的master.info、relay-log.info和show slave status的狀態都是剛鏈接上master還未啓動IO thread、SQL thread時的狀態。下面將顯示已經進行一段正在執行復制的slave狀態。

首先查看啓動io thread和sql thread後的狀態。使用show processlist查看便可。

mysql> start slave;

mysql> show processlist;   # slave上的信息,爲了排版,簡化了輸出
+----+-------------+---------+--------------------------------------------------------+
| Id | User        | Command | State                                                  |
+----+-------------+---------+--------------------------------------------------------+
|  4 | root        | Sleep   |                                                        |
|  7 | root        | Query   | starting                                               |
|  8 | system user | Connect | Waiting for master to send event                       |
|  9 | system user | Connect | Slave has read all relay log; waiting for more updates |
+----+-------------+---------+--------------------------------------------------------+

能夠看到:

  • Id=8的線程負責鏈接master並讀取binlog,它是IO 線程,它的狀態指示"等待master發送更多的事件";
  • Id=9的線程負責讀取relay log,它是SQL線程,它的狀態指示"已經讀取了全部的relay log"。

再看看此時master上的信息。

mysql> show processlist;        # master上的信息,爲了排版,通過了修改
+----+------+-----------------------+-------------+--------------------------------------+
| Id | User | Host                  | Command     | State                                |
+----+------+-----------------------+-------------+--------------------------------------+
|  4 | root | localhost             | Query       | starting                             |
|----|------|-----------------------|-------------|--------------------------------------|
| 16 | repl | 192.168.100.150:39556 | Binlog Dump | Master has sent all binlog to slave; |
|    |      |                       |             | waiting for more updates             |
+----+------+-----------------------+-------------+--------------------------------------+

master上有一個Id=16的binlog dump線程,該線程的用戶是repl。它的狀態指示"已經將全部的binlog發送給slave了"。

如今,在master上執行一個長事件,以便查看slave上的狀態信息。

仍然使用前面插入數值輔助表的存儲過程,此次分別向兩張表中插入一億條數據(儘管去抽菸、喝茶,夠等幾分鐘的。若是機器性能很差,請大幅減小插入的行數)。

call proc_num1(100000000);
call proc_num2(100000000);

而後去slave上查看信息,以下。由於太長,已經裁剪了一部分沒什麼用的行。

mysql> show slave status\G
mysql: [Warning] Using a password on the command line interface can be insecure.
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 192.168.100.20
                  Master_User: repl
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: master-bin.000003
          Read_Master_Log_Pos: 512685413
               Relay_Log_File: slave-bin.000003
                Relay_Log_Pos: 336989434
        Relay_Master_Log_File: master-bin.000003
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
          Exec_Master_Log_Pos: 336989219
      Slave_SQL_Running_State: Reading event from the relay log

從中獲取到的信息有:

  1. IO線程的狀態
  2. SQL線程的狀態
  3. IO線程讀取到master binlog的哪一個位置:512685413
  4. SQL線程已經執行到relay log的哪一個位置:336989434
  5. SQL線程執行的位置對應於master binlog的哪一個位置:336989219

能夠看出,IO線程比SQL線程超前了不少不少,因此SQL線程比IO線程的延遲較大。

4.5 MySQL複製如何實現斷開重連

不少人覺得change master to語句是用來鏈接master的,實際上這種說法是錯的。鏈接master是IO線程的事情,change master to只是爲IO線程鏈接master時提供鏈接參數。

若是slave歷來沒鏈接過master,那麼必須使用change master to語句來生成IO線程所須要的信息,這些信息記錄在master.info中。這個文件是change master to成功以後當即生成的,之後啓動IO線程時,IO線程都會自動讀取這個文件來鏈接master,不須要先執行change master to語句。

例如,能夠隨時stop slave來中止複製線程,而後再隨時start slave,只要master.info存在,且沒有人爲修改過它,IO線程就必定不會出錯。這是由於master.info會隨着IO線程的執行而更新,不管讀取到master binlog的哪一個位置,都會記錄下這個位置,如此一來,IO線程下次啓動的時候就知道從哪裏開始監控master binlog。

前面還提到一個文件:relay-log.info文件。這個文件中記錄的是SQL線程的相關信息,包括讀取、執行到relay log的哪一個位置,剛重放的數據對應master binlog的哪一個位置。隨着複製的進行,這個文件的信息會即時改變。因此,經過relay-log.info,下次SQL線程啓動的時候就能知道從relay log的哪一個地方繼續讀取、執行。

若是不當心把relay log文件刪除了,SQL線程可能會丟失了一部分相比IO線程延遲的數據。這時候,只需將relay-log.info中第四、5行記錄的"SQL線程剛重放的數據對應master binlog的座標"手動修改到master.info中便可,這樣IO線程下次鏈接master就會從master binlog的這個地方開始監控。固然,也能夠將這個座標做爲change master to的座標來修改master.info。

此外,當mysql實例啓動時,默認會自動start slave,也就是MySQL一啓動就自動開啓複製線程。若是想要禁止這種行爲,在配置文件中加上:

[mysqld]
skip-slave-start

4.6 一些變量

默認狀況下,slave鏈接到master後會在slave的datadir下生成master.info和relay-log.info文件,可是這是能夠經過設置變量來改變的。

  • master-info-repository={TABLE|FILE}:master的信息是記錄到文件master.info中仍是記錄到表mysql.slave_master_info中。默認爲file。
  • relay-log-info-repository={TABLE|FILE}:slave的信息是記錄到文件relay-log.info中仍是記錄到表mysql.slave_relay_log_info中。默認爲file。

IO線程每次從master複製日誌要寫入到relay log中,可是它是先放在內存的,等到必定時機後纔會將其刷到磁盤上的relay log文件中。刷到磁盤的時機能夠由變量控制。

另外,IO線程每次從master複製日誌後都會更新master.info的信息,也是先更新內存中信息,在特定的時候纔會刷到磁盤的master.info文件;同理SQL線程更新realy-log.info也是同樣的。它們是能夠經過變量來設置更新時機的。

  • sync-relay-log=N:設置爲大於0的數表示每從master複製N個事件就刷一次盤。設置爲0表示依賴於操做系統的sync機制。
  • sync-master-info=N:依賴於master-info-repository的設置,若是爲file,則設置爲大於0的值時表示每更新多少次master.info將其寫入到磁盤的master.info中,設置爲0則表示由操做系統來決定什麼時候調用fdatasync()函數刷到磁盤。若是設置爲table,則設置爲大於0的值表示每更新多少次master.info就更新mysql.slave_master_info表一次,若是設置爲0則表示永不更新該表。
  • sync-relay-log-info=N:同上。

5.一主多從

一主多從有兩種狀況,結構圖以下。

如下是一主多從的結構圖(和一主一從的配置方法徹底一致):

如下是一主多從,但某slave是另外一羣MySQL實例的master:

配置一主多從時,須要考慮一件事:slave上是否要開啓binlog? 若是不開啓slave的binlog,性能確定要稍微好一點。可是開啓了binlog後,能夠經過slave來備份數據,也能夠在master宕機時直接將slave切換爲新的master。此外,若是是上面第二種主從結構,這臺slave必須開啓binlog。能夠將某臺或某幾臺slave開啓binlog,並在mysql動靜分離的路由算法上稍微減小一點到這些slave上的訪問權重。

上面第一種一主多從的結構沒什麼可解釋的,它和一主一從的配置方式徹底同樣,可是能夠考慮另外一種狀況:向現有主從結構中添加新的slave。因此,稍後先介紹這種添加slave,再介紹第二種一主多從的結構。

5.1 向現有主從結構中添加slave

官方手冊:https://dev.mysql.com/doc/refman/5.7/en/replication-howto-additionalslaves.html

例如在前文一主一從的實驗環境下添加一臺新的slave。

由於新的slave在開始複製前,要有master上的基準數據,還要有master binlog的座標。按照前文一主一從的配置方式,固然很容易獲取這些信息,但這樣會將master鎖住一段時間(由於要備份基準數據)。

深刻思考一下,其實slave上也有數據,還有relay log以及一些倉庫文件標記着數據複製到哪一個地方。因此,徹底能夠從slave上獲取基準數據和座標,也建議這樣作

仍然有三種方法從slave上獲取基準數據:冷備份、mysqldump和xtrabackup。方法見前文將slave恢復到master指定的座標

其實臨時關閉一個slave對業務影響很小,因此我我的建議,新添加slave時採用冷備份slave的方式,不只備份恢復的速度最快,配置成slave也最方便,這一點和前面配置"一主一從"不同。但冷備份slave的時候須要注意幾點:

  1. 能夠考慮將slave1徹底shutdown再將整個datadir拷貝到新的slave2上。
  2. 建議新的slave2配置文件中的"relay-log"的值和slave1的值徹底一致,不然應該手動從slave2的relay-log.info中獲取IO線程鏈接master時的座標,並在slave2上使用change master to語句設置鏈接參數。
    方法很簡單,因此不作演示了。

5.2 配置一主多從(從中有從)

此處實現的一主多從是下面這種結構:

這種結構對MySQL複製來講,是一個很好的提高性能的方式。對於只有一個master的主從複製結構,每多一個slave,意味着master多發一部分binlog,業務稍微繁忙一點時,這種壓力會加重。而這種一個主master、一個或多個輔助master的主從結構,很是有助於MySQL集羣的伸縮性,對壓力的適應性也很強。

除上面一主多從、從中有從的方式可提高複製性能,還有幾種提高MySQL複製性能的方式:

  1. 將不一樣數據庫複製到不一樣slave上。
  2. 能夠將master上的事務表(如InnoDB)複製爲slave上的非事務表(如MyISAM),這樣slave上回放的速度加快,查詢數據的速度在必定程度上也會提高。

回到這種主從結構,它有些不一樣,master只負責傳送日誌給slave一、slave2和slave3,slave 2_1和slave 2_2的日誌由slave2負責傳送,因此slave2上也必需要開啓binlog選項。此外,還必須開啓一個選項--log-slave-updates讓slave2可以在重放relay log時也寫本身的binlog,不然slave2的binlog僅接受人爲的寫操做。

問:slave可否進行寫操做?重放relay log的操做是否會記錄到slave的binlog中?

  1. 在slave上沒有開啓read-only選項(只讀變量)時,任何有寫權限的用戶均可以進行寫操做,這些操做都會記錄到binlog中。注意,read-only選項對具備super權限的用戶以及SQL線程執行的重放寫操做無效。默認這個選項是關閉的。
mysql> show variables like "read_only"; 
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| read_only     | OFF   |
+---------------+-------+
  1. 在slave上沒有開啓log-slave-updates和binlog選項時,重放relay log不會記錄binlog。

因此若是slave2要做爲某些slave的master,那麼在slave2上必需要開啓log-slave-updates和binlog選項。爲了安全和數據一致性,在slave2上還應該啓用read-only選項。

環境以下:

如下是master、slave1和slave2上配置文件內容。

# master上的配置
[mysqld]
datadir=/data
socket=/data/mysql.sock
server_id=100
sync-binlog=1
log_bin=master-bin
log-error=/data/err.log
pid-file=/data/mysqld.pid

# slave1上的配置
[mysqld]
datadir=/data
socket=/data/mysql.sock
server_id=111
relay-log=slave-bin
log-error=/data/err.log
pid-file=/data/mysqld.pid

log-slave-updates          # 新增配置
log-bin=master-slave-bin   # 新增配置
read-only=ON               # 新增配置


# slave2上的配置
[mysqld]
datadir=/data
socket=/data/mysql.sock
server_id=123
relay-log=slave-bin
log-error=/data/err.log
pid-file=/data/mysqld.pid
read-only=ON

由於slave2目前是全新的實例,因此先將slave1的基準數據備份到slave2。因爲slave1自身就是slave,臨時關閉一個slave對業務影響很小,因此直接採用冷備份slave的方式。

# 在slave2上執行
shell> mysqladmin -uroot -p shutdown

# 在slave1上執行:
shell> mysqladmin -uroot -p shutdown
shell> rsync -az --delete /data 192.168.100.19:/
shell> service mysqld start

冷備份時,如下幾點千萬注意

  1. 由於slave2是slave1的從,因此在啓動MySQL前必須將備份到slave2上的和複製有關的文件都刪除。包括:
    • (1).master.info。除非配置文件中指定了skip-slave-start,不然slave2將再次鏈接到master並做爲master的slave。
    • (2).relay-log.info。由於slave1啓動後會繼續執行relay log中的內容(若是有未執行的),這時slave1會將這部分寫入binlog並傳送到slave2。
    • (3).刪除relay log文件。其實不是必須刪除,但建議刪除。
    • (4).刪除relay log index文件。
    • (5).刪除DATADIR/auto.conf。這個文件必須刪除,由於這裏面保留了mysql server的UUID,而master和slave的UUID必須不能一致。在啓動mysql的時候,若是沒有這個文件會自動生成本身的UUID並保存到auto.conf中。
  2. 檢查slave1上從master複製過來的專門用於複製的用戶repl是否容許slave2鏈接。若是不容許,應該去master上修改這個用戶。
  3. 由於slave1是剛開啓的binlog,因此slave2鏈接slave1時的binlog position應該指定爲4。即便slave1不是剛開啓的binlog,它在重啓後也會滾動binlog。

因此,在slave2上繼續操做:

shell> ls /data
auto.cnf    ib_buffer_pool  ib_logfile1  performance_schema  slave-bin.000005
backuptest  ibdata1         master.info  relay-log.info      slave-bin.index
err.log     ib_logfile0     mysql        slave-bin.000004    sys            

shell> rm -f /data/{master.info,relay-log.info,auto.conf,slave-bin*}
shell> service mysqld start

最後連上slave2,啓動複製線程。

shell> mysql -uroot -p
mysql> change master to
        master_host='192.168.100.150',
        master_port=3306,
        master_user='repl',
        master_password='P@ssword1!',
        master_log_file='master-slave-bin.000001',
        master_log_pos=4;
mysql> start slave;
mysql> show slave status\G

6.MySQL複製中一些經常使用操做

6.1 篩選要複製的庫和表

默認狀況下,slave會複製master上全部庫。能夠指定如下變量顯式指定要複製的庫、表和要忽略的庫、表,也能夠將其寫入配置文件。

Replicate_Do_DB: 要複製的數據庫
        Replicate_Ignore_DB: 不復制的數據庫
         Replicate_Do_Table: 要複製的表
     Replicate_Ignore_Table: 不復制的表
    Replicate_Wild_Do_Table: 通配符方式指定要複製的表
Replicate_Wild_Ignore_Table: 通配符方式指定不復制的表

若是要指定列表,則屢次使用這些變量進行設置。

須要注意的是,儘管顯式指定了要複製和忽略的庫或者表,可是master仍是會將全部的binlog傳給slave並寫入到slave的relay log中,真正負責篩選的slave上的SQL線程

另外,若是slave上開啓了binlog,SQL線程讀取relay log後會將全部的事件都寫入到本身的binlog中,只不過對於那些被忽略的事件只記錄相關的事務號等信息,不記錄事務的具體內容。因此,若是以前設置了被忽略的庫或表,後來取消忽略後,它們在取消忽略之前的變化是不會再重放的,特別是基於gtid的複製會嚴格比較binlog中的gtid。

總之使用篩選的時候應該多多考慮是否真的要篩選,是不是永久篩選。

6.2 reset slave和reset master

reset slave會刪除master.info/relay-log.info和relay log,而後新生成一個relay log。可是change master to設置的鏈接參數還在內存中保留着,因此此時能夠直接start slave,並根據內存中的change master to鏈接參數複製日誌。

reset slave all除了刪除reset slave刪除的東西,還刪除內存中的change master to設置的鏈接信息。

reset master會刪除master上全部的二進制日誌,並新建一個日誌。在正常運行的主從複製環境中,執行reset master極可能致使異常情況。因此建議使用purge來刪除某個時間點以前的日誌(應該保證只刪除那些已經複製完成的日誌)。

6.3 show slave hosts

若是想查看master有幾個slave的信息,可使用show slave hosts。如下爲某個master上的結果:

mysql> show slave hosts; 
+-----------+------+------+-----------+--------------------------------------+
| Server_id | Host | Port | Master_id | Slave_UUID                           |
+-----------+------+------+-----------+--------------------------------------+
|       111 |      | 3306 |        11 | ff7bb057-2466-11e7-8591-000c29479b32 |
|      1111 |      | 3306 |        11 | 9b119463-24d2-11e7-884e-000c29867ec2 |
+-----------+------+------+-----------+--------------------------------------+

能夠看到,該show中會顯示server-id、slave的主機地址和端口號、它們的master_id以及這些slave獨一無二的uuid號。

其中show結果中的host顯示結果是由slave上的變量report_host控制的,端口是由report_port控制的。

例如,在slave2上修改其配置文件,添加report-host項後重啓MySQL服務。

[mysqld]
report_host=192.168.100.19

在slave1(前文的實驗環境,slave1是slave2的master)上查看,host已經顯示爲新配置的項。

mysql> show slave hosts;
+-----------+----------------+------+-----------+--------------------------------------+
| Server_id | Host           | Port | Master_id | Slave_UUID                           |
+-----------+----------------+------+-----------+--------------------------------------+
|       111 | 192.168.100.19 | 3306 |        11 | ff7bb057-2466-11e7-8591-000c29479b32 |
|      1111 |                | 3306 |        11 | 9b119463-24d2-11e7-884e-000c29867ec2 |
+-----------+----------------+------+-----------+--------------------------------------+

6.4 多線程複製

在老版本中,只有一個SQL線程讀取relay log並重放。重放的速度確定比IO線程寫relay log的速度慢很是多,致使SQL線程很是繁忙,且實現到從庫上延遲較大沒錯,多線程複製能夠解決主從延遲問題,且使用得當的話效果很是的好(關於主從複製延遲,是生產環境下最多見的問題之一,且沒有很好的辦法來避免。後文稍微介紹了一點方法)

在MySQL 5.6中引入了多線程複製(multi-thread slave,簡稱MTS),這個多線程指的是多個SQL線程,IO線程仍是隻有一個。當IO線程將master binlog寫入relay log中後,一個稱爲"多線程協調器(multithreaded slave coordinator)"會對多個SQL線程進行調度,讓它們按照必定的規則去執行relay log中的事件。

須要謹記於心的是,若是對多線程複製沒有了解的很透徹,千萬不要在生產環境中使用多線程複製。它的確帶來了一些複製性能的提高,而且能解決主從超高延遲的問題,但隨之而來的是不少的"疑難雜症",這些"疑難雜症"並不是是bug,只是須要多多瞭解以後才知道爲什麼會出現這些問題以及如何解決這些問題。稍後會簡單介紹一種多線程複製問題:gaps。

經過全局變量slave-parallel-workers控制SQL線程個數,設置爲非0正整數N,表示多加N個SQL線程,加上原有的共N+1個SQL線程。默認爲0,表示不加任何SQL線程,即關閉多線程功能。

mysql> show variables like "%parallel%";
+------------------------+-------+
| Variable_name          | Value |
+------------------------+-------+
| slave_parallel_workers | 0     |
+------------------------+-------+

顯然,多線程只有在slave上開啓纔有效,由於只有slave上纔有SQL線程。另外,設置了該全局變量,須要重啓SQL線程才生效,不然內存中仍是隻有一個SQL線程。

例如,初始時slave上的processlist以下:

設置slave_parallel_workers=2

mysql> set @@global.slave_parallel_workers=2;
mysql> stop slave sql_thread;
msyql> start slave sql_thread;
mysql> show full processlist;

可見多出了兩個線程,其狀態信息是"Waiting for an event from Coordinator"。

雖然是多個SQL線程,可是複製時每一個庫只能使用一個線程(默認狀況下,能夠經過--slave-parallel-type修改並行策略),由於若是一個庫可使用多個線程,多個線程並行重放relay log,可能致使數據錯亂。因此應該設置線程數等於或小於要複製的庫的數量,設置多了無效且浪費資源。

6.4.1 多線程複製帶來的不一致問題

雖然多線程複製帶來了必定的複製性能提高,但它也帶來了不少問題,最嚴重的是一致性問題。完整的內容見官方手冊。此處介紹其中一個最重要的問題。

關於多線程複製,最多見也是開啓多線程複製前最須要深刻了解的問題是:因爲多個SQL線程同時執行relay log中的事務,這使得slave上提交事務的順序極可能和master binlog中記錄的順序不一致(除非指定變量slave_preserve_commit_order=1)。(注意:這裏說的是事務而不是事件。由於MyISAM的binlog順序無所謂,只要執行完了就正確,並且多線程協調器可以協調好這些任務。因此只需考慮innodb基於事務的binlog)

舉個簡單的例子,master上事務A先於事務B提交,到了slave上由於多SQL線程的緣由,可能事務B提交了事務A卻還沒提交。

是否還記得show slave status中的Exec_master_log_pos表明的意義?它表示SQL線程最近執行的事件對應的是master binlog中的哪一個位置。問題由此而來。經過show slave status,咱們看到已經執行事件對應的座標,它前面可能還有事務沒有執行。而在relay log中,事務B記錄的位置是在事務A以後的(和master同樣),因而事務A和事務B之間可能就存在一個孔洞(gap),這個孔洞是事務A剩餘要執行的操做。

正常狀況下,多線程協調器記錄了一切和多線程複製相關的內容,它能識別這種孔洞(經過打低水位標記low-watermark),也能正確填充孔洞。即便是在存在孔洞的狀況下執行stop slave也不會有任何問題,由於在中止SQL線程以前,它會等待先把孔洞填充完。但危險因素太多,好比忽然宕機、忽然殺掉mysqld進程等等,這些都會致使孔洞持續下去,甚至可能由於操做不當而永久丟失這部分孔洞。

那麼如何避免這種問題,出現這種問題如何解決?

1.如何避免gap。

前面說了,多個SQL線程是經過協調器來調度的。默認狀況下,可能會出現gap的狀況,這是由於變量slave_preserve_commit_order的默認值爲0。該變量指示協調器是否讓每一個SQL線程執行的事務按master binlog中的順序提交。所以,將其設置爲1,而後重啓SQL線程便可保證SQL線程按序提交,也就不可能會有gap的出現。

當事務B準備先於事務A提交的時候,它將一直等待。此時slave的狀態將顯示:

Waiting for preceding transaction to commit   # MySQL 5.7.8以後顯示該狀態
Waiting for its turn to commit       # MySQL 5.7.8以前顯示該狀態

儘管不會出現gap,但show slave statusExec_master_log_pos仍可能顯示在事務A的座標以後。

因爲開啓slave_preserve_commit_order涉及到很多操做,它還要求開啓slave的binlog--log-bin(所以須要重啓mysqld),且開啓重放relay log也記錄binlog的行爲--log-slave-updates,此外,還必須設置多線程的並行策略--slave-parallel-type=LOGICAL_CLOCK

shell> mysqladmin -uroot -p shutdown

shell> cat /etc/my.cnf
log_bin=slave-bin
log-slave-updates
slave_parallel_workers=1
slave_parallel_type=LOGICAL_CLOCK

shell>service mysqld start

2.如何處理已經存在的gap。

方法之一,是從master上從新備份恢復到slave上,這種方法是處理不當的最後解決辦法。

正常的處理方法是,使用START SLAVE [SQL_THREAD] UNTIL SQL_AFTER_MTS_GAPS;,它表示SQL線程只有先填充gaps後才能啓動。實際上,它涉及了兩個操做:

  • (1).填充gaps
  • (2).自動中止SQL線程(因此以後須要手動啓動SQL線程)

通常來講,在填充完gaps以後,應該先reset slave移除已經執行完的relay log,而後再去啓動sql_thread。

6.4.2 多線程複製切換回單線程複製

多線程的帶來的問題不止gaps一種,因此沒有深刻了解多線程的狀況下,千萬不能在生產環境中啓用它。若是想將多線程切換回單線程,能夠執行以下操做:

START SLAVE UNTIL SQL_AFTER_MTS_GAPS;
SET @@GLOBAL.slave_parallel_workers = 0;
START SLAVE SQL_THREAD;

6.5 slave升級爲master的大體操做

當master忽然宕機,有時候須要切換到slave,將slave提高爲新的master。但對於master忽然宕機可能形成的數據丟失,主從切換是沒法解決的,它只是儘量地不間斷提供MySQL服務。

假如如今有主服務器M,從服務器S一、S2,S1做爲未來的新的master。

  1. 在將S1提高爲master以前,須要保證S1已經將relay log中的事件已經replay完成。即下面兩個狀態查看語句中SQL線程的狀態顯示爲:"Slave has read all relay log; waiting for the slave I/O thread to update it"。
show slave status;
show processlist;
  1. 中止S1上的IO線程和SQL線程,而後將S1的binlog清空(要求已啓用binlog)。
mysql> stop slave;
mysql> reset master;
  1. 在S2上中止IO線程和SQL線程,經過change master to修改master的指向爲S1,而後再啓動io線程和SQL線程。
mysql> stop slave;
mysql> change master to master_host=S1,...
mysql> start slave;
  1. 將應用程序本來指向M的請求修改成指向S1,如修改MySQL代理的目標地址。通常會經過MySQL Router、Amoeba、cobar等數據庫中間件來實現。
  2. 刪除S1上的master.info、relay-log.info文件,不然下次S1重啓服務器會繼續以slave角色運行。
  3. 未來M從新上線後,能夠將其配置成S1的slave,而後修改應用程序請求的目標列表,添加上新上線的M,如將M加入到MySQL代理的讀目標列表。

注意:reset master很重要,若是不是基於GTID複製且開啓了log-slave-updates選項時,S1在應用relay log的時候會將其寫入到本身的binlog,之後S2會複製這些日誌致使重複執行的問題。

其實上面只是提供一種slave升級爲Master的解決思路,在實際應用中環境可能比較複雜。例如,上面的S1是S2的master,這時S1若是沒有設置爲read-only,當M宕機時,能夠不用中止S1,也不須要reset master等操做,受影響的操做僅僅只是S1一直沒法鏈接M而已,但這對業務不會有多大的影響。

相信理解了前面的內容,分析主從切換的思路應該也沒有多大問題。

6.6 指定不復制到slave上的語句

前面說的篩選要複製的庫和表能夠用於指定不復制到slave上的庫和表,但卻沒有篩選不復制到slave的語句。

但有些特殊狀況下,可能須要這種功能。例如,master上建立專門用於複製的用戶repl,這種語句其實沒有必要複製到slave上,甚至出於安全的考慮不該該複製到slave上。

可使用sql_log_bin變量對此進行設置,默認該變量的值爲1,表示全部語句都寫進binlog,從而被slave複製走。若是設置爲0,則以後的語句不會寫入binlog,從而實現"不復制某些語句到slave"上的功能。

例如:屏蔽建立repl用戶的語句。

mysql> set sql_log_bin=0;
mysql> create user repl@'%' identified by 'P@ssword1!';
mysql> grant replication slave on *.* to repl@'%';
mysql> set sql_log_bin=1;

在使用該變量時,默認是會話範圍內的變量,必定不能設置它的全局變量值,不然全部語句都將不寫binlog。

6.7 主從高延遲的解決思路

slave經過IO線程獲取master的binlog,並經過SQL線程來應用獲取到的日誌。由於各個方面的緣由,常常會出現slave的延遲(即Seconds_Behind_Master的值)很是高(動輒幾天的延遲是常見的,幾個小時的延遲已經算短的),使得主從狀態不一致。

一個很容易理解的延遲示例是:假如master串行執行一個大事務須要30分鐘,那麼slave應用這個事務也大約要30分鐘,從master提交的那一刻開始,slave的延遲就是30分鐘,更極端一點,因爲binlog的記錄時間點是在事務提交時,若是這個大事務的日誌量很大,好比要傳輸10多分鐘,那麼極可能延遲要達到40分鐘左右。並且更嚴重的是,這種延遲具備滾雪球的特性,從延遲開始,很容易致使後續加重延遲。

因此,第一個優化方式是不要在mysql中使用大事務,這是mysql主從優化的第一口訣。

在迴歸正題,要解決slave的高延遲問題,先要知道Second_Behind_Master是如何計算延遲的:SQL線程比IO線程慢多少(其本質是NOW()減去Exec_Master_Log_Pos處設置的TIMESTAMP)。在主從網絡狀態良好的狀況下,IO線程和master的binlog大多數時候都能保持一致(也便是IO線程沒有多少延遲,除非事務很是大,致使二進制日誌傳輸時間久,但mysql優化的一個最基本口訣就是大事務切成小事務),因此在這種理想狀態下,能夠認爲主從延遲說的是slave上的數據狀態比master要延遲多少。它的計數單位是秒。

1.從產生Binlog的master上考慮,能夠在master上應用group commit的功能,並設置參數binlog_group_commit_sync_delaybinlog_group_commit_sync_no_delay_count,前者表示延遲多少秒才提交事務,後者表示要堆積多少個事務以後再提交。這樣一來,事務的產生速度下降,slave的SQL線程相對就獲得緩解

2.再者從slave上考慮,能夠在slave上開啓多線程複製(MTS)功能,讓多個SQL線程同時從一個IO線程中取事務進行應用,這對於多核CPU來講是很是有效的手段。可是前面介紹多線程複製的時候說過,沒有掌握多線程複製的方方面面以前,千萬不要在生產環境中使用多線程複製,要是出現gap問題,很讓人崩潰。

3.最後從架構上考慮。主從延遲是由於slave跟不上master的速度,那麼能夠考慮對master進行節流控制,讓master的性能降低,從而變相提升slave的能力。這種方法確定是沒人用的,但確實是一種方法,提供了一種思路,好比slave使用性能比master更好的硬件。另外一種比較可取的方式是加多箇中間slave層(也就是master->slaves->slaves),讓多箇中間slave層專一於複製(也可做爲非業務的他用,好比用於備份)。

4.使用組複製或者Galera/PXC的多寫節點,此外還能夠設置相關參數,讓它們對延遲自行調整。但通常都不須要調整,由於有默認設置。

還有比較細緻的方面能夠下降延遲,好比設置爲row格式的Binlog要比statement要好,由於不須要額外執行語句,直接修改數據便可。好比master設置保證數據一致性的日誌刷盤規則(sync_binlog/innodb_flush_log_at_trx_commit設置爲1),而slave關閉binlog或者設置性能優先於數據一致性的binlog刷盤規則。再好比設置slave的隔離級別使得slave的鎖粒度放大,不會輕易鎖表(多線程複製時避免使用此方法)。還有不少方面,選擇好的磁盤,設計好分庫分表的結構等等,這些都是直接全局的,實在沒什麼必要在這裏多作解釋。

相關文章
相關標籤/搜索