寫在前面:2020年面試必備的Java後端進階面試題總結了一份複習指南在Github上,內容詳細,圖文並茂,有需要學習的朋友可以Star一下!
GitHub地址:https://github.com/abel-max/Java-Study-Note/tree/master
今天我們來深挖一下mysql的複製機制到底有哪一些,以及binlog和relay-log的結構到底是什麼樣子的。
binlog作用
binlog的主要作用是記錄數據庫中表的更改,它只記錄改變數據的sql,不改變數據的sql不會寫入,比如select語句一般不會被記錄,因爲他們不會對數據產生任何改動。
用一個實際的場景看下binlog產生的過程,準備sql:
create table test(text varchar(20));
insert into test values ('test_text');
select * from test;
flush logs;
查看binlog
show binlog events in 'binlog.000029';
顯示的結果如下:
binlog
另外,也可以使用mysqlbinlog工具來查看binlog的內容:
show variables like 'log_%'; #查看日誌目錄
mysqlbinlog --short-form --force-if-open --base64-output=never /usr/local/var/mysql/binlog.000029
從日誌我們可以看到執行了創建表的語句以及一個Format_desc頭和Ratate輪換事件,這個我們會在後面講到,先看幾個字段代表的含義。
Log_name代表日誌文件的名稱,比如我這裏的查詢是直接查詢binlog.000029,默認的寫法是show binlog events,但是這樣只會查詢到第一個binlog,並不是當前激活狀態的binlog,如果你不知道binlog有哪些,可以用命令:
show binary logs; #查看binlog列表
show master status; #查看最新的binlog
Pos代表文件開始的位置。
Event_type代表事件的類型。
Server_id是創建事件的服務器ID。
End_log_pos代表事件在文件中的結束位置,以上面爲例,第一次查詢的結束位置是723,第二次insert之後文件的開始位置就是從723開始。
Info代表事件信息,是一段可讀的文本內容。
binlog日誌結構
binlog日誌的結構大概是長這樣的,它由索引文件和binlog文件組成,其中binlog事件又包含通用頭、提交頭和事件體3個部分組成。
首先說說索引文件,索引文件的每一行都包含了一個binlog文件的完整文件名(類似host-bin.001),一些命令比如flush logs將所有日誌寫入磁盤會影響到索引文件。
每個binlog文件以若干個binlog事件組成,以格式描述事件(Format_description)作爲文件頭(上面的binlog圖片Format_desc事件),以日誌輪換事件(rotate)作爲文件尾。
Format_description包含binlog文件的服務器信息、文件狀態的關鍵信息等。如果服務器關閉或者重啓,則會創建一個新的binlog文件,同時寫入一個新的format_description。他的格式大致如下。
2 binlog-version
string[50] mysql-server version 4 create timestamp
1 event header length
string[p] event type header lengths
日誌輪換事件則包含下一個binlog的文件名以及開始讀取的位置,它由服務器寫完binlog後添加到文件尾,輪換事件並不會每次都存在,格式如下。
if binlog-version > 1 {
8 position
} string[p] name of the next binlog
binlog事件包含若干個事務組成的組(group),每個組對應一個事務,如果是create alter語句不屬於事務語句的話,則他們本身就是一個組,每個組要麼全部執行,要麼都不執行。
binlog事件結構
每個binlog事件由3個部分組成:
- 通用頭,包含binlog中所有事件具備的基本信息。
- 提交頭,對於不同類型的事件來說,提交頭的內容也不盡相同
- 事件體,存儲事件的主要數據,同樣對於不同類型事件也不同。
binlog輪換和清理
從上面的例子我們也可以看出來,binlog並非只有一個,而基於真實的場景來說,始終寫一個binlog文件肯定也是不可取的,而binlog輪換主要有3個場景:
- 服務器啓動,每次服務器啓動都會生成一個新的binlog文件。
- 達到最大大小,可以通過binlog-cache-size控制大小,達到最大大小後將更換。
- 顯示刷新,flush logs將所有日誌寫入磁盤,這時候會創建一個新的文件寫入,從第一個例子也能看出來執行完之後生成了一個新的日誌binlog.000030的文件並且開始的位置是4。
隨着時間的推移,我們的binlog文件會越來越多,這時候有兩種方式可以清除binlog:
- 通過設置expire-logs-days控制想保留的binlog日誌文件天數,系統將會自動清理。
- 通過PURGE BINARY LOGS手動清理
relay-log結構
relay-log中繼日誌是連接master和slave的核心,我們來深入瞭解一下它的結構和使用。
relay-log的結構和binlog非常相似,只不過他多了一個master.info和relay-log.info的文件。
master.info記錄了上一次讀取到master同步過來的binlog的位置,以及連接master和啓動複製必須的所有信息。
relay-log.info記錄了文件複製的進度,下一個事件從什麼位置開始,由sql線程負責更新。
上一篇文章我們提到了整個複製流程的過程大概是這個樣子:
知道binlog和relay-log的結構之後,我們重新梳理一下整個鏈路的流程,這裏我們假定master.info和relay-log.info都是存在的情況:
- Master收到客戶端請求語句,在語句結束之前向二進制日誌寫入一條記錄,可能包含多個事件。
- 此時,一個Slave連接到Master,Master的dump線程從binlog讀取日誌併發送到Slave的IO線程。
- IO線程從master.info讀取到上一次寫入的最後的位置。
- IO線程寫入日誌到relay-log中繼日誌,如果超過指定的relay-log大小,寫入輪換事件,創建一個新的relay-log。
- 更新master.info的最後位置
- SQL線程從relay-log.info讀取進上一次讀取的位置
- SQL線程讀取日誌事件
- 在數據庫中執行sql
- 更新relay-log.info的最後位置
- Slave記錄自己的binlog日誌
但是在這裏IO和SQL線程有會產生重複事件的問題,舉一個場景:
- 先記錄中繼日誌,然後更新master.info位置
- 此時服務器崩潰,寫入master.info失敗
- 服務器恢復,再次同步從master.info獲取到的是上一次的位置,會導致事件重複執行
既然會有這個問題還爲什麼要這樣做呢?假設反過來,先更新master.info再記錄中繼日誌,這樣帶來的問題就是丟失數據了。而mysql認爲丟失比重複更嚴重,所以要先刷新日誌,保大還是保小mysql幫你做了決定。