MySQL組複製系列文章:html
這一篇對MySQL組複製作個詳細的整理和解釋,是MySQL組複製官方手冊的整理版和總結。mysql
MySQL組複製是一個MySQL插件,它基於常規的MySQL複製,利用了基於行格式的二進制日誌和GTID等特性。下圖是MySQL組複製的總體框架圖。算法
如下是對該圖中各組件的大體介紹,涉及到的術語先瀏覽一遍,後面會詳細解釋。sql
MySQL組複製是MySQL 5.7.17開始引入的新功能,爲主從複製實現高可用功能。它支持單主模型和多主模型兩種工做方式(默認是單主模型)。bootstrap
當第一個節點啓動組複製功能以前(不是啓動mysql實例,而是啓動組複製插件的功能),通常會將這個節點設置爲組的引導節點,這不是必須的,但除非特殊調試環境,沒人會吃撐了用第2、第三個節點去引導組。緩存
所謂引導組,就是建立組。組建立以後,其餘節點才能加入到組中。安全
將某節點設置爲引導節點的方式是在該節點上設置如下變量爲ON:網絡
set @@global.group_replication_bootstrap_group=on;
開啓引導功能後,通常會當即開啓該節點的組複製功能來建立組,而後當即關閉組引導功能。因此,在第一個節點上,這3個語句常放在一塊兒執行:架構
set @@global.group_replication_bootstrap_group=on; start group_replication; set @@global.group_replication_bootstrap_group=off;
當第一個節點成功加入到組中後,這個節點會被標記爲ONLINE,只有標記爲ONLINE的節點,纔是組中有效節點,能夠向外提供服務、能夠進行組內通訊和投票。併發
若是配置的是單主模型(single-primary mode)的組複製,第一個加入組的節點會自動選爲primary節點。若是配置爲多主模型(multi-primary mode)的組複製,則沒有master節點的概念。
新節點要加組,在配置好配置文件後,只需執行如下3個過程等待成功返回就能夠了。
change master to master_user='XXXX', master_password='YYYY' for channle 'group_replication_recovery'; install plugin group_replication soname 'group_replication.so'; start group_replication;
雖然操做不多,但這裏涉及到一個很重要的過程:恢復過程。
若是一個新的節點要加入組,它首先要將本身的數據和組內的數據保持同步,同步的過程其實是從組中獲取某個節點的Binlog,而後應用到本身身上,填補本身缺失的那部分數據,這是經過異步複製完成的。而新節點和組中數據保持同步的過程稱爲恢復過程(或者稱爲分佈式恢復過程),它由組複製插件架構圖中的"Recovery"組件來完成。
當新節點啓動了它本身的組複製功能時,它將根據本身配置文件中 group_replication_group_seeds
選項的值來選一個節點做爲同步時的數據供應節點,這個節點稱爲"donor"(中文意思就是"供應者")。例如:
loose-group_replication_group_seeds="192.168.100.21:20001,192.168.100.22:20002"
上面的配置中,兩個節點均可以成爲donor,它們稱爲"種子節點(seed)"。新節點從前向後逐一選擇,當和第一個donor交互失敗,就會選擇第二個donor,直到選完最後一個donor,若是還失敗,將超時等待一段時間後再從頭開始選擇。建議每一個節點的配置文件中,都將組中全部節點寫入種子節點列表中。
當選擇好一個donor後,新節點會和這個donor創建一個數據恢復的通道group_replication_recovery
。Recovery組件會從donor上覆制全部新節點缺失的數據對應的binlog,而後應用在自身。當新節點數據和組中已經保持一致,則新節點成功加入到組,它將標記爲ONLINE。
實際上,這只是恢復的一個階段,還有第二階段,有了上面的基礎,下面的解釋就容易理解了。
在新節點準備開始加入組的時候,Recovery組件有兩個功能:(1).選擇donor,並從donor處複製缺失的數據;(2).監控組中新產生的事務並放進本身的事務緩存隊列中。
例如,在填補缺失的數據時,客戶端向組中的可寫節點插入了一條數據,這個數據不會被新節點的異步複製抓走,新節點會監控到這個事務。以下圖:
因此將恢復過程歸納一下:新節點的recovery組件經過異步複製通道從donor處獲取p1以前的全部數據,並監控組中新生成的事務放入本身的事務緩存隊列中。當新節點將p1以前的數據恢復完成後,將執行緩存隊列中的事務,直到緩存中的事務數量減爲0後,才表示新節點已經徹底遇上了組中的數據,這是它才能夠標識爲ONLINE,並真正成爲組中的一員。
這裏有個問題須要考慮:事務數量相同,爲何新節點能遇上組?第一個緣由是事務不會源源不斷地生成,它總有停頓的時候;第二個緣由歸功於基於行格式的binlog(row-based binlog),除了發起事務的那個節點,只要事務被大多數節點贊成了,全部節點都根據binlog行中設置的值直接修改數據,而不會完整地執行事務邏輯。換句話說,事務發起節點修改數據的速度沒有其餘節點快,儘管它們是同時提交的。
假設組中已經有5個節點(s一、s二、s三、s四、s5)了,這些節點目前全都是ONLINE狀態,這個狀態表示能正確向外提供服務、能正確進行組內通訊、能正確投票。假設s1是單主模型的主節點。
當在節點s1上執行了如下事務A1:
start transaction; insert into t values(3); commit;
s1稱爲事務的發起節點。s1首先會執行這個事務到commit,在真正commit以前有一個before_commit
的過程,這個過程會將binlog傳播給組內其它節點。當組內其它節點的receiver線程(其實就是io_thread,但組複製中不這樣稱呼)收到binlog後,將對這個事務進行決策,若是組內大多數節點(由於組內有5個節點,因此至少3個節點才知足大多數的要求)對這個事務達成一致,則這個事務會真正提交(決定後、提交前會先將buffer binlog寫到disk binlog),s2-s5會將收到的binlog也寫入到本身的disk binlog中,並經過applier線程(sql_thread)對這個事務進行應用。若是大多數節點未達成一致,則這個事務會回滾,日誌不會寫入到日誌中。
須要注意的是,決策經過後,事務發起節點的commit操做和其它節點對binlog的應用沒有強烈的前後關係,各節點收到"決策經過"的消息後,是獨立完成事務的。因此,有些節點可能會有延遲。當延遲在容許的範圍內,不會有什麼問題,但若是某個節點嚴重落後於其餘節點(拖後腿),組複製會放慢整個組的處理速度,也可能會作一些限流,關於這個問題的處理,叫作flow control。
如下爲binlog中兩個事務的對應的日誌內容,每一個事務的binlog是在成功決定提交以後才寫入的:
+------------+------------------------------------------------------------------------+ | Gtid | SET @@SESSION.GTID_NEXT= 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:4000007'| | Query | BEGIN | | Table_map | table_id: 111 (mut_gr.t1) | | Write_rows | table_id: 111 flags: STMT_END_F | | Xid | COMMIT /* xid=152 */ | | Gtid | SET @@SESSION.GTID_NEXT= 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:3000010'| | Query | BEGIN | | Table_map | table_id: 111 (mut_gr.t1) | | Write_rows | table_id: 111 flags: STMT_END_F | | Xid | COMMIT /* xid=153 */ | +------------+------------------------------------------------------------------------+
問題是,組內全部節點數據和狀態都是同步的,爲何還要對事務進行決策?在主節點上判斷事務可否執行、可否提交不就能夠了嗎?對於單主模型下執行正常的事務來講確實如此。可是多主模型下多個節點可執行併發事務,並且組複製內部也有些特殊的事件(例如成員加組、離組),這些都須要獲得大多數節點的贊成才能經過,因此必須先進行決策。後文會逐一解釋這些狀況。
多個事務分兩種:線性的事務和併發的事務。線性的多個事務和執行單個事務沒有什麼區別,無非是一個一個事務從上到下執行下去。併發事務就要多方面考慮。
若是有多個客戶端同時發起了事務t一、t二、t三、t4,這4個事務會由於GTID特性而具備全局惟一性並有序。例如:
'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:146' # 第1個節點的gtid序列 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1000033' # 第2個節點的gtid序列 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:2000010' # 第3個節點的gtid序列 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:3000009' # 第4個節點的gtid序列 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:4000007' # 第5個節點的gtid序列
只要同一個節點執行的事務,它的前綴是同樣的,只是後綴會不斷增加。例如,3個s1節點的事務,它們gtid的後綴可能會是14六、14七、148。3個s2節點的事務,它們的gtid後綴可能會是100003三、100003四、1000035。
注意:
- 每一個事務除了gtid,還有一個分佈式的xid。這個id也必須全局惟一,但同一個事務在不一樣節點上的體現值不同。例如,在s1節點上,事務的xid=200,應用到s2節點上,這個事務的xid會改成適應本身節點上的id,可能值爲xid=222。
- 另外,這個xid並不能保證順序。例如s1上執行開啓事務t1執行DML,但不提交,而後在s2節點上開啓事務t2執行DML並提交,最後提交s1上的事務,那麼在s1上,t1的xid雖然值更小,但在binlog中卻排在t2後面。
回到正題,併發事務t一、t二、t三、t4如何執行:
(1).若是是單主模型,那麼這4個事務都會路由到主節點上,若是這4個事務修改的數據互不衝突、互不影響,那麼它們都會成功應用到全部節點上。若是這4個事務之間有衝突,那麼先提交的事務將獲勝(事實上,這時的衝突事務會被阻塞)。
(2).若是是多主模型,若是這4個事務路由到同一個節點上執行,這和單主模型的狀況同樣。若是路由到不一樣節點,且併發執行,若是無衝突,則一切都OK,若是併發事務由衝突,那麼先提交的事務獲勝。此時,後提交的衝突事務,其實是在修改過時的數據。
問題是如何進行事務的衝突檢測?使用組複製有一大堆的限制:必須使用InnoDB引擎、必須開啓gtid模式、表中必須有主鍵等等。若是建立了MyISAM表,或者建立沒有主鍵的表,不要緊,它會複製走,由於它們是DDL語句,MySQL中沒有DDL事務,可是不能再向這樣的表中插入數據,這是強制的,若是插入數據將報錯。
ERROR 3098 (HY000): The table does not comply with the requirements by an external plugin.
其實這3個要求都是爲了衝突檢測作準備:
當在某節點發起事務後,這個節點的replication協議模塊(該模塊的位置見文章開頭的架構圖)會收集該事務將要修改行的寫集(write-set,術語),寫集是根據每行的主鍵進行hash計算的,是否記得在組複製的配置文件中有一行:
transaction_write_set_extraction=XXHASH64
這就是指定以"XXHASH64"算法將主鍵hash爲寫集。而後該協議模塊將binlog和寫集一塊兒傳播出去,並被其它節點的replication協議模塊收到。當其它節點收到後,會對寫集進行驗證,這個過程稱爲certify,它由certifier線程完成。若是驗證經過,則爲此事務投上本身的一票,多數節點經過後交給applier組件寫入數據。若是驗證不經過,意味着出現了事務衝突,這時將直接通告全組並回滾。注意,只要檢測到了衝突,全部節點都會回滾,組複製不會爲衝突事件進行投票決策。這裏所投的票是非衝突方面的決策。
若是多個節點上出現了併發事務,由於寫集是根據表中每行的主鍵值來計算的,因此不一樣事務若是修改同一行數據,它的寫集會相等,這表示併發事務出現了衝突。這時會將消息報告出去,並進行投票,投票的結果是先提交者獲勝,失敗的事務將回滾。
若是兩個事務常常出現衝突,建議將這兩個事務路由到同一節點上執行。
關於事務衝突問題,主要是多主模型下的問題,單主模型下只有單個主節點可寫,不會出現事務衝突問題。單主模型下,若是兩個事務修改的是同一行,第二個事務將會由於獨佔鎖衝突而被阻塞。
在MySQL中,沒有DDL事務,全部的DDL語句都沒法保證原子性,沒法回滾。但DDL語句畢竟會對錶中數據產生影響,它是一個事件,必須被複制走。
先看一下DDL語句和DML語句在binlog中的記錄格式的區別:(show binlog events in 'BINLOG_FILE';
,爲了排版,刪掉了幾個字段)
+-----------+-------------------------------------------------------------------+ | Gtid | SET @@SESSION.GTID_NEXT= 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:2' | | Query | create database gr_test | | Gtid | SET @@SESSION.GTID_NEXT= 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:3' | | Query | use `gr_test`; create table t4(id int primary key) | | Gtid | SET @@SESSION.GTID_NEXT= 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:4' | | Query | BEGIN | | Table_map | table_id: 117 (gr_test.t4) | | Write_rows| table_id: 117 flags: STMT_END_F | | Xid | COMMIT /* xid=63 */ | +-----------+-------------------------------------------------------------------+
上面的結果中,前4行是2個DDL語句表明的事件,後5行是一個DML語句表明的事件,它是一個分佈式事務。不難看出,DDL語句從頭至尾就只記錄了它的GTID和語句部分。
換句話說,DDL語句不會進行衝突檢測。這會出現兩個可怕的陷阱:
陷阱一:
若是多個節點併發執行多個DDL語句,後執行的DDL若是能正確執行,實際上是在對前面的DDL進行覆蓋。若是不能正確執行,將報錯,例如第一個DDL語句的做用是刪除一個字段,第二個DDL語句是修改這個字段的數據類型,這時第二個DDL語句將失去操做目標而報錯。
陷阱二:
若是多個節點併發執行DDL和DML語句,將可能出錯。例如第一個DDL語句是truncate語句,它將刪除表中全部數據,第二個DML語句則是update語句,顯然第二個DML語句在truncate後已經失去了更新目標。
因此,不要執行併發的DDL+DDL語句,也不要執行併發的DDL+DML語句。
在binlog中,除了DDL語句、DCL語句(grant,revoke)語句、DML語句生成的事件,還有一種因組複製而存在的特殊事件:視圖更新事件(view change)。
這個視圖是什麼視圖?在組複製插件中,有一個內置的服務稱爲"成員管理服務"(group membership service)。這個服務負責維護組內的成員關係,並向外展現當前組內成員列表。這個成員關係,或者當前成員列表就是所謂的成員視圖。
成員管理服務動態維護成員成員視圖,每當有成員加組、離組時,都會自動觸發成員視圖更新的行爲,這意味着讓成員管理服務去從新配置當前的組成員。例如,一個新成員加組成功時,這個組的大小加1,離組時組大小減1。
加組、離組的過程稍許複雜,在解釋它們以前,先解釋下視圖id和視圖更新事件。
在成員加組、離組時會觸發視圖更改,它不是DDL、DCL、DML,但卻也會看成一個事件寫入binlog,這樣才能在組內傳播,讓其它節點對視圖更改進行投票。這個事件稱爲"視圖更改事件"。
例如,如下是binlog中某次視圖更改對應的事件。
View_change | view_id=15294216022242634:2
在上面的示例中,有一個view_id,它是視圖標識符,用來唯一地標識一個視圖。它的格式由兩部分組成:第一部分是建立組時隨機生成的,在組中止(關閉)以前一直保持不變;第二部分是一個單調遞增的整數,每次視圖發生更改都遞增一次。例如,如下是4個節點加入組時的binlog事件。
View_change | view_id=15294216022242634:1 # 第一個節點建立組 View_change | view_id=15294216022242634:2 # 第二個節點加入組 View_change | view_id=15294216022242634:3 # 第三個節點加入組 View_change | view_id=15294216022242634:4 # 第四個節點加入組
須要注意的是,加組失敗時也會記錄到binlog中,視圖更改事件就像DDL/DCL事件同樣,是非事務型的,不具備原子性和回滾性,只要發生了就會記錄到binlog中,即便加組失敗或者離組失敗。
使用這種混合視圖id的緣由是,能夠明確地標記當成員加入或離開時發生的組成員配置更改,也能標記全部成員離開組後沒有任何信息保留在視圖中。實際上,單純使用單調遞增的整數做爲標識符會致使在組重啓後重用視圖ID,但顯然這會破壞恢復過程所依賴的二進制日誌標記的惟一性。總而言之,第一部分標識這個組是從何時啓動的,第二部分標識組在什麼時間點發生了更改。
在組通訊層(見本文開頭的架構圖),視圖更改以及它們關聯的視圖id是加組以前和以後的區分邊界。經過視圖id,能夠知道某個事務是視圖階段的。例如,一個新節點加入到組,對應的view_id爲id1,那麼id1以前的事務須要所有複製到新節點上,id1以後的事務是加組以後的事務,不須要複製給新節點(前文已經解釋過,在遇上組中數據以前,這部分事務會放進新節點的事務緩存隊列)。
其實,binlog中的view_change事件還充當另外一個角色:組內其他節點感知新節點已經成功加組或成功離組。例如新成員加組的狀況,當view_id寫入binlog,表示這個新節點已經標記爲ONLINE,其它節點知道它已經在線了,信息廣播的時候也會廣播給這個新節點,這個新節點也會佔有一個法定票數。
新成員加組的狀況,其實前文已經解釋過了,這裏作個小回顧:觸發視圖更改事件,同時找一個donor,從這個donor上經過recovery通道異步複製它所缺失的數據,並監控新生成的事務放進事務緩存隊列中,當事務緩存隊列中的事務數量爲0,將視圖更改事件寫入到binlog中,並將該節點標記爲ONLINE,此時它纔算是成功加入到組中。
成員離組的狀況分爲兩種:自願離組和非自願離組。這兩種離組方式形成的影響不一樣。
除了自願離組的狀況(自願離組見下一小節),全部離組的狀況都是非自願離組。好比節點宕機,斷網等等。
阻塞後的組只能手動干預,例如重啓整個組。也能夠對當前已阻塞的組強制更改組成員。例如,5個節點的組,當前只剩下2個節點爲ONLINE,那麼能夠強制更新這個組,讓這個組只包含這兩個成員,也就是說強制更改組的大小。
SET GLOBAL group_replication_force_members="192.168.100.21:10000,192.168.100.22:10001";
這種方法必須只能做爲最後的手段,一個操做不當,就會致使腦裂。爲何會致使腦裂?
由於非自願離開的成員可能並不是下線了,而是出現了網絡分區或其它緣由將這個節點給隔離了。這樣一來,這個節點會自認爲本身是組中的惟一成員,它不知道還有另外一個甚至多個同名的組存在。雖然被隔離的節點由於不知足大多數的要求而被阻塞,但若是將這些隔離的組之1、之二等強制更改組大小,那麼它們都會解除阻塞,容許寫入新數據,從而出現數據不一致、腦裂等各類惡劣事件。
因此,當多個節點非自願離組致使組被阻塞後,最安全的方法是重啓整個複製組。或者將全部被隔離的節點都徹底下線,而後強制更改剩下的組讓其解除阻塞,最後再將下線的節點從新加入到這個組中。
只有一種狀況是節點自願離組的狀況:執行stop group_replication;
語句。
自願離組時,待離組成員會觸發視圖更改事件,通知組內其它成員:老孫我如今要去尋仙拜師了,猴兒們別掛念你孫爺爺。而後組內的其它節點就當這個成員從未出現過同樣,它的離開除了對組複製性能形成一點影響以外,沒有其它任何影響。
節點自願離組時,不會丟失法定票數。因此不管多少個節點自願離組,都不會出現"達不到大多數"的要求而阻塞組。
舉個例子,5個節點的組,陸陸續續地依次自願退出了4個節點,不管哪一個節點退出,都會觸發視圖更改事件更改組的大小,這樣一來,永遠也不會出現組被阻塞的問題。
非自願離組觸發的視圖更改不會更改組的大小,離組節點過多,會沒法達到大多數的要求而阻塞組;自願離組觸發的視圖更改會更改組的大小,不管多少個節點自願離組,剩下的節點老是組的所有,固然能知足組的大多數要求,所以毫不會阻塞組。
使用組複製須要有一個低延遲、高帶寬的網絡環境,由於業務越繁忙,組內節點數量越多,組內要傳遞的消息就越多。若是忽然遇到一個很是大的事務(例如load data infile
中的數據很是多),可能會讓組複製很是慢。
因此,若是業務中常常有大事務,或者網絡帶寬資源不足,能夠考慮開啓組內信息壓縮功能。
例如,如下設置開啓了壓縮功能,壓縮的閾值爲2M左右。
STOP GROUP_REPLICATION; SET GLOBAL group_replication_compression_threshold= 2097152; START GROUP_REPLICATION;
當一個事務的消息超過2M時,就會將這個消息進行壓縮。默認狀況下已經開啓了壓縮功能,其閾值爲1000000字節(大體1MB)。若是要關閉壓縮功能,將閾值設置爲0便可。
當組內其它節點收到了壓縮的消息後,會進行解壓,而後讀取其中內容。
下圖描述了壓縮和解壓發生的時間點:
通常狀況下,無需去修改壓縮的閾值,除非出現了性能嚴重不足的狀況。
組內的不少信息均可以被監控,它們都記錄在mysql的performance_schema架構中。
這部分沒什麼理論性的內容,詳細內容見個人翻譯:監控MySQL組複製。
在真正開始使用組複製以前,第一件事不是去學會如何搭建組複製環境、不是學一大堆的理論,而是瞭解並記住它的要求和侷限性,不然一不當心就會釀成大禍。
詳細內容見個人翻譯:MySQL組複製的限制和侷限性。
在使用組複製過程當中,限制多多,要求多多,不免的問題也多多,有些是比較嚴重的問題,有些是小問題。因而,如何進行排錯?
我我的有幾點總結:
show binlog events
仔細比對各節點的GTID。好像都是些廢話啊。我在配置單主模型的組複製和配置多主模型的組複製中很是詳細介紹了配置組複製的步驟,除此以外還給出了幾個我遇到過的幾個問題和解決方案。
這部分瀏覽便可,一切都是自動的,人爲可控度不高,幾乎也不須要人爲修改。
組複製只有在組內全部節點都收到了事務,且大多數成員對該事務所在順序及其餘一些相關內容都達成一致時纔會進行事務的提交。
若是寫入組的事務總數量都在組中任何成員的寫入能力範圍以內,則可良好運行下去。若是某節點或某些節點的寫吞吐量較其餘節點更差,則這些節點可能會落後。
當組中有一些成員落後了,可能會帶來一些問題,典型的是讀取這些落後節點上的數據都是比較舊的。根據節點爲何落後的緣由,組內其餘成員可能會保存更多或更少的上下文,以便知足落後成員潛在的數據傳輸請求。
好在複製協議中有一種機制,能夠避免在應用事務時,快速成員和慢速成員之間拉開的距離太大。這就是所謂的流程控制(flow control)機制。該機制試圖實現如下幾個目標:
可由兩個工做隊列來決定是否節流:(1)認證隊列(certification);(2)二進制日誌上的應用隊列(applier)。當這兩個隊列中的任何一個超出隊列最大值(閾值)時,將會觸發節流機制。只需配置:(1)是否要對ceritier或applier或二者作flow control;(2)每一個隊列的閾值是多少。
flow control依賴於兩個基本的機制: