天天看點

MySQL高可用架構--組複制(group replication)搭建測試

一、架構搭建

      1.首先備份主庫資料,有兩種方法,冷備份和熱備份。冷備份需要先停止master服務,sudo/etc/init.d/mysql stop,然後通過cp或者scp等指令将資料檔案傳輸到指定檔案夾,這裡我選擇在一台伺服器上啟動三個執行個體來搭建組複制,是以就用sudo cp -R /var/lib/mysql /home/piriineos/data/mysql/data1/來複制資料,需要注意的是,這裡用root權限複制資料是以在目标地點的資料所有者是root,對mysql需要将資料檔案改為mysql所有,sudo chown -R mysql /home/piriineos/data/mysql ,複制成功後還需要注意一點就是auto.cnf檔案,用來儲存根據sererid生成的server uuid,對組複制有用,用來唯一辨別mysql執行個體,這裡由于複制的master的檔案,是以将uuid随便改幾個數字以差別開來。

       熱備份不需要停止服務,這在生産環境是必須的條件。這裡我用mysqldump來轉儲資料,mysqldump對innodb表來說不影響服務,但是對于myisam表會用FTWRL來鎖住所有表,導緻服務停止。這裡我就不備份系統表了,mysqldump --databases mgr--single-transaction --master-data=2 -uroot -p -h127.0.0.1>/home/piriineos/data/msater_backup.sql 。 然後在初始化一個新的資料庫,mysql_install_db --basedir=/var/lib/mysql--datadir=/home/piriineos/data/mysql/data1/ --user=mysql,然後啟動執行個體用source或者通道将備份的庫導入到新執行個體的庫中,不過在此之前需要将新的兩個資料庫配置一下配置檔案。

       2.8.0的真正的配置檔案并不是存儲在/etc/mysql/my.cnf,可以将其打開發現!includedir /etc/mysql/mysql.conf.d/這麼一句話,這是将這個檔案裡的内容include到my.cnf裡,因而我将真正的配置檔案複制兩份到新的資料庫那裡,sudo cp /etc/mysql/mysql.conf.d/mysqld.cnf/home/piriineos/data/mysql/data1/ ,同樣需要更改所有者,然後配置參數。

       首先是執行個體啟動參數的配置:

       port            = 3307

       pid-file   =/tmp/mysql1.pid

       socket            =/tmp/mysql1.sock

       datadir           =/home/piriineos/data/mysql/data1/mysql

       log-error =/home/piriineos/data/mysql/error1.log

       server-id = 13307               #這個必須設定,不然會使用預設值

       然後是mysql運作參數的設定:

       innodb_buffer_pool_size = 1073741824   #我的機器總記憶體8G,系統運作占用3G,因而每個執行個體配置設定1G記憶體

       innodb_buffer_pool_instances = 1

       innodb_flush_log_at_trx_commit = 1

       sync_binlog = 1                      #跟上一個參數配合使用,在每次事務的送出階段分别前後寫入硬碟

       binlog_checksum = NONE    #組複制必須關閉

       open_files_limit = 65535

       thread_cache_size = 8

       transaction_isolation =READ-COMMITTED        

       然後是日志和gtid的參數設定:

       log_bin = mysql-bin      #避免使用預設伺服器名

       binlog_format = row      #組複制必須

       slow_query_log = 1

       slow_query_log_file =/home/piriineos/data/mysql/data1/mysql/mysql-slow.log

       skip-slave-start = 1       

       log-slave-updates = 1     #必須

       gtid-mode = ON            #必須

       enforce-gtid-consistency = ON     #必須

       binlog_gtid_simple_recovery=1    #在5.7.17後建議開啟,加快恢複速度

       master_info_repository=TABLE   #必須

       relay_log_info_repository=TABLE      #必須

       sync_relay_log = 1

       sync_master_info = 100

       sync_relay_log_info = 100

       relay-log-index = relay-log.index 

       relay-log = relay-log

       然後是組複制參數設定:

       transaction_write_set_extraction =XXHASH64  #唯一确定事務影響行的主鍵,必須開啟

       loose-group_replication_group_name ='aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' #唯一辨別一個組

       loose-group_replication_ip_whitelist ='127.0.0.1/8'    #允許接入組的ip來源

       loose-group_replication_local_address ='127.0.0.1:33062' #用于組間通信的位址

       loose-group_replication_group_seeds ='127.0.0.1:33061,127.0.0.1:33062,127.0.0.1:33063'       #donor位址

       loose-group_replication_bootstrap_group =OFF  #引導節點設定

       loose-group_replication_single_primary_mode= true  #單主模式

       loose-group_replication_enforce_update_everywhere_checks= false

       loose-group_replication_start_on_boot =false     #避免重新開機自動自動組複制

       3.配置檔案寫好後,通過mysqld或者mysqld_safe啟動執行個體,需要注意的是這裡有個坑,就是啟動的時候必須用mysql身份進行,用其他使用者和root都不行,這個說在5.7已經修複,但是我現在8.0了還是有問題。。sudo -umysql mysqld_safe--defaults-file=/home/piriineos/data/mysql/data1/my.cnf 。啟動成功後将備份的資料導入,mysql -uroot -p </home/piriineos/data/master_backup.sql。

       4.啟動三個執行個體後在上面建立複制使用者,create user '[email protected]' identified by 'u201313742'。然後給授予複制的權限,grant replication slave on *.* to '[email protected]'identified by 'u201313742'。

       5.安裝引擎,8.0中預設是不安裝組複制引擎的,install plugin group_replication soname'group_replication.so'。可以通過show plugins檢視是否安裝成功。

      6.檢查組複制相關參數是否設定正确,show variables like '%group%;'如果不正确通過set global設定。檢查完畢後準備啟動組複制。這裡一定要注意的一點是,如果你是mysqldump并且沒有dump系統庫,或者你在新執行個體上reset master過,那麼一定要檢查gtidexecuted和gtid purged參數,上面兩個會導緻新庫的這兩個參數被清空,進而使slave将master的所有gtid事務全都拉取過來。可以通過set  globalgtid_purge='xx'來将slave已經執行過的gtid事務标記。

       7.檢查完畢後先啟動master。master作為第一個節點需要初始化整個組複制環境,set globalgroup_replication_bootstrap_group=on;将節點配置到某個channel,change master tomaster_user='repl',master_password='u201313742' for channel'group_replication_recovery';這裡有個warning就是顯示使用密碼登入很不安全,不知道有什麼辦法可以更安全登入。然後start group_replication啟動組複制,并且将bootstrap的參數關閉。

       8.master啟動完畢後再啟動兩個slave,同樣需要安裝組複制引擎,并且建立複制使用者,并授權,注意要與master的使用者一緻。然後changemaster to master_user='repl',master_password='u201313742' for channel'group_replication_recovery';然後start group_replication。

       9.觀察節點狀态是否正确,可以通過select * from mysql.group_replication_members;來檢視跟組複制相關的一些表來了解節點狀态,如果節點全都是online,則說明一切正常,slave已經追上或者快要追上master,如果為recovery則說明slave正在從donor恢複或者正嘗試連接配接donor進行恢複,如果是error(僅本地有此狀态)則說明這個節點已經挂了。需要注意的是,組複制遵從多數原則,即需要大于組成員數一半的節點才能正常工作,因而兩個節點時有個節點當機那麼另一個節點會進入super_read_only狀态避免寫入,同理三個節點則最多容忍一個節點當機,以此類推。

       下面是我踩過的一些坑:

       1.[ERROR] Plugin group_replicationreported: 'This member has more executed transactions than those present in thegroup. Local transactions: 1d1d8fd2-74a5-11e8-b1d4-80fa5b3b3f3d:1 > Grouptransactions: '

2018-06-23T09:36:09.290682Z0 [ERROR] Plugin group_replication reported: 'The member contains transactionsnot present in the group. The member will now exit the group.'

       slave執行的事務gtid與master不一緻,如果隻是因為誤操作,或者是一些無關緊要的資料,可以通過set globalgroup_replication_allow_local_disjoint_gtids_join=on;來忽略這些事務,或者通過reset master清空gtidexecuted表然後重新設定gtid purged參數跟master的gtid executed一緻來跳過這些事務。如果這些資料不一緻會導緻問題那麼可以通過pt-table-sync來檢查誤差資料并同步,然後再通過reset master等操作重設gtid相關參數,需要注意的是這個工具需要binlog格式為statment以使slave也能執行同樣檢查語句。

       2.[ERROR] Plugin group_replication reported:'There was an error when connecting to the donor server. Please check thatgroup_replication_recovery channel credentials and all MEMBER_HOST columnvalues of performance_schema.replication_group_members table are correct andDNS resolvable.'

       主機名解析不正确,我看了下/etc/hosts檔案,發現piriineos主機名居然解析為127.0.1.1,而我的donor名單都是127.0.0.1,把piriineos也設定為127.0.0.1即可。

       3.[ERROR] Plugin group_replicationreported: '[GCS] Timeout while waiting for the group communication engine to beready!'

       一般是組中半數以上節點挂了。

       4.[ERROR] Slave I/O for channel'group_replication_recovery': Fatal error: Invalid (empty) username whenattempting to connect to the master server. Connection attempt terminated.Error_code: 1593

       忘記給slave也change master to了。

       5.[ERROR] Slave SQL for channel'group_replication_recovery': Error 'Operation CREATE USER failed for'piriineos'@'127.0.0.1'' on query. Default database: ''. Query: 'CREATE USER'piriineos'@'127.0.0.1' IDENTIFIED WITH 'mysql_native_password' AS'*077BFD72D9E814194FBA9A90A6A8DB13BC476718'', Error_code: 1396

       slave apply了master作業系統表的事務,這裡是master上建立使用者的語句,忘記set sql_log_bin=0;了,不想讓slave執行的操作都需要設定它。這裡的解決辦法是直接把這個事務的gtid放到purged表中,跳過他即可。

二、架構原理

       1.組複制基于分布式一緻性協定(paxos協定的變體),遵循大多數規則,在多主模式下事務送出需要(N/2+1)的節點數同意才能通過。組複制其實跟gelera replication很相似,除了大多數規則,關于galera跟MGR的比較可以看看這篇文章

Galera vs Group Replication的差別。通過一緻性協定,MGR可以保證事務全局的原子性和有序性。組中維護一個全局的事務執行清單,存儲了全局認可的已執行事務,此時各個節點可以分别在自己的庫中執行自己的事務,但是在送出時需要廣播通知其他的所有節點,廣播中包括這個事務的相關資訊(影響的行等)和事務送出時全局事務清單,收到廣播的節點将事務收進事務隊列(該隊列中的事務都是廣播後的事務),事務隊列中的事務會進行沖突檢測,出現沖突的事務隻送出最先廣播(隊列最前面)的事務,其他的rollback。有人可能會疑惑為什麼每個節點單獨rollback而不通知其他節點仍然可以保持一緻性,這是因為事務隊列是全局一緻的,順序也是全局一緻的,因而沖突檢測的結果自然也就一樣了。

      2.沖突檢測的原理大緻如下:沖突檢測有兩個因素:事務是否修改了同樣的資料(以行為機關,通過主鍵識别);是否是同時修改的。通過事務執行時的資料庫快照版本來判定。資料庫的快照是用GTID SET來辨別的。事務送出前從全局變量gtid_executed中擷取GTID SET。這個GTIDSET就是代表了目前事務在送出前,資料庫中已經執行了的GTIDs。也即是目前事務送出前,資料庫的快照版本。資料庫的快照版本會随着事務的Binlog Events一起廣播到其他的節點上,用來做沖突檢測。

       沖突檢測時,從沖突檢測資料庫中找到目前事務修改的資料的記錄。如果沒有找到,就沒有沖突。如果找到了,則将自己的快照版本和沖突檢測資料庫中記錄的GTID SET進行比對:如果自己的快照版本中包含了沖突檢測資料庫中記錄的所有GTIDs,那麼就沒有沖突。自己的快照中包含了,說明該事務在初始節點上執行時,上一次對本行資料的修改已經在初始節點上送出了。是以就不是同時執行的。如果自己的快照版本中沒有包含沖突檢測資料庫中記錄的所有GTIDs,則說明目前事務在初始節點上執行時,上一次對本行資料的修改還沒有在初始節點上執行。是以判定為同時執行。

       3.組複制提供失敗檢測:組複制提供了一種故障檢測機制,其能夠找到和報告哪些伺服器是沒有響應的,并假定其是死的。更深入地說,故障檢測器是提供關于哪些伺服器可能已死的資訊的分布式服務。後續如果組同意懷疑可能是真的,則由組來決定該伺服器确實已經失敗。這意味着組中的其餘成員進行協調決定排除給定成員。伺服器無響應時觸發懷疑機制。當伺服器A在給定時間段内沒有從伺服器B接收消息時,發生逾時就會引起懷疑。 如果伺服器與組的其餘部分隔離,則它懷疑所有其他伺服器都失敗。由于無法與組達成協定(因為它無法獲得大多數成員認可),是以其懷疑沒有後果。當伺服器以此方式與組隔離時,它無法執行任何本地事務。

       4.組複制提供組成員視圖服務:MySQL組複制依賴于組成員服務group membership service。這是内置的插件。它定義哪些伺服器是線上的并參與在複制組中。線上伺服器清單通常稱為視圖view。是以,組中的每個伺服器具有一緻的視圖,其是在給定時刻積極參與到組中的成員。

      複制組的成員們不僅需要同意事務是否送出,而且也需要決定目前視圖。是以,如果伺服器同意新的伺服器成為組的一部分,則該組本身被重新配置以将該伺服器內建在其中,進而觸發視圖改變。相反的情況也發生,如果伺服器自願地離開組,則該組動态地重新布置其配置,并且觸發視圖改變。

當成員自願離開時,它首先啟動動态組重新配置。這觸發一個過程,所有成員必須同意新的視圖(也就是新視圖中沒有該離開成員)。然而,如果成員不由自主地離開(例如它已意外停止或網絡連接配接斷開),則故障檢測機制實作這一事實,并且提出該組的重新配置(新視圖沒有該離開成員)。如上所述,這需要來自組中大多數成員的同意。如果組不能夠達成一緻(例如,以不存在大多數伺服器線上的方式進行分區),則系統不能動态地改變配置,進而阻止腦裂情況引起的多寫。

三、架構測試

       1.主節點當機測試:

              *主節點當機分為兩種情況,一種是情況不太嚴重的,僅僅是因為某種原因伺服器崩潰了,但是      資料還在并且完好。

              首先讓三個執行個體進組,然後kill掉master。ps -ef|grep mysql找到master的程序:mysql     650       1  0 19:51 ?        00:00:04 /usr/sbin/mysqld --daemonize--pid-file=/var/run/mysqld/mysqld.pid,然後sudo      kill -9 650。然後檢視此時組的狀态,select* from performance_schema.replication_group_members;

MySQL高可用架構--組複制(group replication)搭建測試

              此時3307端口的執行個體被選為的新的master,這裡插入一些資料模拟當機後應用繼續向新主庫插   入資料,insert into mgr.tb1 (name) values('test'),('test')....('test');然後啟動當機節點sudo /etc/init.d/mysql    start,并将其加入組中,檢視節點狀态:

MySQL高可用架構--組複制(group replication)搭建測試

              可以發現是recovering狀态,代表新加入節點正在從donor那裡拉取落後的事務并進行重制。

              等待狀态變為online後檢視mgr.tb1表的資料:

MySQL高可用架構--組複制(group replication)搭建測試

              檢視主節點和當機節點的gtid參數:

MySQL高可用架構--組複制(group replication)搭建測試
MySQL高可用架構--組複制(group replication)搭建測試

              *然後模拟主節點當機并且資料丢失的情況,首先備份主節點重要庫的資料,mysqldump           --databasesmgr --single-transaction --master-data=2 -uroot -p -h127.0.0.1 -P3306                                         >/home/piriineos/data/msater_backup.sql,并記錄此時的gtid executed,然後先kill掉現在的3307         端口       的新主節點,然後      sudo rm -rf/home/piriineos/data/mysql/data1/mysql/ 模拟資料丢失, 檢視此時組的狀态發現3308端口實       例成為了新的主節點,同樣插入一些資料。

              用mysql_install_db重建資料庫,重新開機節點後将備份的資料導入到新庫中,需要注意的是這裡的   系統表都是mysql_install_db重建的,因而以前系統表和binlog等資料都不存在了,因而需要設定      gtidpurged,将其設定為之前記錄的gtid executed,不然啟動組複制後會從donor拉去重複資料,設定 将節點加入組中,檢視show master status發現當機節點已經追上其他節點。

              不過實際情況肯定要比這種模拟複雜的多,像當機節點加入後進行追趕時應用仍然會像主節點   插入資料,此時如果插入速度過快導緻新節點追不上組中其他節點會進行流量控制降低資料更新速   度來讓新節點追上來。

              組複制通過heartbeat發現有節點不能通信時會通知其他節點不再往該節點發送資料,如果發現      不能通信的是主節點,則會根據權重選取新的主節點,通過這個機制組複制能夠快速的從主節點宕   機中恢複過來。不過仍然存在的問題是主節點切換後應用不知道,需要中間件調節,還有就是當機      的節點恢複和重新加入組需要手動操作。

       2.高并發OLTP場景測試:

              *測試環境為:作業系統:debian8;cpu:i7 6700HQ(2.6GHz);記憶體:三星8G;硬碟:西數機械      硬碟1T。

              *用sysbench來模拟oltp高并發場景,首先準備測試資料:sysbench./home/piriineos/sysbench- 1.0/tests/include/oltp_legacy/oltp.lua--mysql-host=127.0.0.1 --mysql-port=3307 --mysql-user=root –mysql-    password=u201313742 --oltp-test-mode=complex--oltp-tables-count=5 --oltp-table-size=100000   –   threads=5 --time=60 --report-interval=10prepare。

測試腳本用的是sysbench自帶的,這個腳本進行了五種不同形式(ref、all、order by等)的随機數查詢,以及随機的update,delete和insert。測試模式是complex,即增删改查全都測試并且使用事務,測試五個表,每個表100000條資料(主要是當時手滑給/挂載的空間太少了,隻有10G,不敢弄多了資料),并發連接配接數為3(怕太多了給電腦跑崩潰了)。測試跟執行個體在同一台電腦,因而會對結果有一些影響。

MySQL高可用架構--組複制(group replication)搭建測試

       然後執行測試:sysbench./tests/include/oltp_legacy/oltp.lua --mysql-host=127.0.0.1 –mysql-      port=3307 --mysql-user=root--mysql-password=u201313742 --oltp-test-mode=complex –oltp-tables- count=3 --oltp-table-size=100000 --threads=3--time=60 --report-interval=10 run >>     /home/piriineos/work/sysbench.log。

              此時master和slave的狀态為:

              show processlist(master):

MySQL高可用架構--組複制(group replication)搭建測試

       show processlist(slave):

MySQL高可用架構--組複制(group replication)搭建測試

       show master status(master):

MySQL高可用架構--組複制(group replication)搭建測試

       show master status(slave):

MySQL高可用架構--組複制(group replication)搭建測試

              檢視sysbench執行日志:

    [ 10s ] thds: 3 tps: 54.19 qps: 1088.30(r/w/o: 762.86/216.76/108.68) lat (ms,95%): 369.77 err/s: 0.00 reconn/s: 0.00

    [ 20s ] thds: 3 tps: 68.80 qps: 1377.21(r/w/o: 963.21/276.40/137.60) lat (ms,95%): 200.47 err/s: 0.00 reconn/s: 0.00

    [ 30s ] thds: 3 tps: 55.50 qps: 1108.80(r/w/o: 777.00/220.80/111.00) lat (ms,95%): 204.11 err/s: 0.00 reconn/s: 0.00

    [ 40s ] thds: 3 tps: 53.00 qps: 1060.30(r/w/o: 742.00/212.30/106.00) lat (ms,95%): 176.73 err/s: 0.00 reconn/s: 0.00

    [ 50s ] thds: 3 tps: 69.20 qps: 1384.90(r/w/o: 968.80/277.70/138.40) lat (ms,95%): 167.44 err/s: 0.00 reconn/s: 0.00

    [ 60s ] thds: 3 tps: 73.40 qps: 1468.00(r/w/o: 1027.60/293.60/146.80) lat (ms,95%): 4.57 err/s: 0.00 reconn/s:    0.00

    SQL statistics:

    queries performed:

        read:                            52416

        write:                           14976

        other:                           7488

        total:                           74880

    transactions:                        3744   (61.30 per sec.)

    queries:                             74880  (1226.07 per sec.)

    ignored errors:                      0      (0.00 per sec.)

    reconnects:                          0      (0.00 per sec.)

    General statistics:

    total time:                          61.0715s

    total number of events:              3744

    Latency (ms):

         min:                                    2.68

         avg:                                   48.70

         max:                                 3409.39

         95th percentile:                      196.89

         sum:                               182324.61

    Threads fairness:

    events (avg/stddev):           1248.0000/46.27

    execution time (avg/stddev):   60.7749/0.41

          其中比較重要的是tps、qps和latency 95th percent,tps平均為61.3,qps平均為1226.07,前95     的請求響應時間為196ms,這個值太大了,在實際生産環境是不可以接受的,不過考慮到這裡三個實      例都運作在同一個伺服器,并且使用的是機械硬碟(ssd沒空間了),而且觀察到每10s的請求延時一 直在下降,說明測試時間還是太短,時間長了後innodb buffer pool等記憶體資料穩定後延時應該會穩定  到一個低一些的值,因而這麼高也是情理之中。

              這次測試因為條件限制存在着一些問題,比如說多個執行個體在同一伺服器;測試跟mysql執行個體在同一伺服器;測試資料過少;測試并發度不夠;測試時間太短;測試次數不夠;沒有對從庫進行讀測   試。

              其中在測試完成後觀察slave同步的過程中有一個發現,由于有一個節點是master當機後變成   slave,因而兩個slave的參數設定有一些不一緻,其中一個sync_relay_log=1,sync_master_info=100,

       sync_relay_log_info=100;而另一個則全都為預設值10000,我通過手動show master status發現10000  的這一方完成同步的速度大概要比另一個快一倍,我把較慢的那個頁設定為10000後很快也完成了    同步,可見這三個參數影響還是很大的。

       3.測試是否有腦裂:

*對于MGR來說是不存在腦裂的,因為MGR是大多數原則,正常運作或者做出決議等都需要大      多數節點參與,當部分節點失去連接配接時,多數那一方(數量需要大于N/2+1,不然也無法寫入隻能   讀)正常運作,并把少數一方剔除,而少數一方的節點廣播後發現節點數量少于N/2後就會強制進   入super read only狀态,不再接受應用寫入請求,因而也就不會造成多寫問題。不過有個問題就是少數節點權限切換後對于應用是非透明的,應用依然會進行寫入請求,因而需要其他元件來調節。

              由于我是在一台電腦上運作的三個執行個體,沒辦法用改變ip的方法來讓模拟腦裂,而通過iptables 禁用端口tcp連接配接也不知道為什麼不管用,是以就不測試腦裂的情況了。

              在某些非組複制叢集上是有可能發生腦裂的,比如DRBD+heartbeat的方案,備庫通過heartbeat     監視master狀态,備庫和master之間的連接配接中斷但是master仍然活着,此時drbd以為master當機自動更新為新的master就會導緻腦裂,像這種類似的情況可以通過一些技巧來避免腦裂,比如說使用備援的heartbeat連接配接,隻有在全部連接配接都失連時才判斷;連接配接斷開後重新開機對方,避免同時存在多個活躍節點;設定仲裁節點,連接配接斷開後雙方各自ping一下仲裁節點判斷是否是自己的問題;通過第三方節點(vip)來控制備庫更新等。

四、總結

      1.在使用MGR還是發現了一些問題,一個是節點切換對應用不透明,應用依然按照原來的認知寫入和讀取;一個是當機後的節點恢複需要手動;一個是斷開連接配接的節點重新連接配接後不會自動加入節點,與之類似的gelera cluster會等待連接配接恢複重連或者timeout踢出節點。

       2.MGR的大多數原則也存在一些隐患,那就是無法處理大多數節點當機的情況,這還是有可能的,當它們當機後整個組将不能寫入。

       3.還有其他的一些限制比如:僅支援InnoDB表,并且每張表一定要有一個主鍵,用于做write set的沖突檢測;必須打開GTID特性,二進制日志格式必須設定為ROW,用于選主與write set;COMMIT可能會導緻失敗,類似于快照事務隔離級别的失敗場景;目前一個MGR叢集最多支援9個節點;不支援外鍵于savepoint特性,無法做全局間的限制檢測與部分部分復原;二進制日志不支援binlog eventchecksum等。