MySQL全面瓦解27:主從複製(原理 + 實踐)
概念
主從複製,是指建立一個和主資料庫完全一樣的資料庫環境(稱為從資料庫),並將主庫的操作行為進行復制的過程:將主資料庫的DDL和DML的操作日誌同步到從資料庫上,
然後在從資料庫上對這些日誌進行重新執行,來保證從資料庫和主資料庫的資料的一致性。
為什麼要做主從複製
1、在複雜的業務操作中,經常會有操作導致鎖行甚至鎖表的情況,如果讀寫不解耦,會很影響執行中的業務,使用主從複製,讓主庫負責寫,從庫負責讀。
即使主庫出現了鎖表的情景,通過讀從庫也可以保證業務的正常執行。
2、保證資料的熱備份,主庫宕機後能夠及時替換主庫,保障業務可用性。
3、架構的演進:業務量擴大,I/O訪問頻率增高,單機無法滿足,主從複製可以做多庫方案,降低磁碟I/O訪問的頻率,提高單機的I/O效能。
4、本質上也是分治理念,主從複製、讀寫分離即是壓力分拆的過程。
主從複製的原理
當在從庫上啟動複製時,首先建立I/O執行緒連線主庫,主庫隨後建立Binlog Dump執行緒讀取資料庫事件併發送給I/O執行緒,I/O執行緒獲取到事件資料後更新到從庫的中繼日誌Relay Log中去,之後從庫上的SQL執行緒讀取中繼日誌Relay Log中更新的資料庫事件並應用,
如下圖所示:
細化一下有如下幾個步驟:
1、MySQL主庫在事務提交時把資料變更(insert、delet、update)作為事件日誌記錄在二進位制日誌表(binlog)裡面。
2、主庫上有一個工作執行緒 binlog dump thread,把binlog的內容傳送到從庫的中繼日誌relay log中。
3、從庫根據中繼日誌relay log重做資料變更操作,通過邏輯複製來達到主庫和從庫的資料一致性。
4、MySQL通過三個執行緒來完成主從庫間的資料複製,其中binlog dump執行緒跑在主庫上,I/O執行緒和SQL執行緒跑在從庫上。擁有多個從庫的主庫會為每一個連線到主庫的從庫建立一個binlog dump執行緒。
搭建主從例項
我們這邊在個人PC機上進行MySQL主從複製的搭建測試,所以使用Docker會更方便。有如下優勢:
1、可以節省資源。
2、相對於Docker來說,虛擬機器搭建對機器配置有要求,且安裝MySQL步驟繁瑣。
3、一臺機器上可以執行多個Docker容器,所以我們可以部署多個MySQL服務。
4、Docker容器之間相互獨立,有獨立ip,互不衝突
5、Docker使用步驟簡便,啟動容器在秒級別。
Daocker安裝
我這邊是mac機,以此作為示範。可以用Homebrew 進行安裝,也可以手動下載安裝。
手動下載的話可以點選以下連結下載EdgeDocker for Mac。
如同Mac OS 其它軟體一樣,安裝也非常簡單,雙擊下載的 .dmg 檔案,然後將鯨魚圖示拖拽到 Application 資料夾即可。
從應用中找到 Docker 圖示並點選執行。去Docker Hub上去註冊一個Docker ID,進行登入即可。
開啟終端,可以檢視Docker相關的版本及相關資訊。可以輸入如下資訊:
1 # 檢視docker版本 2 docker --version 3 # 檢視docker基本資訊 4 docker info
搭建主從伺服器
1、拉取docker的MySQL映象,這邊以5.7的版本為準:
1 docker pull mysql:5.7
2、使用此映象啟動主庫容器:
1 docker run -p 3307:3306 --name master -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7
從這邊可以看出,Master主庫容器對外對映的埠是3307,賬號預設root,密碼是123456,執行完之後,docker中運行了一個名為master的MySQL例項。
docker容器是相互獨立的,每個容器有其獨立的ip,所以不同容器使用相同的埠並不會衝突。這裡我們應該儘量使用mysql預設的3306埠,否則可能會出現無法通過ip連線docker容器內mysql的問題。
因為容器內的3306埠對映的對外埠是3307,所以外部主機可以通過 宿主機ip:3307 訪問到MySQL的服務,而實際是訪問容器內MySQL的3306埠,密碼就是我們前面設定好的123456。
登入完之後可以看到一個空庫,這個就是我們在docker裡面拉的MySQL映象。
3、同樣的道理,我們繼續建一個slave(從庫)的MySQL例項。注意名稱為slave,埠為3308
1 docker run -p 3308:3306 --name slave -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7
4、主從都搭建完成之後,我們可以使用 docker ps來檢視當前執行的容器資訊,如下:
注意下第一個引數為 container id,是我們後續進入服務進行配置的容器識別編號。
配置主伺服器(Master)
首先,進入到 Master 伺服器。
1 wengzhihua@B000000147796DS ~ % docker exec -it 777fe9ce7f9d /bin/bash 2 root@777fe9ce7f9d:/#
這邊注意:777fe9ce7f9d 是 master 的 container id,然後檢視MySQL的狀態。
1 root@777fe9ce7f9d:/# service mysql status 2 [info] MySQL Community Server 5.7.35 is running.
然後到MySQL的目錄下去修改配置,切換到/etc/mysql目錄下,然後vi my.cnf對my.cnf進行編輯:
1 root@777fe9ce7f9d:/# cd /etc/mysql 2 root@777fe9ce7f9d:/etc/mysql# vi my.cnf 3 bash: vi: command not found
此時會報出bash: vi: command not found,需要我們在docker容器內部自行安裝vim。我們使用apt-get install vim命令來安裝vim
1 root@777fe9ce7f9d:/etc/mysql# apt-get install vim 2 Reading package lists... Done 3 Building dependency tree 4 Reading state information... Done 5 E: Unable to locate package vim
這邊又提示Unable to locate package vim。
執行apt-get update,然後再次執行apt-get install vim即可成功安裝vim。
然後我們就可以使用vim編輯my.cnf,在my.cnf中新增如下配置:
1 [mysqld] 2 ## 設定server_id,一般設定為IP,同一區域網內使用唯一值即可,注意要保證唯一,這邊我們暫且使用主庫的對映埠,方便識別 3 server_id=3307 4 ## 複製過濾:也就是指定哪個資料庫不用同步(mysql庫一般不同步) 5 binlog-ignore-db=mysql 6 ## 開啟二進位制日誌功能,可以隨便取,最好有含義(關鍵就是這裡了) 7 log-bin=test-mysql-bin 8 ## 為每個session 分配的記憶體,在事務過程中用來儲存二進位制日誌的快取 9 binlog_cache_size=1M 10 ## 主從複製的格式(mixed,statement,row,預設格式是statement) 11 binlog_format=mixed 12 ## 二進位制日誌自動刪除/過期的天數。預設值為0,表示不自動刪除。 13 expire_logs_days=7 14 ## 跳過主從複製中遇到的所有錯誤或指定型別的錯誤,避免slave端複製中斷。 15 ## 如:1062錯誤是指一些主鍵重複,1032錯誤是因為主從資料庫資料不一致 16 slave_skip_errors=1062
配置完成之後重啟服務:
1 service mysql restart
這個命令會使得容器停止,重新啟動就可以了。
接下來建立資料同步使用者:
1 root@777fe9ce7f9d:/etc/mysql# mysql -u root -p 2 Enter password: 3 Welcome to the MySQL monitor. Commands end with ; or \g. 4 Your MySQL connection id is 2 5 Server version: 5.7.35-log MySQL Community Server (GPL) 6 7 Copyright (c) 2000, 2021, Oracle and/or its affiliates. 8 9 Oracle is a registered trademark of Oracle Corporation and/or its 10 affiliates. Other names may be trademarks of their respective 11 owners. 12 13 Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. 14 15 mysql> CREATE USER 'slave'@'%' IDENTIFIED BY '123456'; 16 Query OK, 0 rows affected (0.02 sec) 17 18 mysql> GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'slave'@'%'; 19 Query OK, 0 rows affected (0.00 sec)
這裡主要是要授予使用者 slave,REPLICATION SLAVE許可權和REPLICATION CLIENT許可權,用於同步資料。
配置從伺服器(Slave)
同主伺服器一樣的操作方式,先進入MySQL配置檔案
1 wengzhihua@B000000147796DS ~ % docker exec -it d4ba5e063deb /bin/bash 2 root@d4ba5e063deb:/# cd etc/mysql 3 root@d4ba5e063deb:/etc/mysql# vi my.cnf
然後配置從庫的資訊:
1 [mysqld] 2 ## 設定server_id,一般設定為IP,注意要唯一,這邊我們使用從庫的對映埠,方便識別 3 server_id=3308 4 ## 複製過濾:也就是指定哪個資料庫不用同步(mysql庫一般不同步) 5 binlog-ignore-db=mysql 6 ## 開啟二進位制日誌功能,以備Slave作為其它Slave的Master時使用 7 log-bin=test-mysql-slave1-bin 8 ## 為每個session 分配的記憶體,在事務過程中用來儲存二進位制日誌的快取 9 binlog_cache_size=1M 10 ## 主從複製的格式(mixed,statement,row,預設格式是statement) 11 binlog_format=mixed 12 ## 二進位制日誌自動刪除/過期的天數。預設值為0,表示不自動刪除。 13 expire_logs_days=7 14 ## 跳過主從複製中遇到的所有錯誤或指定型別的錯誤,避免slave端複製中斷。 15 ## 如:1062錯誤是指一些主鍵重複,1032錯誤是因為主從資料庫資料不一致 16 slave_skip_errors=1062 17 ## relay_log配置中繼日誌 18 relay_log=edu-mysql-relay-bin 19 ## log_slave_updates表示slave將複製事件寫進自己的二進位制日誌 20 log_slave_updates=1 21 ## 防止改變資料(除了特殊的執行緒) 22 read_only=1
配置完成之後重啟服務 service mysql restart:
1 root@d4ba5e063deb:/etc/mysql# service mysql restart 2 [info] Stopping MySQL Community Server 5.7.35. 3 ... 4 [info] MySQL Community Server 5.7.35 is stopped. 5 [info] Re-starting MySQL Community Server 5.7.35.
跟上面一樣,這個命令會使得容器停止,重新啟動就可以了。
完成Master和Slave的連線
注意,需要保證 Master 和 Slave 除了不同步的資料庫,其他資料庫的資料要一致。
1、在 Master 進入 MySQL, 然後執行命令:
1 mysql> show master status; 2 +----------------------+----------+--------------+------------------+-------------------+ 3 | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set | 4 +----------------------+----------+--------------+------------------+-------------------+ 5 | test-mysql-bin.000001 | 617 | | mysql | | 6 +----------------------+----------+--------------+------------------+-------------------+ 7 1 row in set (0.00 sec)
記錄下 File 和 Position 欄位的值,後面會用到。
2、然後再查詢一下主從兩個容器所對應的IP,主庫對應 172.17.0.2,從庫對應172.17.0.3:
1 wengzhihua@B000000147796DS ~ % docker ps 2 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 3 d4ba5e063deb mysql:5.7 "docker-entrypoint.s…" 5 hours ago Up 19 minutes 33060/tcp, 0.0.0.0:3308->3306/tcp, :::3308->3306/tcp slave 4 777fe9ce7f9d mysql:5.7 "docker-entrypoint.s…" 5 hours ago Up 51 minutes 33060/tcp, 0.0.0.0:3307->3306/tcp, :::3307->3306/tcp master 5 wengzhihua@B000000147796DS ~ % docker inspect --format='{{.NetworkSettings.IPAddress}}' 777fe9ce7f9d 6 172.17.0.2 7 wengzhihua@B000000147796DS ~ % docker inspect --format='{{.NetworkSettings.IPAddress}}' d4ba5e063deb 8 172.17.0.3
3、然後到 Slave 中進入 mysql,執行命令:
1 mysql> change master to master_host='172.17.0.2', master_user='slave', master_password='123456', master_port=3306, master_log_file='test-mysql-bin.000001', master_log_pos=617, master_connect_retry=30; 2 Query OK, 0 rows affected, 2 warnings (0.02 sec)
大意就是在從庫使用什麼賬號(slave)進行同步,同步的主庫的IP(master_host)、密碼(master_password)、埠(master_port)、binlog日誌檔案(master_log_file)以及其實同步的位置(master_log_pos)等,
理解下這個命令的各個引數:
1 master_host: Master 的IP地址 2 master_user: 在 Master 中授權的用於資料同步的使用者,就我們之前在主庫容器裡建立的哪個slave使用者 3 master_password: 同步資料的使用者的密碼 4 master_port: Master 的資料庫的埠號,注意,這邊是3306,不是3307,3307是對映外部宿主主機的,寫成3307,會造成 Slave_IO_Running 一直是 Connecting 狀態,在這邊踩坑了 5 master_log_file: 指定 Slave 從哪個日誌檔案開始複製資料,即上文中提到的 File 欄位的值 6 master_log_pos: 從哪個 Position 開始讀,即上文中提到的 Position 欄位的值 7 master_connect_retry: 當重新建立主從連線時,如果連線失敗,重試的時間間隔,單位是秒,預設是60秒。
在 Slave 的 MySQL 終端執行檢視主從同步狀態
1 mysql> show slave status \G; 2 *************************** 1. row *************************** 3 Slave_IO_State: 4 Master_Host: 172.17.0.2 5 Master_User: slave 6 Master_Port: 3307 7 Connect_Retry: 30 8 Master_Log_File: edu-mysql-bin.000001 9 Read_Master_Log_Pos: 617 10 Relay_Log_File: edu-mysql-relay-bin.000001 11 Relay_Log_Pos: 4 12 Relay_Master_Log_File: edu-mysql-bin.000001 13 Slave_IO_Running: No 14 Slave_SQL_Running: No 15 Replicate_Do_DB: 16 Replicate_Ignore_DB: 17 Replicate_Do_Table: 18 Replicate_Ignore_Table: 19 Replicate_Wild_Do_Table: 20 Replicate_Wild_Ignore_Table: 21 Last_Errno: 0 22 Last_Error: 23 Skip_Counter: 0 24 Exec_Master_Log_Pos: 617 25 Relay_Log_Space: 154 26 Until_Condition: None 27 Until_Log_File: 28 Until_Log_Pos: 0 29 Master_SSL_Allowed: NoSlaveIORunning 和 SlaveSQLRunning 是No,表明 Slave 還沒有開始複製過程。相反 SlaveIORunning 和 SlaveSQLRunning 是Yes表明已經開始工作了。 執行一下命令,開始啟動主從同步, Slave_IO_Running: Connecting, Slave_SQL_Running: Yes。
1 start slave;
1 mysql> show slave status \G; 2 *************************** 1. row *************************** 3 Slave_IO_State: Waiting for master to send event 4 Master_Host: 172.17.0.2 5 Master_User: slave 6 Master_Port: 3306 7 Connect_Retry: 30 8 Master_Log_File: edu-mysql-bin.000001 9 Read_Master_Log_Pos: 2351 10 Relay_Log_File: edu-mysql-relay-bin.000006 11 Relay_Log_Pos: 1259 12 Relay_Master_Log_File: edu-mysql-bin.000001 13 Slave_IO_Running: Yes 14 Slave_SQL_Running: Yes 15 Replicate_Do_DB: 16 Replicate_Ignore_DB: 17 Replicate_Do_Table: 18 Replicate_Ignore_Table: 19 Replicate_Wild_Do_Table: 20 Replicate_Wild_Ignore_Table: 21 Last_Errno: 0 22 Last_Error: 23 Skip_Counter: 0 24 Exec_Master_Log_Pos: 2351 25 Relay_Log_Space: 1640 26 Until_Condition: None
驗證同步是否成功:
1、我們在主服務這邊建立一個test庫,庫下面建立了一個person表,並增加了一條資料。
2、轉到從庫這邊,馬上就查詢到資料了
主從同步延遲解決方案
最近部門瘋狂招人,面試Java程式設計師的時候在資料庫部分問的最頻繁的問題就是這個,問題不難,不過很多候選人同學沒辦法比較完整的回答,跟今天的標題有點相關,我就蠻放上來。
按照我們這邊的要求,主從庫同步應該是近實時的,極端情況下也不應該超過8s,如果超過,我們認為是有問題的。
1、保證資料庫處在最有狀態下:優化系統配置(連結層或者儲存引擎層):最大連線數、允許錯誤數、允許超時時間、pool_size、log_size,保證記憶體、CPU、儲存空間的擴容(硬體部分)。 2、業務量不多的情況下,不做讀寫分離。既然主從延遲是由於從庫同步寫庫不及時引起的,那我們也可以在有主從延遲的地方改變讀庫方式,由原來的讀從庫改為讀主庫。當然這也會增加程式碼的一些邏輯複雜性。(部分業務讀主庫) 3、假如你的業務時間允許,你可以在寫入主庫的時候,確保資料都同步到從庫了之後才返回這條資料寫入成功,當然如果有多個從庫,你也必須確保每個從庫都寫入成功。顯然,這個方案對效能和時間的消耗是極大的,不推薦。 4、可以引入redis或者其他nosql資料庫來儲存我們經常會產生主從延遲的業務資料。當我在寫入資料庫的同時,我再寫入一份到redis中。我們可以先去檢視redis中是否有這個資料,如果有我們就可以直接從redis中讀取這個資料。當資料真正同步到資料庫中的時候,再從redis中把資料刪除。 5、任何的伺服器都是有吞吐量的限制的,沒有任何一個方案可以無限制的承載使用者的大量流量。所以我們必須估算好我們的伺服器能夠承載的流量上限是多少。達到這個上限之後,就要採取快取,限流,降級的方式來應對我們的流量。這也是應對主從延遲的根本處理辦法。 6、如果系統流量確實龐大,單純的讀寫分離已經無法解決問題了,那麼就應該對資料庫進一步治理,垂直分割槽和水平分割槽是不錯的方式,下一章我們會詳細說說。 碼字不易,歡迎關注,歡迎轉載 作者:翁智華 出處:https://www.cnblogs.com/wzh2010/ 本文采用「CC BY 4.0」知識共享協議進行許可,轉載請註明作者及出處。