MySQL全面瓦解27:主從復制(原理 + 實踐)


概念

主從復制,是指建立一個和主數據庫完全一樣的數據庫環境(稱為從數據庫),並將主庫的操作行為進行復制的過程:將主數據庫的DDL和DML的操作日志同步到從數據庫上,

然后在從數據庫上對這些日志進行重新執行,來保證從數據庫和主數據庫的數據的一致性。

為什么要做主從復制

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

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

2、保證數據的熱備份,主庫宕機后能夠及時替換主庫,保障業務可用性。

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

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

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

讀寫比 主庫 從庫
50:50 1 1
66.6:33.3 1 2
80:20 1 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 進行安裝,也可以手動下載安裝。

手動下載的話可以點擊以下鏈接下載 Edge Docker 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: 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表,並增加了一條數據。

 

2、轉到從庫這邊,馬上就查詢到數據了 

主從同步延遲解決方案

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

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM