目前,Redis还没有一个类似于MySQL Proxy或Oracle RAC的官方HA方案。
    #Redis 2.8版开始正式提供名为Sentinel的主从切换方案(后面附上,未测试)
    因此,如何在出现故障时自动转移是一个需要解决的问题。
    通过对网上一些资料的搜索,有建议采用HAProxy或Keepalived来实现的,事实上如果是做Failover而非负载均衡的话,Keepalived的效率肯定是超过HAProxy的,所以我决定采用Keepalived的方案。

    环境介绍:

    Master: 192.168.0.100
    Slave: 192.168.0.101
    Virtural IP Address (VIP): 192.168.0.200

    设计思路:

    当 Master 与 Slave 均运作正常时, Master负责服务,Slave负责Standby;
    当 Master 挂掉,Slave 正常时, Slave接管服务,同时关闭主从复制功能;
    当 Master 恢复正常,则从Slave同步数据,同步数据之后关闭主从复制功能,恢复Master身份,于此同时Slave等待Master同步数据完成之后,恢复Slave身份。
    然后依次循环。
    需要注意的是,这样做需要在Master与Slave上都开启本地化策略,否则在互相自动切换的过程中,未开启本地化的一方会将另一方的数据清空,造成数据完全丢失。

    下面,是具体的实施步骤:

    在Master和Slave上安装Keepalived
    $ yum install keepalived
    默认安装完成keepalived有默认的配置文件,因此我们重写覆盖它:
    首先,在Master上创建如下配置文件:

    1. $ vim /etc/keepalived/keepalived.conf
    2. ! Configuration File for keepalived
    3. global_defs {
    4. router_id redis100
    5. }
    6. vrrp_script chk_redis
    7. {
    8. script "/etc/keepalived/scripts/redis_check.sh 127.0.0.1 6379"
    9. interval 2
    10. timeout 2
    11. fall 3
    12. }
    13. vrrp_instance redis {
    14. state MASTER # master set to SLAVE also
    15. interface eth0
    16. virtual_router_id 50
    17. priority 150
    18. nopreempt # no seize,must add
    19. advert_int 1
    20. authentication { #all node must same
    21. auth_type PASS
    22. auth_pass 1111
    23. }
    24. virtual_ipaddress {
    25. 192.168.0.200/24
    26. }
    27. track_script {
    28. chk_redis
    29. }
    30. notify_master "/etc/keepalived/scripts/redis_master.sh 127.0.0.1 192.168.0.101 6379"
    31. notify_backup "/etc/keepalived/scripts/redis_backup.sh 127.0.0.1 192.168.0.101 6379"
    32. notify_fault /etc/keepalived/scripts/redis_fault.sh
    33. notify_stop /etc/keepalived/scripts/redis_stop.sh
    34. }

    然后,在Slave上创建如下配置文件:
    ! Configuration File for keepalived

    1. global_defs {
    2. router_id redis101
    3. }
    4. vrrp_script chk_redis
    5. {
    6. script "/etc/keepalived/scripts/redis_check.sh 127.0.0.1 6379"
    7. interval 2
    8. timeout 2
    9. fall 3
    10. }
    11. vrrp_instance redis {
    12. state BACKUP
    13. interface eth0
    14. virtual_router_id 50
    15. priority 100
    16. advert_int 1
    17. authentication { #all node must same
    18. auth_type PASS
    19. auth_pass 1111
    20. }
    21. virtual_ipaddress {
    22. 192.168.0.200/24
    23. }
    24. track_script {
    25. chk_redis
    26. }
    27. notify_master "/etc/keepalived/scripts/redis_master.sh 127.0.0.1 192.168.0.100 6379"
    28. notify_backup "/etc/keepalived/scripts/redis_backup.sh 127.0.0.1 192.168.0.100 6379"
    29. notify_fault /etc/keepalived/scripts/redis_fault.sh
    30. notify_stop /etc/keepalived/scripts/redis_stop.sh
    31. }

    在Master和Slave上创建监控Redis的脚本

    $ mkdir /etc/keepalived/scripts
    $ vim /etc/keepalived/scripts/redis_check.sh

    1. #!/bin/bash
    2. ALIVE=`/usr/redis/redis-cli -h $1 -p $2 PING`
    3. LOGFILE="/var/log/keepalived-redis-check.log"
    4. echo "[CHECK]" >> $LOGFILE
    5. date >> $LOGFILE
    6. if [ $ALIVE == "PONG" ]; then :
    7. echo "Success: redis-cli -h $1 -p $2 PING $ALIVE" >> $LOGFILE 2>&1
    8. exit 0
    9. else
    10. echo "Failed:redis-cli -h $1 -p $2 PING $ALIVE " >> $LOGFILE 2>&1
    11. exit 1
    12. fi

    编写以下负责运作的关键脚本:
    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

    因为Keepalived在转换状态时会依照状态来呼叫:
    当进入Master状态时会呼叫notify_master
    当进入Backup状态时会呼叫notify_backup
    当发现异常情况时进入Fault状态呼叫notify_fault
    当Keepalived程序终止时则呼叫notify_stop

    首先,在Redis Master上创建notity_master与notify_backup脚本:
    $ vim /etc/keepalived/scripts/redis_master.sh

    1. #!/bin/bash
    2. REDISCLI="/usr/redis/redis-cli -h $1 -p $3"
    3. LOGFILE="/var/log/keepalived-redis-state.log"
    4. echo "[master]" >> $LOGFILE
    5. date >> $LOGFILE
    6. echo "Being master...." >> $LOGFILE 2>&1
    7. echo "Run MASTER cmd ..." >> $LOGFILE 2>&1
    8. $REDISCLI SLAVEOF $2 $3 >> $LOGFILE
    9. sleep 10 #delay 10 s wait data async cancel sync
    10. echo "Run SLAVEOF NO ONE cmd ..." >> $LOGFILE
    11. $REDISCLI SLAVEOF NO ONE >> $LOGFILE 2>&1

    $ sudo vim /etc/keepalived/scripts/redis_backup.sh

    1. #!/bin/bash
    2. REDISCLI="/usr/redis/redis-cli"
    3. LOGFILE="/var/log/keepalived-redis-state.log"
    4. echo "[backup]" >> $LOGFILE
    5. date >> $LOGFILE
    6. echo "Run SLAVEOF cmd ..." >> $LOGFILE
    7. $REDISCLI SLAVEOF $2 $3 >> $LOGFILE 2>&1
    8. # echo "Being slave...." >> $LOGFILE 2>&1
    9. sleep 15 #delay 15 s wait data sync exchange role

    接着,在Redis Slave上创建notity_master与notify_backup脚本:

    $ vim /etc/keepalived/scripts/redis_master.sh

    1. #!/bin/bash
    2. REDISCLI="/usr/redis/redis-cli -h $1 -p $3"
    3. LOGFILE="/var/log/keepalived-redis-state.log"
    4. echo "[master]" >> $LOGFILE
    5. date >> $LOGFILE
    6. echo "Being master...." >> $LOGFILE 2>&1
    7. echo "Run SLAVEOF cmd ... " >> $LOGFILE
    8. $REDISCLI SLAVEOF $2 $3 >> $LOGFILE 2>&1
    9. #echo "SLAVEOF $2 cmd can't excute ... " >> $LOGFILE
    10. sleep 10 ##delay 15 s wait data sync exchange role
    11. echo "Run SLAVEOF NO ONE cmd ..." >> $LOGFILE
    12. $REDISCLI SLAVEOF NO ONE >> $LOGFILE 2>&1

    $ vim /etc/keepalived/scripts/redis_backup.sh

    1. #!/bin/bash
    2. REDISCLI="/usr/redis/redis-cli"
    3. LOGFILE="/var/log/keepalived-redis-state.log"
    4. echo "[BACKUP]" >> $LOGFILE
    5. date >> $LOGFILE
    6. echo "Being slave...." >> $LOGFILE 2>&1
    7. echo "Run SLAVEOF cmd ..." >> $LOGFILE 2>&1
    8. $REDISCLI SLAVEOF $2 $3 >> $LOGFILE
    9. sleep 100 #delay 10 s wait data async cancel sync
    10. exit(0)

    然后在Master与Slave创建如下相同的脚本:

    $ vim /etc/keepalived/scripts/redis_fault.sh

    1. #!/bin/bash
    2. LOGFILE=/var/log/keepalived-redis-state.log
    3. echo "[fault]" >> $LOGFILE
    4. date >> $LOGFILE

    $ sudo vim /etc/keepalived/scripts/redis_stop.sh

    1. #!/bin/bash
    2. LOGFILE=/var/log/keepalived-redis-state.log
    3. echo "[stop]" >> $LOGFILE
    4. date >> $LOGFILE

    给脚本都加上可执行权限:

    (这点很重要,最开始由于这不没做,运行后一直报错 “VRRP_Instance(redis) Now in FAULT state”)
    $ sudo chmod +x /etc/keepalived/scripts/*.sh

    脚本创建完成以后,我们开始按照如下流程进行测试:
    1.启动Master上的Redis
    $ /etc/init.d/redis start
    2.启动Slave上的Redis
    $ /etc/init.d/redis start
    3.启动Master上的Keepalived
    $ /etc/init.d/keepalived start
    4.启动Slave上的Keepalived
    $ /etc/init.d/keepalived start
    5.尝试通过VIP连接Redis:
    $ redis-cli -h 10.6.1.200 INFO
    连接成功,Slave也连接上来了。
    role:master
    slave0:10.6.1.144,6379,online
    6.尝试插入一些数据:
    $ redis-cli -h 10.6.1.200 SET Hello Redis
    OK
    从VIP读取数据
    $ redis-cli -h 10.6.1.200 GET Hello
    “Redis”
    从Master读取数据
    $ redis-cli -h 10.6.1.143 GET Hello
    “Redis”
    从Slave读取数据
    $ redis-cli -h 10.6.1.144 GET Hello
    “Redis”

    下面,模拟故障产生:
    将Master上的Redis停了
    $ service redis_6379 stop
    查看Master上的Keepalived日志
    $ tailf /var/log/keepalived-redis-state.log
    [fault]
    Thu Sep 27 08:29:01 CST 2012
    同时Slave上的日志显示:

    $ tailf /var/log/keepalived-redis-state.log
    [master]
    Fri Sep 28 14:14:09 CST 2012
    Being master….
    Run SLAVEOF cmd …
    OK
    Run SLAVEOF NO ONE cmd …
    OK

    然后我们可以发现,Slave已经接管服务,并且担任Master的角色了。

    $ redis-cli -h 192.168.0.200 INFO
    role:master

    然后我们恢复Master的Redis进程

    $ service redis_6379 start
    查看Master上的Keepalived日志

    $ tailf /var/log/keepalived-redis-state.log

    [master]
    Thu Sep 27 08:31:33 CST 2012
    Being master….
    Run SLAVEOF cmd …
    OK
    Run SLAVEOF NO ONE cmd …
    OK

    同时Slave上的日志显示:

    $ tailf /var/log/keepalived-redis-state.log
    [backup]
    Fri Sep 28 14:16:37 CST 2012
    Being slave….
    Run SLAVEOF cmd …
    OK

    可以发现目前的Master已经再次恢复了Master的角色,故障切换以及自动恢复都成功了。
    主从用到的脚本及keepalived.conf 可以从这下载 http://download.csdn.net/detail/huwei2003/8252221

    注意事项:主从的redis都要开启本地备份

    附:
    Redis Sentinel的主从切换方案
    Redis 2.8版开始正式提供名为Sentinel的主从切换方案,Sentinel用于管理多个Redis服务器实例,主要负责三个方面的任务:
    1. 监控(Monitoring): Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。
    2. 提醒(Notification): 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
    3. 自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器; 当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。
    Redis Sentinel 是一个分布式系统, 你可以在一个架构中运行多个 Sentinel 进程(progress), 这些进程使用流言协议(gossip protocols)来接收关于主服务器是否下线的信息, 并使用投票协议(agreement protocols)来决定是否执行自动故障迁移, 以及选择哪个从服务器作为新的主服务器。
    启动Sentinel
    使用—sentinel参数启动,并必须指定一个对应的配置文件,系统会使用配置文件来保存 Sentinel 的当前状态, 并在 Sentinel 重启时通过载入配置文件来进行状态还原。
    redis-server /path/to/sentinel.conf —sentinel
    使用TCP端口26379,可以使用redis-cli或其他任何客户端与其通讯。
    如果启动 Sentinel 时没有指定相应的配置文件, 或者指定的配置文件不可写(not writable), 那么 Sentinel 会拒绝启动。
    配置Sentinel
    以下是一段配置文件的示例:

    1. sentinel monitor mymaster 127.0.0.1 6379 2
    2. sentinel down-after-milliseconds mymaster 60000
    3. sentinel failover-timeout mymaster 180000
    4. sentinel parallel-syncs mymaster 1
    5. sentinel monitor resque 192.168.1.3 6380 4
    6. sentinel down-after-milliseconds resque 10000
    7. sentinel failover-timeout resque 180000
    8. sentinel parallel-syncs resque 5
    1. 第一行配置指示 Sentinel 去监视一个名为 mymaster 的主服务器, 这个主服务器的 IP 地址为 127.0.0.1 端口号为 6379 而将这个主服务器判断为失效至少需要 2 Sentinel 同意 (只要同意 Sentinel 的数量不达标,自动故障迁移就不会执行)。<br /> 不过需要注意的是,无论你设置要多少个 Sentinel 同意才能判断一个服务器失效,一个 Sentinel 都需要获得系统中多数(majority Sentinel 的支持,才能发起一次自动故障迁移,并预留一个给定的配置纪元 Configuration Epoch ,一个配置纪元就是一个新主服务器配置的版本号)。也就是说,如果只有少数(minoritySentinel 进程正常运作的情况下,是不能执行自动故障迁移的。<br /> down-after-milliseconds 选项指定了 Sentinel 认为服务器已经断线所需的毫秒数(判定为主观下线SDOWN)。<br />parallel-syncs 选项指定了在执行故障转移时, 最多可以有多少个从服务器同时对新的主服务器进行同步, 这个数字越小, 完成故障转移所需的时间就越长,但越大就意味着越多的从服务器因为复制而不可用。可以通过将这个值设为 1 来保证每次只有一个从服务器处于不能处理命令请求的状态。<br /> 主观下线和客观下线<br /> 1. 主观下线(Subjectively Down 简称 SDOWN)指的是单个 Sentinel 实例对服务器做出的下线判断。<br /> 2. 客观下线(Objectively Down 简称 ODOWN)指的是多个 Sentinel 实例在对同一个服务器做出 SDOWN 判断, 并且通过 SENTINEL is-master-down-by-addr 命令互相交流之后, 得出的服务器下线判断。<br /> 客观下线条件只适用于主服务器: 对于任何其他类型的 Redis 实例, Sentinel 在将它们判断为下线前不需要进行协商, 所以从服务器或者其他 Sentinel 永远不会达到客观下线条件。<br /> 只要一个 Sentinel 发现某个主服务器进入了客观下线状态, 这个 Sentinel 就可能会被其他 Sentinel 推选出, 并对失效的主服务器执行自动故障迁移操作。<br />每个Sentinel实例都执行的定时任务<br /> 1. 每个 Sentinel 以每秒钟一次的频率向它所知的主服务器、从服务器以及其他 Sentinel 实例发送一个 PING 命令。<br /> 2. 如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 那么这个实例会被 Sentinel 标记为主观下线。 一个有效回复可以是: +PONG -LOADING 或者 -MASTERDOWN 。<br /> 3. 如果一个主服务器被标记为主观下线, 那么正在监视这个主服务器的所有 Sentinel 要以每秒一次的频率确认主服务器的确进入了主观下线状态。<br /> 4. 如果一个主服务器被标记为主观下线, 并且有足够数量的 Sentinel (至少要达到配置文件指定的数量)在指定的时间范围内同意这一判断, 那么这个主服务器被标记为客观下线。<br /> 5. 在一般情况下, 每个 Sentinel 会以每 10 秒一次的频率向它已知的所有主服务器和从服务器发送 INFO 命令。 当一个主服务器被 Sentinel 标记为客观下线时, Sentinel 向下线主服务器的所有从服务器发送 INFO 命令的频率会从 10 秒一次改为每秒一次。<br /> 6. 当没有足够数量的 Sentinel 同意主服务器已经下线, 主服务器的客观下线状态就会被移除。 当主服务器重新向 Sentinel PING 命令返回有效回复时, 主服务器的主管下线状态就会被移除。

    Sentinel API
    有两种方式可以与Sentinel进行通讯:指令、发布与订阅。
    Sentinel命令
    PING :返回 PONG 。
    SENTINEL masters :列出所有被监视的主服务器,以及这些主服务器的当前状态;
    SENTINEL slaves :列出给定主服务器的所有从服务器,以及这些从服务器的当前状态;
    SENTINEL get-master-addr-by-name : 返回给定名字的主服务器的 IP 地址和端口号。 如果这个主服务器正在执行故障转移操作, 或者针对这个主服务器的故障转移操作已经完成, 那么这个 命令返回新的主服 务器的 IP 地址和端口号;
    SENTINEL reset : 重置所有名字和给定模式 pattern 相匹配的主服务器。 pattern 参数是一个 Glob 风格的模式。 重置操作清楚主服务器目前的所有状态, 包括正在执行中的故障转移, 并移除目前已经发现和关联的, 主服务器的所有从服务器和 Sentinel ;
    SENTINEL failover : 当主服务器失效时, 在不询问其他 Sentinel 意见的情况下, 强制开始一次自动故障迁移。
    客户端可以通过SENTINEL get-master-addr-by-name 获取当前的主服务器IP地址和端口号,以及SENTINEL slaves 获取所有的Slaves信息

    发布与订阅信息
    客户端可以将 Sentinel 看作是一个只提供了订阅功能的 Redis 服务器: 你不可以使用 PUBLISH 命令向这个服务器发送信息, 但你可以用 SUBSCRIBE 命令或者 PSUBSCRIBE 命令, 通过订阅给定的频道来获取相应的事件提醒。
    一个频道能够接收和这个频道的名字相同的事件。 比如说, 名为 +sdown 的频道就可以接收所有实例进入主观下线(SDOWN)状态的事件。
    通过执行 PSUBSCRIBE 命令可以接收所有事件信息。
    +switch-master :配置变更,主服务器的 IP 和地址已经改变。 这是绝大多数外部用户都关心的信息。
    可以看出,我们使用Sentinel命令和发布订阅两种机制就能很好的实现和客户端的集成整合:
    使用get-master-addr-by-name和slaves指令可以获取当前的Master和Slaves的地址和信息;而当发生故障转移时,即Master发生切换,可以通过订阅的+switch-master事件获得最新的Master信息。
    PS:更多Sentinel的可订阅事件参见官方文档。

    sentinel.conf中的notification-script
    在sentinel.conf中可以配置多个sentinel notification-script , 如sentinel notification-script mymaster ./check.sh
    这个是在群集failover时会触发执行指定的脚本。脚本的执行结果若为1,即稍后重试(最大重试次数为10);若为2,则执行结束。并且脚本最大执行时间为60秒,超时会被终止执行。

    PS:目前会存在该脚本被执行多次的问题,查找资料有人解释是:
    脚本分为两个级别, SENTINEL_LEADER 和 SENTINEL_OBSERVER ,前者仅由领头 Sentinel 执行(一个 Sentinel),而后者由监视同一个 master 的所有 Sentinel 执行(多个 Sentinel)。