Home  >  Article  >  Database  >  使用keepalived实现redis的故障切换

使用keepalived实现redis的故障切换

WBOY
WBOYOriginal
2016-06-07 16:41:071313browse

线上的redis环境一直是单点,确实挺危险的。刚开始想用redis的Sentinel来做,可看了半天发现这东西也不靠谱,还挺麻烦的样子,只能暂时抛弃,换成自己熟悉的keepalived来做。 有了方案后剩下的就是查询相关资料了,google了一堆的资料,发现大家的做法普遍是

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

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

<code>场景: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就行了吗?</code>

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

<code>场景:
        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 再升级为主</code>

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

<code>当进入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。</code>

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

<code>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.重复上述流程。</code>

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

主:

<code>[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</code>

从:

<code>[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</code>

注意:

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

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

<code>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.如果再有故障,重复上面的过程。</code>

自言自语:

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

参考资料:

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

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

其他:《Keepalived 权威指南》

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn