工作不怎麼忙,搞點兒開發吧差點兒事,就想著弄點兒架構的事兒。正好前段時間看過關於keepalived+雙主實現高可用的文章,也恰好身邊的朋友所在的公司也部分用這個架構。沒什麼事兒就搞搞吧,正好對比下MMM、MHA、keepalived+雙主三種架構的優劣和DB維護的體驗感。簡單講講自己的用戶體驗感, ...
工作不怎麼忙,搞點兒開發吧差點兒事,就想著弄點兒架構的事兒。正好前段時間看過關於keepalived+雙主實現高可用的文章,也恰好身邊的朋友所在的公司也部分用這個架構。沒什麼事兒就搞搞吧,正好對比下MMM、MHA、keepalived+雙主三種架構的優劣和DB維護的體驗感。簡單講講自己的用戶體驗感,就搭建難易程度講MMM的安裝包封裝好的,修改的配置文件較MHA少一些,比keepalived+雙主要稍微麻煩點兒。本著省事,維護起來省事還是覺得MMM真的便利,黑盒操作適合我這種懶人加小白類型。
某位大佬講過,如果你的公司還在用MMM和MHA,那麼你可以考慮跳槽了。我覺得這句話很有道理,其實並不是讓我們真的去跳槽,畢竟每個人工作的目的,環境不一樣,有些架構上的事兒我們決定不了。沒法隨性而行,但不能停止探索的腳步,本過程從搭建調研/搭建過程/搭建測試/搭建總結四個方面講述我對雙主+keepalived的理解和用戶體驗感。
一、搭建調研
傳統的高可用架構如MHA、MMM存在一些不成熟的問題,如腦裂。引入keepalived和雙主複製模式,實現高可用架構,但keepalived本身是在機器宕機時才會實現漂移功能,我們的目標是要MySQL實例宕機後要實現故障切換,還需要輔助的腳本來幫助keepalived來實現更靈活的漂移。
keepalived簡介
keepalived是集群管理中保證集群高可用的一個軟體解決方案,其功能類似於heartbeat,用來防止單點故障,這裡的作用我理解其實就是保證VIP的順利漂移。虛擬路由冗餘協議,可以認為是實現路由器高可用的協議,即將N台提供相同功能的路由器組成一個路由器組,這個組裡面有一個master和多個backup,master上面有一個對外提供服務的vip,master會發組播(組播地址為224.0.0.18),當backup收不到vrrp包時就認為master宕掉了,這時就需要根據VRRP的優先順序來選舉一個backup當master,這樣的話就可以保證路由器的高可用了。
keepalived配置說明
keepalived只有一個配置文件keepalived.conf,裡面主要包括以下幾個配置區域,分別是global_defs、vrrp_instance和virtual_server。
- global_defs:主要是配置故障發生時的通知對象以及機器標識;
- vrrp_instance:用來定義對外提供服務的VIP區域及其相關屬性;
- virtual_server:虛擬伺服器定義。
二、搭建過程
搭建環境(伺服器配置忽略)
master1:172.16.3.190/22 3309 VIP:172.16.3.123/22
master2:172.16.3.189/22 3309 1、搭建雙主複製集(忽略) 2、master1和master2上安裝keepalived服務,並修改配置文件,如下配置1 #master1配置keepalived 2 yum install keepalived.x86_64 3 [root@172-16-3-190 we_ops_admin]# cat /etc/keepalived/keepalived.conf 4 ! Configuration File for keepalived 5 6 global_defs { 7 router_id lvs_master1 8 } 9 10 vrrp_instance VI_1 { 11 state BACKUP 12 interface eth0 13 virtual_router_id 172 14 priority 100 15 advert_int 1 16 nopreempt 17 authentication { 18 auth_type PASS 19 auth_pass 1111 20 } 21 virtual_ipaddress { 22 172.16.3.123/22 23 } 24 } 25 26 virtual_server 172.16.3.123 3309 { 27 delay_loop 6 28 lb_algo rr 29 lb_kind NAT 30 nat_mask 255.255.255.0 31 persistence_timeout 50 32 protocol TCP 33 34 real_server 172.16.3.190 3309 { 35 weight 3 36 notify_down /opt/shells/keepalived_mysql.sh 37 TCP_CHECK { 38 connect_timeout 3 39 nb_get_retry 3 40 delay_before_retry 3 41 connect_port 3309 42 } 43 } 44 } 45 46 #master2上安裝keepalived 47 yum install keepalived.x86_64 48 [root@172-16-3-189 we_ops_admin]# cat /etc/keepalived/keepalived.conf 49 ! Configuration File for keepalived 50 51 global_defs { 52 router_id lvs_master2 53 } 54 55 vrrp_instance VI_1 { 56 state BACKUP 57 interface eth0 58 virtual_router_id 172 59 priority 50 60 advert_int 1 61 # nopreempt 62 authentication { 63 auth_type PASS 64 auth_pass 1111 65 } 66 virtual_ipaddress { 67 172.16.3.123/22 68 } 69 } 70 71 virtual_server 172.16.3.123 3309 { 72 delay_loop 6 73 lb_algo rr 74 lb_kind NAT 75 nat_mask 255.255.255.0 76 persistence_timeout 50 77 protocol TCP 78 79 real_server 172.16.3.189 3309 { 80 weight 3 81 notify_down /opt/shells/keepalived_mysql.sh 82 TCP_CHECK { 83 connect_timeout 3 84 nb_get_retry 3 85 delay_before_retry 3 86 connect_port 3309 87 } 88 } 89 }
上述配置中我們可以保證keepalived服務對VIP:172.16.3.123/22的控制權,預設是keepalived服務關閉,那麼會觸發VIP的漂移。正常運行的服務不會發生異常停止的現象,如果系統發生宕機會觸發所有的服務停止,這裡系統宕機是觸發VIP漂移的導火索。只是這裡我們想讓keepalived服務於MySQL複製集,那麼這裡的導火索自然而然是MySQL服務的狀態。如果服務狀態不可用,那麼我們希望這個應用VIP可以漂移到複製集的另一臺機器上;如果服務狀態可用,我們希望VIP不要漂移。要想實現這個目的,我們還需要一個服務腳本來幫助我們去幫助keepalived發現MySQL服務宕機後的動作,腳本如下配置。
1 [root@172-16-3-190 we_ops_admin]# cat /opt/shells/keepalived_mysql.sh 2 #!/bin/bash 3 pkill keepalived 4 /sbin/ifdown eth0 && /sbin/ifup eth0 5 #授予可執行許可權 6 [root@172-16-3-190 we_ops_admin]# ls -lh /opt/shells/keepalived_mysql.sh 7 -rwxr-xr-x 1 root root 66 Sep 27 19:29 /opt/shells/keepalived_mysql.sh
通過步驟1·2的配置,啟動MySQL服務,啟動keepalived服務,這裡的master1和master2基本就可以實現高可用,保證了master1服務不可用時,master2還能繼續提供資料庫的支持。
三、搭建測試
1、master1的MySQL服務宕機,VIP會從master1上摘除漂移落盤到master2上,且master1上的keepalived服務也會停止。應用連接VIP,master2繼續為整個集群提供資料庫支持。
1 #停止master1上的MySQL服務 2 [root@172-16-3-190 we_ops_admin]# /etc/init.d/mysql_3309 stop 3 Shutting down MySQL (Percona Server).. SUCCESS! 4 5 #keepalived服務也停止了,且VIP已經被從master1上摘除 6 [root@172-16-3-190 we_ops_admin]# /etc/init.d/keepalived status 7 keepalived dead but subsys locked 8 [root@172-16-3-190 we_ops_admin]# ip add 9 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN 10 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 11 inet 127.0.0.1/8 scope host lo 12 inet6 ::1/128 scope host 13 valid_lft forever preferred_lft forever 14 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc htb state UP qlen 1000 15 link/ether 52:54:00:f4:ec:b2 brd ff:ff:ff:ff:ff:ff 16 inet 172.16.3.190/22 brd 172.16.3.255 scope global eth0 17 inet6 fe80::5054:ff:fef4:ecb2/64 scope link 18 valid_lft forever preferred_lft forever 19 #VIP漂移到master2上 20 [root@172-16-3-189 we_ops_admin]# ip add 21 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN 22 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 23 inet 127.0.0.1/8 scope host lo 24 inet6 ::1/128 scope host 25 valid_lft forever preferred_lft forever 26 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc htb state UP qlen 1000 27 link/ether 52:54:00:2d:96:5c brd ff:ff:ff:ff:ff:ff 28 inet 172.16.3.189/22 brd 172.16.3.255 scope global eth0 29 inet 172.16.3.123/22 scope global secondary eth0 30 inet6 fe80::5054:ff:fe2d:965c/64 scope link 31 valid_lft forever preferred_lft forever
2、master1重新加入集群,VIP不會重新漂移回來,造成二次波動或者腦裂現象
1 #重啟master1上的MySQL服務 2 [root@172-16-3-190 we_ops_admin]# /etc/init.d/mysql_3309 start 3 Starting MySQL (Percona Server)............... SUCCESS! 4 #重啟master1上的keepalived服務 5 [root@172-16-3-190 we_ops_admin]# /etc/init.d/keepalived start 6 Starting keepalived: [ OK ] 7 #VIP還是在master2上,且master1上並沒有VIP,因為master1上設置非搶占模式,即使優先順序更高 8 [root@172-16-3-190 we_ops_admin]# ip add #master1 9 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc htb state UP qlen 1000 10 link/ether 52:54:00:f4:ec:b2 brd ff:ff:ff:ff:ff:ff 11 inet 172.16.3.190/22 brd 172.16.3.255 scope global eth0 12 inet6 fe80::5054:ff:fef4:ecb2/64 scope link 13 valid_lft forever preferred_lft forever 14 15 [root@172-16-3-189 we_ops_admin]# ip add master2 16 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc htb state UP qlen 1000 17 link/ether 52:54:00:2d:96:5c brd ff:ff:ff:ff:ff:ff 18 inet 172.16.3.189/22 brd 172.16.3.255 scope global eth0 19 inet 172.16.3.123/22 scope global secondary eth0 20 inet6 fe80::5054:ff:fe2d:965c/64 scope link 21 valid_lft forever preferred_lft forever3、master2服務宕機(如果想讓VIP重新漂移回master1上,一般情況下生成環境不允許也不建議進行二次切換)
1 #關閉master2實例 2 [root@172-16-3-189 we_ops_admin]# /etc/init.d/mysql_3309 stop 3 Shutting down MySQL (Percona Server).. SUCCESS! 4 [root@172-16-3-189 we_ops_admin]# /etc/init.d/keepalived status 5 keepalived dead but subsys locked 6 #VIP已經從master2上飄走了 7 [root@172-16-3-189 we_ops_admin]# ip add 8 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc htb state UP qlen 1000 9 link/ether 52:54:00:2d:96:5c brd ff:ff:ff:ff:ff:ff 10 inet 172.16.3.189/22 brd 172.16.3.255 scope global eth0 11 inet6 fe80::5054:ff:fe2d:965c/64 scope link 12 valid_lft forever preferred_lft forever 13 14 #VIP已經落盤到master1上 15 [root@172-16-3-190 we_ops_admin]# ip add 16 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc htb state UP qlen 1000 17 link/ether 52:54:00:f4:ec:b2 brd ff:ff:ff:ff:ff:ff 18 inet 172.16.3.190/22 brd 172.16.3.255 scope global eth0 19 inet 172.16.3.123/22 scope global secondary eth0 20 inet6 fe80::5054:ff:fef4:ecb2/64 scope link 21 valid_lft forever preferred_lft forever 22 #server-id可以證明連接到master1實例 23 [root@172-16-3-190 we_ops_admin]# /opt/app/mysql_3309/bin/mysql -urepl -prepl --socket=/opt/app/mysql_3309/tmp/mysql.sock --port=3309 --host=172.16.3.123 24 Warning: Using a password on the command line interface can be insecure. 25 Welcome to the MySQL monitor. Commands end with ; or \g. 26 Your MySQL connection id is 33 27 Server version: 5.6.20-68.0-log Percona Server (GPL), Release 68.0, Revision 656 28 29 Copyright (c) 2009-2014 Percona LLC and/or its affiliates 30 Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved. 31 32 Oracle is a registered trademark of Oracle Corporation and/or its 33 affiliates. Other names may be trademarks of their respective 34 owners. 35 36 Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. 37 38 mysql> show global variables like '%server_id%'; 39 +----------------+---------+ 40 | Variable_name | Value | 41 +----------------+---------+ 42 | server_id | 1903309 | 43 | server_id_bits | 32 | 44 +----------------+---------+ 45 2 rows in set (0.01 sec)上述三個測試操作,實踐了VIP從master1到master2,最後再重新漂移回master1。這個切換過程中沒有任何的其他問題,說明keepalived+雙主的MySQL架構的健壯性還是比較強大的,且實現了服務的高可用。 四、搭建總結 本次測試是想換一種架構,尋找一種捷徑解決MHA腦裂的問題,通常情況下,上聯交換機的波動容易造成集群中主與備主對VIP的爭搶,造成應都可以連接兩個資料庫實例的現象發生。MHA對於VIP的漂移是經過兩個步驟來完成,一個是對VIP的摘除,另一個是VIP的落盤,即通常意義的VIP刪除,另一個機器上添加VIP。但很遺憾的是本次測試並沒有解決或者替代VIP腦裂的現象,反而也容易出現腦裂這個問題,因為本身keepalived對於VIP的管理也是經過了兩個步驟,即VIP的刪除和添加,這兩個步驟是分開的,如果不加以對其中一臺機器鎖定,就很容易出現腦裂的現象。 但是值得慶幸的是,即使發生了VIP的腦裂,兩台機器上都有VIP,但是應用連接的只是其中一臺機器,寫入也是其中一臺,因此並不是真正意義上的腦裂。這種情況在反覆停止MySQL實例,VIP來回漂移時會出現,我們可以手動刪除沒有真正意義落盤的那台機器上的VIP。 上述配置存在的問題 1、腦裂(VIP的腦裂,這裡並不是真正意義上的腦裂,可以根據server_id來判斷應用只是連接了其中一臺機器) 由於master1設置了不搶占VIP,master2註釋了不搶占VIP模式。master1宕機重新加入集群後不會搶奪VIP,但是master2宕機後重新加入集群後會搶占VIP,此時VIP會出現在master1和master2上。 通過innotop工具實時並不能抓到應用連接,但通過表的數據增長判斷應用連接到master1上,而master2的數據沒有增長(同步停止,已經被迫中斷了)。即其實此時的腦裂並不是雙寫,而是寫到了mater1上。 #VIP在master2上,master1重新加入集群,準備將VIP從master2上切回master1
[root@172-16-3-189 we_ops_admin]#/etc/init.d/mysql_3309 stop #master1上停止實例
Shutting down MySQL (Percona Server).. SUCCESS!
[root@172-16-3-189 we_ops_admin]#/etc/init.d/keepalived status
keepalived dead but subsys locked
[root@172-16-3-189 we_ops_admin]#ip add #vip居然還在master2上
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc htb state UP qlen 1000
link/ether 52:54:00:2d:96:5c brd ff:ff:ff:ff:ff:ff
inet 172.16.3.189/22 brd 172.16.3.255 scope global eth0
inet 172.16.3.123/22 scope global secondary eth0
inet6 fe80::5054:ff:fe2d:965c/64 scope link
valid_lft forever preferred_lft forever [root@172-16-3-190 we_ops_admin]#ip add #VIP也漂移到master1上,應用連接到master1上寫
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc htb state UP qlen 1000
link/ether 52:54:00:f4:ec:b2 brd ff:ff:ff:ff:ff:ff
inet 172.16.3.190/22 brd 172.16.3.255 scope global eth0
inet 172.16.3.123/22 scope global secondary eth0
inet6 fe80::5054:ff:fef4:ecb2/64 scope link
valid_lft forever preferred_lft forever
#master2上有VIP,但應用沒有連接到master2上且表的行數不增長
mysql> select max(id) from test_keepalived;
+---------+
| max(id) |
+---------+
| 168 |
+---------+
1 row in set (0.00 sec) mysql> select max(id) from test_keepalived;
+---------+
| max(id) |
+---------+
| 168 |
+---------+
1 row in set (0.00 sec) #VIP也在master1上應用連接到master1且表行數在增長
mysql> select max(id) from test_keepalived;
+---------+
| max(id) |
+---------+
| 387 |
+---------+
1 row in set (0.00 sec) mysql> select max(id) from test_keepalived;
+---------+
| max(id) |
+---------+
| 388 |
+---------+
1 row in set (0.00 sec)
2、master2同步被中斷的問題。(VIP在maste2上時,因為master2上已經寫入了數據但沒來得及同步到master1上;master2實例停止後,VIP也漂移到master1,應用連接master1進行寫入,但因為表設計為主鍵自增長,會出現ID為25已寫入master2而沒有同步到master1,應用連接master1寫入到資料庫同步到master2時報主鍵重覆)
1 mysql> show slave status \G; 2 *************************** 1. row *************************** 3 Slave_IO_State: Waiting for master to send event 4 Master_Host: 172.16.3.190 5 Master_User: repl 6 Master_Port: 3309 7 Connect_Retry: 30 8 Master_Log_File: binlog.000036 9 Read_Master_Log_Pos: 103620 10 Relay_Log_File: relay_bin.000038 11 Relay_Log_Pos: 280 12 Relay_Master_Log_File: binlog.000036 13 Slave_IO_Running: Yes 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: 1062 22 Last_Error: Error 'Duplicate entry '25' for key 'PRIMARY'' on query. Default database: 'practice'. Query: 'insert into test_keepalived values(null,1,4)' 23 Skip_Counter: 0 24 Exec_Master_Log_Pos: 120 25 Relay_Log_Space: 104434 26 Until_Condition: None 27 Until_Log_File: 28 Until_Log_Pos: 0 29 Master_SSL_Allowed: No 30 Master_SSL_CA_File: 31 Master_SSL_CA_Path: 32 Master_SSL_Cert: 33 Master_SSL_Cipher: 34 Master_SSL_Key: 35 Seconds_Behind_Master: NULL 36 Master_SSL_Verify_Server_Cert: No 37 Last_IO_Errno: 0 38 Last_IO_Error: 39 Last_SQL_Errno: 1062 40 Last_SQL_Error: Error 'Duplicate entry '25' for key 'PRIMARY'' on query. Default database: 'practice'. Query: 'insert into test_keepalived values(null,1,4)' 41 Replicate_Ignore_Server_Ids: 42 Master_Server_Id: 1903309 43 Master_UUID: 1b589d80-f450-11e7-9150-525400f4ecb2 44 Master_Info_File: /opt/app/mysql_3309/logs/master.info 45 SQL_Delay: 0 46 SQL_Remaining_Delay: NULL 47 Slave_SQL_Running_State: 48 Master_Retry_Count: 86400 49 Master_Bind: 50 Last_IO_Error_Timestamp: 51 Last_SQL_Error_Timestamp: 180929 17:43:30 52 Master_SSL_Crl: 53 Master_SSL_Crlpath: 54 Retrieved_Gtid_Set: 55 Executed_Gtid_Set: 56 Auto_Position: 0 57 1 row in set (0.00 sec)
Keepalived+雙主架構總結 中小型規模採用這種架構省事,master發生故障宕機後,利用keepalived的高可用實現VIP的快速漂移。 1、採用keepalived作為高可用,兩個節點上最好都設置為backup模式,避免意外情況下(比如腦裂)相互搶占導致往兩個節點寫入相同數據而引發衝突; 2、把兩個節點的auto_increment_increment(自增步長)和auto_increment_offset(自增起始值)設成不同值。其目的是為了避免master節點意外宕機時,可能會有部分binlog未能及時複製到slave上被應用,從而會導致slave新寫入數據的自增值和原先master上衝突了(原master重新恢復後),造成同步狀態不正常。因此一開始就使其錯開,如果有合適的容錯機制能解決主從自增ID衝突的話,也可以不這麼做; 3.slave節點伺服器配置不要太差,否則更容易導致複製延遲。作為熱備節點的slave伺服器,硬體配置不能低於master節點; 4.如果對延遲問題很敏感的話,可考慮使用MariaDB分支版本,或者直接上線MySQL 5.7最新版本,利用多線程複製的方式可以很大程度降低複製延遲; 本次測試中遇到如下的問題1可以通過對keepalived配置調整進行解決腦裂問題(兩個keepalived服務對於VIP設置都不搶占); 對於問題2中發生的主鍵衝突這個問題可以通過主鍵的自增長起始值和步長提到的方法進行解決,但是這樣做不符合業務的開發習慣,或者時程式員的開發規範。經過一次對於表自增長主鍵的疑問,發現現在所在公司的開發在做業務開發的時候,通常會讓表的主鍵進行自增,且他們會偷懶的把業務寫入時主鍵不進行寫入,而是讓資料庫自己去做這個事兒,因而主鍵一定是自增的。雖然這種做法不敢苟同,但確實降低了開發對於主鍵的考慮的成本,主鍵出現寫入錯誤的可能性,同時這種不顯性指定主鍵的插入值,也經常會導致這種主鍵重覆的衝突。 對於3中提到的,如果是主備模式的兩台機器,配置應該保持一致,避免延遲帶來的業務延遲。 對於4提到的,請各位自行測試,並行複製確實是可以降低延遲,且5.7的並行複製是真正的並行複製。