线上的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 权威指南》
原文地址:使用keepalived实现redis的故障切换, 感谢原作者分享。