keepalived+双主实践HA

    正好前段时间看过关于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还能继续提供数据库的支持。

三、搭建测试(Bash脚本模拟高并发)

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 forever
3、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架构的健壮性还是比较强大的,且实现了服务的高可用。
    通过一系列的测试发现,在切换过程中也存在类似与MMM和MHA的一个短暂的10s丢失数据库连接的错误,这个VIP的漂移过程中都会出现一些连接无法正常连接到应用数据库,出现一些数据丢失,影响业务的现象出现。但这个时间很短,一般对业务数据的影响不会太大,这里我模拟的是高并发,不停的向数据库写入表数据。
1 Warning: Using a password on the command line interface can be insecure. #这个错误这里测试大概会报10条
2 Warning: Using a password on the command line interface can be insecure.
3 ERROR 2003 (HY000): Can't connect to MySQL server on '172.16.3.123' (111)
4 Warning: Using a password on the command line interface can be insecure.
5 ERROR 2003 (HY000): Can't connect to MySQL server on '172.16.3.123' (111)
四、搭建总结
    本次测试是想换一种架构,寻找一种捷径解决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的并行复制是真正的并行复制。
原文地址:https://www.cnblogs.com/liyingxiao/p/9726663.html