天天看點

深入解析MySQL:主從複制(原理 + 實踐)

作者:JAVA後端架構
深入解析MySQL:主從複制(原理 + 實踐)

概念

主從複制,是指建立一個和主資料庫完全一樣的資料庫環境(稱為從資料庫),并将主庫的操作行為進行複制的過程:将主資料庫的DDL和DML的記錄檔同步到從資料庫上,

然後在從資料庫上對這些日志進行重新執行,來保證從資料庫和主資料庫的資料的一緻性。

為什麼要做主從複制

1、在複雜的業務操作中,經常會有操作導緻鎖行甚至鎖表的情況,如果讀寫不解耦,會很影響運作中的業務,使用主從複制,讓主庫負責寫,從庫負責讀。

即使主庫出現了鎖表的情景,通過讀從庫也可以保證業務的正常運作。

2、保證資料的熱備份,主庫當機後能夠及時替換主庫,保障業務可用性。

3、架構的演進:業務量擴大,I/O通路頻率增高,單機無法滿足,主從複制可以做多庫方案,降低磁盤I/O通路的頻率,提高單機的I/O性能。

4、本質上也是分治理念,主從複制、讀寫分離即是壓力分拆的過程。

5、讀寫比也影響整個拆分方式,讀寫比越高,主從庫比例應越高,才能保證讀寫的均衡,才能保證較好的運作性能。讀寫比下的主從配置設定方法下:

深入解析MySQL:主從複制(原理 + 實踐)

主從複制的原理

當在從庫上啟動複制時,首先建立I/O線程連接配接主庫,主庫随後建立Binlog Dump線程讀取資料庫事件并發送給I/O線程,I/O線程擷取到事件資料後更新到從庫的中繼日志Relay Log中去,之後從庫上的SQL線程讀取中繼日志Relay Log中更新的資料庫事件并應用,

如下圖所示:

深入解析MySQL:主從複制(原理 + 實踐)

細化一下有如下幾個步驟:

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 進行安裝,也可以手動下載下傳安裝。

手動下載下傳的話可以點選以下連結下載下傳 Edge Docker for Mac。

如同Mac OS 其它軟體一樣,安裝也非常簡單,輕按兩下下載下傳的 .dmg 檔案,然後将鲸魚圖示拖拽到 Application 檔案夾即可。

深入解析MySQL:主從複制(原理 + 實踐)

從應用中找到 Docker 圖示并點選運作。去Docker Hub上去注冊一個Docker ID,進行登入即可。

深入解析MySQL:主從複制(原理 + 實踐)

打開終端,可以檢視Docker相關的版本及相關資訊。可以輸入如下資訊:

1 # 檢視docker版本
2 docker --version
3 # 檢視docker基本資訊
4 docker info            
深入解析MySQL:主從複制(原理 + 實踐)

搭建主從伺服器

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執行個體。

深入解析MySQL:主從複制(原理 + 實踐)

docker容器是互相獨立的,每個容器有其獨立的ip,是以不同容器使用相同的端口并不會沖突。這裡我們應該盡量使用mysql預設的3306端口,否則可能會出現無法通過ip連接配接docker容器内mysql的問題。

因為容器内的3306端口映射的對外端口是3307,是以外部主機可以通過 主控端ip:3307 通路到MySQL的服務,而實際是通路容器内MySQL的3306端口,密碼就是我們前面設定好的123456。

深入解析MySQL:主從複制(原理 + 實踐)

登入完之後可以看到一個空庫,這個就是我們在docker裡面拉的MySQL鏡像。

3、同樣的道理,我們繼續建一個slave(從庫)的MySQL執行個體。注意名稱為slave,端口為3308

1 docker run -p 3308:3306 --name slave -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7            
深入解析MySQL:主從複制(原理 + 實踐)

4、主從都搭建完成之後,我們可以使用 docker ps 來檢視目前運作的容器資訊,如下:

深入解析MySQL:主從複制(原理 + 實踐)

注意下第一個參數為 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: No            

SlaveIORunning 和 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表,并增加了一條資料。

深入解析MySQL:主從複制(原理 + 實踐)

2、轉到從庫這邊,馬上就查詢到資料了

深入解析MySQL:主從複制(原理 + 實踐)

主從同步延遲解決方案

最近部門在招人,面試Java程式員的時候在資料庫部分問的最頻繁的問題就是這個,問題不難,不過很多候選人同學沒辦法比較完整的回答,跟今天的标題有點相關,我就放上來。

按照我們這邊的要求,主從庫同步應該是近實時的,極端情況下也不應該超過8s,如果超過,我們認為是有問題的。

  • 保證資料庫處在最有狀态下:優化系統配置(連結層或者存儲引擎層):最大連接配接數、允許錯誤數、允許逾時時間、pool_size、log_size,保證記憶體、CPU、存儲空間的擴容(硬體部分)。
  • 業務量不多的情況下,不做讀寫分離。既然主從延遲是由于從庫同步寫庫不及時引起的,那我們也可以在有主從延遲的地方改變讀庫方式,由原來的讀從庫改為讀主庫。當然這也會增加代碼的一些邏輯複雜性。(部分業務讀主庫)
  • 假如你的業務時間允許,你可以在寫入主庫的時候,確定資料都同步到從庫了之後才傳回這條資料寫入成功,當然如果有多個從庫,你也必須確定每個從庫都寫入成功。顯然,這個方案對性能和時間的消耗是極大的,不推薦。
  • 可以引入redis或者其他nosql資料庫來存儲我們經常會産生主從延遲的業務資料。當我在寫入資料庫的同時,我再寫入一份到redis中。我們可以先去檢視redis中是否有這個資料,如果有我們就可以直接從redis中讀取這個資料。當資料真正同步到資料庫中的時候,再從redis中把資料删除。
  • 任何的伺服器都是有吞吐量的限制的,沒有任何一個方案可以無限制的承載使用者的大量流量。是以我們必須估算好我們的伺服器能夠承載的流量上限是多少。達到這個上限之後,就要采取緩存,限流,降級的方式來應對我們的流量。這也是應對主從延遲的根本處理辦法。
  • 如果系統流量确實龐大,單純的讀寫分離已經無法解決問題了,那麼就應該對資料庫進一步治理,垂直分區和水準分區是不錯的方式,下一章我們會詳細說說。

為幫助開發者們提升面試技能、有機會入職BATJ等大廠公司,特别制作了這個專輯——這一次整體放出。

大緻内容包括了: Java 集合、JVM、多線程、并發程式設計、設計模式、Spring全家桶、Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、MongoDB、Redis、MySQL、RabbitMQ、Kafka、Linux、Netty、Tomcat等大廠面試題等、等技術棧!

深入解析MySQL:主從複制(原理 + 實踐)

歡迎大家關注公衆号【Java爛豬皮】,回複【666】,擷取以上最新Java後端架構VIP學習資料以及視訊學習教程,然後一起學習,一文在手,面試我有。

每一個專欄都是大家非常關心,和非常有價值的話題,如果我的文章對你有所幫助,還請幫忙點贊、好評、轉發一下,你的支援會激勵我輸出更高品質的文章,非常感謝!

深入解析MySQL:主從複制(原理 + 實踐)

繼續閱讀