使用keepalived实现redis的故障切换

线上的redis环境一直是单点,确实挺危险的。刚开始想用redis的Sentinel来做,可看了半天发现这东西也不靠谱,还挺麻烦的样子,只能暂时抛弃,换成自己熟悉的keepalived来做。

有了方案后剩下的就是查询相关资料了,google了一堆的资料,发现大家的做法普遍是:

场景:CentOS A —>Keepalived Master && Redis Master
     CentOS B—>Keepalived Backup && Redis Backup

1.正常情况CentOS A 提供服务, CentOS B 备份
2.如果CentOS A 挂了,CentOS B 升级为Master;
     CentOS A 恢复正常后,CentOS A 抢占为 Master,CentOS B 降级为 Slave.
3.如果CentOS A 挂,重复上述过程;如果 CentOS B 挂,请自行修复CentOS B.

  可以看到上述步骤2中,一个是当CentOS A 挂了再起来后再次抢占为Master,会照成业务的再次切换,影响线上业务的稳定性;另一个就是可以看到网上大家的做法一般是当CentOS A恢复抢占为Master后,为了保持Redis 主从数据的一致性,会让CentOS A 的 Redis 变成从先跟 CentOS B的Redis 同步数据,同步了一个固定时间后,然后把CentOS A的Redis 变为主,同样CentOS B的Redis 也是先sleep了某一个固定时间等待CentOS A 同步完成后,再变为从,其他状态变化参考这个过程,不多讲。看完后,我深深的震撼了,我靠,这些哥们确信自己真的能精确的控制这些过程吗?脚本中sleep的时间真的可以定下来吗?最简单的Redis 主从复制,主挂了,立即切换到从,都不能保证数据完整(主的数据还没来得及进行持久化,保存到硬盘),加了个keepalived就行了吗?

我的观点是搞运维的要尽量把架构搞的简单点,不要给自己挖坑,过于复杂的架构不仅难维护,而且容易出问题,出了问题还要背黑锅。所以简单的梳理了下我的需求和我想要得到的效果:

场景:
        CentOS A —>Keepalived Master && Redis Master
        CentOS B—>Keepalived Backup && Redis Backup
     
1.正常情况CentOS A 提供服务,CentOS B  备份
2.如果CentOS A 挂了,CentOS B 升级为Master;
  CentOS A 恢复正常后, 不抢占,变为slave.
3.如果CentOS B挂了,CentOS A 再升级为主

大家知道keepalived会有四种状态的变化,每种状态变化时,都可以调用一个脚本,如下:

当进入Master状态时会呼叫notify_master
当进入Backup状态时会呼叫notify_backup
当发现异常情况时进入Fault状态呼叫notify_fault
当Keepalived程序终止时则呼叫notify_stop

    进入Master和Backup这两种状态很容易理解了,就是分别变为主和从;进入Fault这个稍微要注意下,这个是什么意思呢,简单的说就是keepalived发现自己有问题了,既然有问题那他就不可能再去参与Master竞选了,你得修好他才行,进入这种状态一般是keepalived自身出问题了,或者keepalived检测的网卡出问题不通了,再或者就是我们自己写的检测业务的脚本返回错误;进入Stop 这个就容易理解了,执行/etc/init.d/keepalived stop 就会进入这个状态。

    我想要的是上诉步骤2中,当CentOS A 挂了后(Keepalived检测的网卡出问题或者Redis挂了),CentOS A 上的Keepalived也立即stop了,这样一个是流程清晰,另一个是可以避免“脑裂”情况的发生,当然了事后要手动把CentOS A 上的Redis 和 Keepalived 起来;同理上诉步骤3中CentOS B挂了后,也是立即停止Keepalived进程,事后手动启动CentOS B的Redis 和 Keepalived。

这么做有什么好处呢,那就是流程清晰,易于理解,便于维护。让我们梳理下这个流程,看看有多简单:

1.正常情况CentOS A 为Master提供服务,CentOS B 为 Slave 备份。
2.然后出问题了,那就是CentOS A上的 Redis  挂了,Keepalived检测到 Redis挂了后,就进入 Fault状态,调用redis_fault.sh脚本关闭Keepalived ,接着Keepalived进入stop状态了;此时CentOS B升级为Master,调用redis_master.sh脚本将Redis升级为主。
3.然后上去修复CentOS A,启动Redis、Keepalived,由于设置不抢占,这个时候CentOS A的状态就只能是Slave了,调用redis_backup.sh脚本把Redis变为从,跟CentOS B上的Redis同步数据;这个时候CentOS B 继续是主,当然Redis 也是主。
4.天有不测风云啊,CentOS A 刚修好,CentOS B的Redis 又挂了。这时只剩CentOS A 了,义不容辞的也就成为Master了,调用redis_master.sh脚本,将自己的Redis升级为主;CentOS B的 Redis挂的时候,同样会进入Fault状态,调用redis_fault.sh脚本关闭Keepalived ,接着Keepalived进入stop状态。
5.一把泪,只有登录CentOS B查看相关日志后,手动启动Redis、Keepalived。这个时候CentOS A 继续把持Master老大哥地位不放,所以状态不变;CentOS B就只能乖乖的进入Slave状态,调用redis_backup.sh脚本,把Redis搞成CentOS A 上Redis的从。
6.重复上述流程。

流程搞清楚了,剩下的就是Keepalived 配置 以及进入每种状态时呼叫的脚本了,不多说,这个就直接上配置吧.

主:

[root@puppet ~]# cat /etc/keepalived/keepalived.conf
#全局定义
global_defs {
   #运行keepalived的机器的一个标示
   router_id redis-ha
}

vrrp_script chk_redis {
    script "/etc/keepalived/scripts/redis_check.sh"
    interval 1
}

#vrrp实例配置
vrrp_instance VI_1 {
    state BACKUP
    nopreempt
    interface eth0
    virtual_router_id 58
    priority 180
    #检查间隔,默认1s
    advert_int 1
    #在切换到MASTER状态后,延迟进行gratuitous ARP 请求
    garp_master_delay 1
    authentication {
        auth_type PASS
        auth_pass KJj23576hYgu23IP
    }
    #设置额外的监控,里面的任意一个网卡出现问题,都会进入FAULT状态
    track_interface {
       eth0
    }
    track_script {
        chk_redis
    }
    virtual_ipaddress {
        192.168.188.132
    }
    notify_master /etc/keepalived/scripts/redis_master.sh
    notify_backup /etc/keepalived/scripts/redis_backup.sh
    notify_fault  /etc/keepalived/scripts/redis_fault.sh
    notify_stop   /etc/keepalived/scripts/redis_stop.sh
}




[root@puppet ~]# cat /etc/keepalived/scripts/redis_master.sh
#!/bin/bash

REDISCLI="/usr/bin/redis-cli"
LOGFILE="/var/log/keepalived-redis-state.log"

echo "[master]" >> $LOGFILE
date >> $LOGFILE
echo "Being master...." >> $LOGFILE 2>&1

echo "Run SLAVEOF NO ONE cmd ..." >> $LOGFILE
$REDISCLI SLAVEOF NO ONE >> $LOGFILE 2>&1




[root@puppet ~]# cat /etc/keepalived/scripts/redis_backup.sh
#!/bin/bash

REDISCLI="/usr/bin/redis-cli"
LOGFILE="/var/log/keepalived-redis-state.log"

echo "[backup]" >> $LOGFILE
date >> $LOGFILE
echo "Being slave...." >> $LOGFILE 2>&1

echo "Run SLAVEOF cmd ..." >> $LOGFILE
$REDISCLI SLAVEOF 192.168.188.131 6379 >> $LOGFILE  2>&1




[root@puppet ~]# cat /etc/keepalived/scripts/redis_fault.sh
#!/bin/bash

LOGFILE=/var/log/keepalived-redis-state.log

echo "[fault]" >> $LOGFILE
date >> $LOGFILE
/etc/init.d/keepalived stop




[root@puppet ~]# cat /etc/keepalived/scripts/redis_stop.sh
#!/bin/bash

LOGFILE=/var/log/keepalived-redis-state.log

echo "[stop]" >> $LOGFILE
date >> $LOGFILE

从:

[root@agent ~]# cat /etc/keepalived/keepalived.conf
#全局定义
global_defs {
   #运行keepalived的机器的一个标示
   router_id redis-ha
}

vrrp_script chk_redis {
    script "/etc/keepalived/scripts/redis_check.sh"
    interval 1
}

#vrrp实例配置
vrrp_instance VI_1 {
    state BACKUP
    interface eth0
    virtual_router_id 58
    priority 120
    #检查间隔,默认1s
    advert_int 1
    #在切换到MASTER状态后,延迟进行gratuitous ARP 请求
    garp_master_delay 1
    authentication {
        auth_type PASS
        auth_pass KJj23576hYgu23IP
    }
    #设置额外的监控,里面的任意一个网卡出现问题,都会进入FAULT状态
    track_interface {
       eth0
    }
    track_script {
        chk_redis
    }
    virtual_ipaddress {
        192.168.188.132
    }
    notify_master /etc/keepalived/scripts/redis_master.sh
    notify_backup /etc/keepalived/scripts/redis_backup.sh
    notify_fault  /etc/keepalived/scripts/redis_fault.sh
    notify_stop   /etc/keepalived/scripts/redis_stop.sh
}




[root@agent ~]# cat /etc/keepalived/scripts/redis_master.sh
#!/bin/bash

REDISCLI="/usr/bin/redis-cli"
LOGFILE="/var/log/keepalived-redis-state.log"

echo "[master]" >> $LOGFILE
date >> $LOGFILE
echo "Being master...." >> $LOGFILE 2>&1

echo "Run SLAVEOF NO ONE cmd ..." >> $LOGFILE
$REDISCLI SLAVEOF NO ONE >> $LOGFILE 2>&1




[root@agent ~]# cat /etc/keepalived/scripts/redis_backup.sh
#!/bin/bash

REDISCLI="/usr/bin/redis-cli"
LOGFILE="/var/log/keepalived-redis-state.log"

echo "[backup]" >> $LOGFILE
date >> $LOGFILE
echo "Being slave...." >> $LOGFILE 2>&1

echo "Run SLAVEOF cmd ..." >> $LOGFILE
$REDISCLI SLAVEOF 192.168.188.130 6379 >> $LOGFILE  2>&1




[root@agent ~]# cat /etc/keepalived/scripts/redis_fault.sh
#!/bin/bash

LOGFILE=/var/log/keepalived-redis-state.log

echo "[fault]" >> $LOGFILE
date >> $LOGFILE
/etc/init.d/keepalived stop

[root@agent ~]# cat /etc/keepalived/scripts/redis_stop.sh
#!/bin/bash

LOGFILE=/var/log/keepalived-redis-state.log

echo "[stop]" >> $LOGFILE
date >> $LOGFILE

注意:

    从配置里可以看到这种方案有一个坑,就是 CentOS A 上的Keepalived刚启动的时候,状态切换会先切换到Backup,然后参加竞选后才会到Master。这样就会出现问题,当切换到Backup时,会执行redis_backup.sh,跟 CentOS B 的Redis 同步数据,如果这时CentOS B上的Redis数据不全或者为空,悲剧就发生了,这肯定不是我想要的结果了。

    所以正常的启动流程是:先启动CentOS  A 的 Redis,接着启动Keepalived;然后等个几秒,启动 CentOS B 的Redis,接着启动Redis。

说了这么多,不做测试,一切都是白搭,下面让我们跑下测试流程,看看日志结果:

1.分别启动CentOS A的Redis、Keepalived(注意顺序)
[root@puppet ~]# /etc/init.d/redis start
启动 :                                                    [确定]
[root@puppet ~]# /etc/init.d/keepalived start
正在启动 keepalived:                                      [确定]

查看日志输出如下:
[root@puppet ~]# tail -f /var/log/keepalived-redis-state.log

[backup]
2014年 11月 18日 星期二 12:48:51 CST
Being slave....
Run SLAVEOF cmd ...
OK
[master]
2014年 11月 18日 星期二 12:48:55 CST
Being master....
Run SLAVEOF NO ONE cmd ...
OK

可以看到先进入Backup状态,然后后成为Master。这里由于CentOS B上的Redis、Keepalived还没有启动,所以在CentOS A 进入Backup状态时,执行redis_backup.sh,并不会连接上CentOS B的Redis执行数据同步,因此可以放心CentOS A 上Redis 数据的完整性。

接着,启动CentOS B的Redis 、Keepalived.
[root@agent ~]# /etc/init.d/redis status
redis-server 已停
[root@agent ~]# /etc/init.d/redis start
启动 :                                                    [确定]
You have new mail in /var/spool/mail/root
[root@agent ~]# /etc/init.d/keepalived start
正在启动 keepalived:                                      [确定]


[root@agent ~]# tail -f /var/log/keepalived-redis-state.log
[backup]
2014年 11月 18日 星期二 12:14:37 CST
Being slave....
Run SLAVEOF cmd ...
OK

可以看到CentOS B的Redis已经起来,并且已经成为CentOS A Redis的从了。


2.模拟故障,CentOS A的 Redis down 了
[root@puppet ~]# /etc/init.d/redis stop
停止 redis-server:                                        [确定]

可以看到CentOS A的Keepalived先进入fault,接着进入stop状态
[root@puppet ~]# tail -f /var/log/keepalived-redis-state.log
[fault]
2014年 11月 18日 星期二 12:57:44 CST
[stop]
2014年 11月 18日 星期二 12:57:44 CST

CentOS B 升级为Master,同时Redis也变为主
[root@agent ~]# tail -f /var/log/keepalived-redis-state.log
[master]
2014年 11月 18日 星期二 12:16:44 CST
Being master....
Run SLAVEOF NO ONE cmd ...
OK


3.再手动启动CentOS A的Redis 、Keepalived,CentOS A的Redis变为CentOS B Redis的从,并不会进行抢占
[root@puppet ~]# /etc/init.d/redis start
启动 :                                                    [确定]
[root@puppet ~]# /etc/init.d/keepalived start
正在启动 keepalived:                                      [确定]

[root@puppet ~]# tail -f /var/log/keepalived-redis-state.log
[backup]
2014年 11月 18日 星期二 13:00:06 CST
Being slave....
Run SLAVEOF cmd ...
OK



4.这个时候CentOS B 上的Redis 挂 了,CentOS A立即升级为Master,并且Redis也变为 Master角色
[root@agent ~]# /etc/init.d/redis stop
停止 redis-server:                                        [确定]
You have new mail in /var/spool/mail/root

CentOS B的Keepalived先进入fault,接着进入stop状态
[root@agent ~]# tail -f /var/log/keepalived-redis-state.log
[fault]
2014年 11月 18日 星期二 13:02:07 CST
[stop]
2014年 11月 18日 星期二 13:02:07 CST

CentOS A 升级为Master,同时Redis变为主
[root@puppet ~]# tail -f /var/log/keepalived-redis-state.log
[master]
2014年 11月 18日 星期二 13:02:08 CST
Being master....
Run SLAVEOF NO ONE cmd ...
OK

5.再启动CentOS B 的Redis、Keepalived,启动后Redis变为从跟CentOS  A 的 Redis 同步;Cent OS  A 的状态不变
[root@agent ~]# /etc/init.d/redis start
启动 :                                                    [确定]
[root@agent ~]# /etc/init.d/keepalived start
正在启动 keepalived:                                      [确定]

[root@agent ~]# tail -f /var/log/keepalived-redis-state.log
[backup]
2014年 11月 18日 星期二 13:04:45 CST
Being slave....
Run SLAVEOF cmd ...
OK


6.如果再有故障,重复上面的过程。

自言自语:

Keepalived中可以玩的地方还是挺多的,例如心跳时间、自定义检测脚本等等,我这里为了维护的方便只写出最简单的配置,具体可以参见官网文档;同时Redis主从的切换过程也可以加入报警功能,例如我就在redis_master.sh中加入了短信报警的功能,这样可以及时知道那一台Redis出问题了。这次写博客没有大片大片的写安装文档、没有写配置流程,真是个奇迹,不过预计以后写博客的时间也是越来越少了,各种忙碌中...

参考资料:

郭东:http://heylinux.com/archives/1942.html

oschina: http://my.oschina.net/guol/blog/182491

其他:《Keepalived 权威指南》