工作需要,筆記之用。文章很長,倒一杯茶慢慢看。
數據庫的應用場景頗多,如 數據庫雙機同步,一主多從,多主多從,多主一從等;下文記錄多主一從的配置及測試。
大多數復制場景中是一主或者一主多從。這種拓撲用於高可用性場景,讀寫分離。主機負責寫入數據,叢集負責讀數據,橫向擴展讀取程序。但是,多主一從是寫入多個數據庫實例,最后合並成一個結果。
多主一從使得從機從各主機同步接收業務信息(transactions),這樣可以一部服務器為多個主機服務器備份,合並數據表,聯合數據。(無去重)
MySQL 版本:5.7.10
配置機器:兩主一從
1,從機配置,保證從機的倉庫都存在一個表;
[mysqld] master-info-repository=TABLE relay-log-info-repository=TABLE
確保無誤后,可以檢查一下,如下顯示則配置正常:
mysql> SHOW VARIABLES LIKE '%repository%'; +---------------------------+-------+ | Variable_name | Value | +---------------------------+-------+ | master_info_repository | TABLE | | relay_log_info_repository | TABLE | +---------------------------+-------+ 2 rows in set (0.00 sec)
接下來修改 server_id ,這里用了ip的后三位:
[mysqld] server-id=141
輸入下面代碼可以撿測:
mysql> SHOW VARIABLES WHERE VARIABLE_NAME = 'server_id'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | server_id | 141 | +---------------+-------+ 1 row in set (0.00 sec)
注意:MySQL 5.7 安裝后,請在錯誤日志里面去找密碼,如下:
# grep root error.log
2015-12-09T05:34:01.639797Z 1 [Note] A temporary password is generated for root@localhost: T<e-hd0cgI!d
接下來你肯定需要用到這個密碼,所以最好修改一下,示例改為了“password”:
ALTER USER 'root'@'localhost' IDENTIFIED BY 'password';</font>
多主一從最關鍵的工作就是保證在你的兩(或多)個源數據里面不要有相同的主鍵,特別是當你在用AUTO_INCREMENT 這一列時,如果有兩個一樣的主鍵可以想象同步到從機時數據就會紊亂。這里有一個可替代的方法作為參考。在配置時最好關掉GTID,不然在mysql initialize 初始化時會遇到一個問題,並且這些記錄會被復制到從機上去。假設你最開始啟動了GTID,配置完成后,然后你試圖在從機上復制。當你檢查主機的狀態時(輸入 SHOW MASTER STATUS), 你會看到這樣的結果,主機上138個執行記錄,現在會被復制到從機上:
mysql> SHOW MASTER STATUS\G *************************** 1. row *************************** File: mysql-bin.000002 Position: 1286 Binlog_Do_DB: Binlog_Ignore_DB: Executed_Gtid_Set: 73fdfd2a-9e36-11e5-8592-00a64151d129:1-138 1 row in set (0.00 sec)
而在從機上 SHOW SLAVE STATUS 你會看到一些錯誤:
Last_Error: Error 'Can't create database 'mysql'; database exists' on query. Default database: 'mysql'. Query: 'CREATE DATABASE mysql;
以及 RETRIEVED GTID SET 會顯示 已取回138個記錄復制到了從機:
mysql> SHOW SLAVE STATUS\G ... Retrieved_Gtid_Set: 73fdfd2a-9e36-11e5-8592-00a64151d129:1-138 ..
當然,如果配置完成前關掉了GTID那就不會有這些錯誤了。
三台機器配置完成之后,輸入 SHOW MASTER STATUS 顯示如下:
mysql> SHOW MASTER STATUS\G *************************** 1. row *************************** File: mysql-bin.000002 Position: 398 Binlog_Do_DB: Binlog_Ignore_DB: Executed_Gtid_Set: 1 row in set (0.00 sec)
到此,整個架構已經創建,下面進行一些測試。如圖,創建一個漫畫收藏數據庫:
從機
CREATE DATABASE `comicbookdb`; use comicbookdb; CREATE TABLE `comics` ( `comic_id` int(9) NOT NULL AUTO_INCREMENT, `comic_title` varchar(60) NOT NULL, `issue_number` decimal(9,0) NOT NULL, `pub_year` varchar(60) NOT NULL, `pub_month` varchar(60) NOT NULL, PRIMARY KEY (`comic_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1;
在主機和從機上都可以運行同樣的SQL 語句。(因為在主機的一些列上我們用了AUTO_INCREMENT ,你可能覺得從機上就不用AUTO_INCREMENT 了。但是因為我們不會對從機做任何寫入操作,所以你仍舊可以運行同樣的語句,之后再去修改主鍵。后文會有詳述。)
當數據從多個主機復制合並到一個從機時,復制程序將會處理 AUTO_INCREMENT 的問題。
下面在從機上創建表時對於comic_id 列時,沒有聲明 AUTO_INCREMENT出現了錯誤,
mysql> SHOW SLAVE STATUS\G...
Last_SQL_Error: Error 'Field 'comic_id' doesn't have a default value' on query. Default database: 'comicbookdb'. Query: 'INSERT INTO COMICS (comic_title, issue_number, pub_year, pub_month) VALUES('Fly Man','5','2014','03')'
...
為了處理comic_id的主鍵問題,最方便的方法就是在主機中配置 auto_increment_increment ,在my.cnf 或者是my.ini 中:
[mysqld] auto_increment_increment = 2
加這個變量需要重啟mysql服務,但是你也可以直接在命令行操作,如下:
mysql> SET @@auto_increment_increment=2; Query OK, 0 rows affected (0.00 sec)
驗證一下:
mysql> SHOW VARIABLES WHERE VARIABLES_NAME = 'auto_increment_increment'; +-----------------------------+-------+ | Variable_name | Value | +-----------------------------+-------+ | auto_increment_increment | 2 | +-----------------------------+-------+ 1 row in set (0.00 sec)
如上 每個主鍵的增量就是2,只要每個主機的初始值不同就可以了。但不可以簡單的設 0 或 1,因為如果是0的話,它會默認恢復到1,這樣會引起沖突。所以我們設一個較大的值,最低位設置為 0 和 1,代碼如下:
主機 # 1
CREATE DATABASE `comicbookdb`;
use comicbookdb;
CREATE TABLE `comics` (
`comic_id` int(9) NOT NULL AUTO_INCREMENT,
`comic_title` varchar(60) NOT NULL,
`issue_number` decimal(9,0) NOT NULL,
`pub_year` varchar(60) NOT NULL,
`pub_month` varchar(60) NOT NULL,
PRIMARY KEY (`comic_id`)
) ENGINE=InnoDB AUTO_INCREMENT=100000;
主機 # 2
CREATE DATABASE `comicbookdb`;
use comicbookdb;
CREATE TABLE `comics` (
`comic_id` int(9) NOT NULL AUTO_INCREMENT,
`comic_title` varchar(60) NOT NULL,
`issue_number` decimal(9,0) NOT NULL,
`pub_year` varchar(60) NOT NULL,
`pub_month` varchar(60) NOT NULL,
PRIMARY KEY (`comic_id`)
) ENGINE=InnoDB AUTO_INCREMENT=100001;
(這里的初始值在建表的時候設置,同樣也可以在配置里面通過 auto_increment_offset 進行設置。)
建表完畢,在所有主機上啟動 GTID。這里從機也啟動了GTID, 以防之后在這部從機之下加另一台從機。啟動GTID,需要修改配置:
[mysqld] auto_increment_increment = 2 gtid-mode = on enforce-gtid-consistency = 1
重啟每個server之后,可以檢查一下每一台主機狀態,狀態一致:
mysql> SHOW MASTER STATUS\G *************************** 1. row *************************** File: mysql-bin.000005 Position: 154 Binlog_Do_DB: Binlog_Ignore_DB: Executed_Gtid_Set: 1 row in set (0.00 sec)
接下來對所有的主從都做了一個服務器重置(reset master),非必要。重置會清空所有的binnary log ,並且新建一個二值日志。
mysql> RESET MASTER; Query OK, 0 rows affected (0.00 sec) mysql> SHOW MASTER STATUS\G *************************** 1. row *************************** File: mysql-bin.000001 Position: 154 Binlog_Do_DB: Binlog_Ignore_DB: Executed_Gtid_Set: 1 row in set (0.00 sec)
可以見到新的 binary log(mysql-bin.000001),開始位置是154. 我們往主機插入一些數據,然后再看看主機狀態。
主機 #1
mysql> INSERT INTO COMICS (comic_title, issue_number, pub_year, pub_month) VALUES('Fly Man','1','2014','01');
Query OK, 1 row affected (0.02 sec)
mysql> SHOW MASTER STATUS\G
*************************** 1. row ***************************
File: mysql-bin.000001
Position: 574
Binlog_Do_DB:
Binlog_Ignore_DB:
Executed_Gtid_Set: 63a7971c-b48c-11e5-87cf-f7b6a723ba3d:1
1 row in set (0.00 sec)
可見 插入的 GTID 是 63a7971c-b48c-11e5-87cf-f7b6a723ba3d:1 ,冒號前面部分是主機的UUID,這個信息可以在data目錄下的auto.cnf里面查到。
主機 #1
# cat auto.cnf [auto] server-uuid=63a7971c-b48c-11e5-87cf-f7b6a723ba3d
再插入一行數據:
主機 #1
mysql> INSERT INTO COMICS (comic_title, issue_number, pub_year, pub_month) VALUES('Fly Man','2','2014','02');
Query OK, 1 row affected (0.05 sec)
mysql> show master status\G
*************************** 1. row ***************************
File: mysql-bin.000001
Position: 994
Binlog_Do_DB:
Binlog_Ignore_DB:
Executed_Gtid_Set: 63a7971c-b48c-11e5-87cf-f7b6a723ba3d:1-2
1 row in set (0.00 sec)
mysql> select * from comics;
+----------+-------------+--------------+----------+-----------+
| comic_id | comic_title | issue_number | pub_year | pub_month |
+----------+-------------+--------------+----------+-----------+
| 100001 | Fly Man | 1 | 2014 | 01 |
| 100003 | Fly Man | 2 | 2014 | 02 |
+----------+-------------+--------------+----------+-----------+
2 rows in set (0.00 sec)
會發現這個數值變成了2,那么我們再在第二號主機插入兩行數據,並且查看狀態:
主機 #2
mysql> INSERT INTO COMICS (comic_title, issue_number, pub_year, pub_month) VALUES('Fly Man','3','2014','03'); mysql> INSERT INTO COMICS (comic_title, issue_number, pub_year, pub_month) VALUES('Fly Man','4','2014','04'); mysql> show master status\G *************************** 1. row *************************** File: mysql-bin.000005 Position: 974 Binlog_Do_DB: Binlog_Ignore_DB: Executed_Gtid_Set: 75e2e1dc-b48e-11e5-83bb-1438deb0d51e:1-2 1 row in set (0.00 sec) mysql> select * from comics; +----------+-------------+--------------+----------+-----------+ | comic_id | comic_title | issue_number | pub_year | pub_month | +----------+-------------+--------------+----------+-----------+ | 100002 | Fly Man | 3 | 2014 | 03 | | 100004 | Fly Man | 4 | 2014 | 04 | +----------+-------------+--------------+----------+-----------+ 2 rows in set (0.00 sec)
主機#2有不同的UUID, 這也是我們分辨GTID對應於哪一步主機。那我們現在已經有兩組GTID的會復制到從機上。當然,從機也有自己的UUID.
主機#1 跟主機#2的GTID設置:
63a7971c-b48c-11e5-87cf-f7b6a723ba3d:1-2 75e2e1dc-b48e-11e5-83bb-1438deb0d51e:1-2
在此,通常我會確認下從機是沒有運行的:
從機
mysql> show slave status\G
Empty set (0.00 sec)
不同於平時的復制,在多主復制中,你需要為每一台主機創建一個通道,為這個通道命名。這里稱之為 “master-142”(主機-142)和 “master-143”(主機143)去匹配server_id(就像IP一樣)。接下來就演示如何開啟主機#1(server_id=142)的數據復制。
從機
mysql> CHANGE MASTER TO MASTER_HOST='192.168.1.142', MASTER_USER='replicate', MASTER_PASSWORD='password', MASTER_AUTO_POSITION = 1 FOR CHANNEL 'master-142'; Query OK, 0 rows affected, 2 warnings (0.23 sec)
這里有兩個警告,可以忽略。
mysql> SHOW WARNINGS\G *************************** 1. row *************************** Level: Note Code: 1759 Message: Sending passwords in plain text without SSL/TLS is extremely insecure. *************************** 2. row *************************** Level: Note Code: 1760 Message: Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information. 2 rows in set (0.00 sec)
現在我們可以開啟從機通道“master-142”:
mysql> START SLAVE FOR CHANNEL 'master-142'; Query OK, 0 rows affected (0.03 sec)
這個命令同時啟動了SQL_THREAD 跟 IO_THREAD。將來你會考慮停止一個或者多個線程,所以這里對應有一些語法,如何指定一個需要修改的通道:
START SLAVE SQL_THREAD FOR CHANNEL 'master-142'; START SLAVE IO_THREAD FOR CHANNEL 'master-142';
也可以發一個簡單的命令“START SLAVE” 為需要執行復制操作的通道來開啟這兩個線程。從機啟動,可以見到GTID被取出並且開始寫入數據庫。
mysql> SHOW SLAVE STATUS FOR CHANNEL 'master-142'\G *************************** 1. row *************************** Slave_IO_State: Waiting for master to send event Master_Host: 192.168.1.142 ... Master_UUID: 63a7971c-b48c-11e5-87cf-f7b6a723ba3d ... Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates ... Retrieved_Gtid_Set: 63a7971c-b48c-11e5-87cf-f7b6a723ba3d:1-2 Executed_Gtid_Set: 63a7971c-b48c-11e5-87cf-f7b6a723ba3d:1-2 Auto_Position: 1 ... Channel_Name: master-142
檢查從機,可以見到相關數據:
mysql> select * from comics;
+----------+-------------+--------------+----------+-----------+
| comic_id | comic_title | issue_number | pub_year | pub_month |
+----------+-------------+--------------+----------+-----------+
| 100001 | Fly Man | 1 | 2014 | 01 |
| 100003 | Fly Man | 2 | 2014 | 02 |
+----------+-------------+--------------+----------+-----------+
2 rows in set (0.00 sec)
主機#1 完成之后,我們開始配置主機#2:
CHANGE MASTER TO MASTER_HOST='192.168.1.143', MASTER_USER='replicate', MASTER_PASSWORD='password', MASTER_AUTO_POSITION = 1 FOR CHANNEL 'master-143';
然后再次確認從機狀態:
從機
mysql> SHOW SLAVE STATUS FOR CHANNEL 'master-143'\G *************************** 1. row *************************** Slave_IO_State: Waiting for master to send event Master_Host: 192.168.1.143 ... Master_UUID: 75e2e1dc-b48e-11e5-83bb-1438deb0d51e ... Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates ... Retrieved_Gtid_Set: 75e2e1dc-b48e-11e5-83bb-1438deb0d51e:1-2 Executed_Gtid_Set: 63a7971c-b48c-11e5-87cf-f7b6a723ba3d:1-2, 75e2e1dc-b48e-11e5-83bb-1438deb0d51e:1-2, Auto_Position: 1 ... Channel_Name: master-143
我們可以見到從機已經獲取到兩個GTID,並且已經在執行。再看看數據庫表的內容,也能夠發現四行數據都已經合並到了從機:
mysql> select * from comics; +----------+-------------+--------------+----------+-----------+ | comic_id | comic_title | issue_number | pub_year | pub_month | +----------+-------------+--------------+----------+-----------+ | 100001 | Fly Man | 1 | 2014 | 01 | | 100002 | Fly Man | 3 | 2014 | 03 | | 100003 | Fly Man | 2 | 2014 | 02 | | 100004 | Fly Man | 4 | 2014 | 04 | +----------+-------------+--------------+----------+-----------+ 4 rows in set (0.01 sec)
復制過程處理了自增 AUTO_INCREMENT 的值。如果檢查一下sql語句執行復制的原話,你會有所發現:
INSERT INTO COMICS (comic_title, issue_number, pub_year, pub_month) VALUES('Fly Man','1','2014','01') INSERT INTO COMICS (comic_title, issue_number, pub_year, pub_month) VALUES('Fly Man','2','2014','02'); INSERT INTO COMICS (comic_title, issue_number, pub_year, pub_month) VALUES('Fly Man','3','2014','03'); INSERT INTO COMICS (comic_title, issue_number, pub_year, pub_month) VALUES('Fly Man','4','2014','04');
在主機上生成的自增值會隨着聲明傳遞給從機。檢查主機上的binary log, 來到comic_id 這一列 “SET INSERT_ID = 100001”,整段會隨着sql語句一起傳給從機;
從機
# mysqlbinlog mysql-bin.000001 ... # at 349 #160106 21:08:01 server id 142 end_log_pos 349 CRC32 0x48fb16a2 Intvar SET INSERT_ID=100001/*!*/; #160106 21:08:01 server id 142 end_log_pos 543 CRC32 0xbaf55210 Query thread_id=1exec_time=0 error_code=0 use `comicbookdb`/*!*/; SET TIMESTAMP=1452132481/*!*/; INSERT INTO COMICS (comic_title, issue_number, pub_year, pub_month) VALUES('Fly Man','1','2014','01') /*!*/; ...
整個講解完畢,請大家多多指教。
測試結果:
還有一個方法,利用MyISAM的簡單文件存儲的特點來實現,可以參考鏈接:http://blog.csdn.net/lmocm/article/details/42125319
參考:
1,MySQL 5.7 Multi-Source Replication – Automatically Combining Data From Multiple Databases Into One
https://mysqlhighavailability.com/mysql-5-7-multi-source-replication-automatically-combining-data-from-multiple-databases-into-one/
2, MySQL Multi-Source Replication Overview
https://dev.mysql.com/doc/refman/5.7/en/replication-multi-source-overview.html
3, MySQL多主一從的實現(MyISAM的簡單文件存儲)
http://blog.csdn.net/lmocm/article/details/42125319