Redis M/S + Keepalived 主从备份高可用

目录

高可用

高可用是指服务器可以正常访问的时间,衡量的标准是在多长时间内可以提供正常服务(99.9%、99.99%、99.999% 等等)。而在 Redis 的语境中,高可用的含义似乎要更宽泛一些,除了保证提供正常服务(如:主从分离、快速容灾技术等),还需要考虑数据容量的扩展、数据安全不会丢失等。

Redis 的高可用

在 Redis 中,实现高可用的技术主要包括持久化、复制、哨兵和集群:

  • 持久化:即将数据存储在硬盘,保证数据不会因进程退出而丢失,主要作用是数据备份。
  • 复制:主要实现了数据的多机备份以及对于读操作的负载均衡和简单的故障恢复。缺陷是故障恢复无法自动化、写操作无法负载均衡、存储能力受到单机的限制。
  • 哨兵:在复制的基础上,哨兵实现了自动化的故障恢复。缺陷是写操作无法负载均衡、存储能力受到单机的限制。
  • 集群:通过集群,Redis 解决了写操作无法负载均衡以及存储能力受到单机限制的问题,实现了较为完善的高可用方案。

本文主要讨论通过 Redis 的 “复制” 技术支撑的高可用方案。

Redis 主从复制配置

Redis 主从复制实际上就是将 MASTER 节点的数据,复制到其他 SLAVE 节点去进行存储。

修改关键配置:

  • MASTER
bind 0.0.0.0
port 6379
slave-serve-stale-data yes
slave-read-only no
...
  • SLAVE
bind 0.0.0.0
port 6379
slave-serve-stale-data yes
slave-read-only no
slaveof 172.16.81.140 6379
...

可以看见,Redis 主从配置中最主要的一个项目就是 slaveof,他指定了 SLAVE 节点与 MASTER 节点的从属关系。

重启 Redis Daemon 之后,查看配合是否有生效:

  • MASTER
$ redis-cli
127.0.0.1:6379> INFO
...
# Replication
role:master
connected_slaves:1
slave0:ip=172.18.22.202,port=6379,state=online,offset=108458,lag=0
master_repl_offset:108458
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:22541
repl_backlog_histlen:85918
  • SLAVE
$ redis-cli
127.0.0.1:6379> INFO
...
# Replication
role:slave
master_host:172.18.22.204
master_port:6379
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_repl_offset:108654
slave_priority:100
slave_read_only:0
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

可以看见 MASTER 和 SLAVE 当前是互相认证的:在 MASTER 上可以查看分别由哪些 SLAVE,状态如何,复制(Replication)的配置如何;在 SLAVE 上可以查看自己的 MASTER 是谁,状态如何,自己的权重是多少,是否只读。如果是只读的话,SLAVE 将无法写入任何数据:

127.0.0.1:6379> SET test2 123
(error) READONLY You can't write against a read only slave.

下面进行一次同步测试:

  • MASTER
$ redis-cli
127.0.0.1:6379> SET test1 123
OK
  • SLAVE
$ redis-cli
127.0.0.1:6379> GET test1
"123"

Redis 主从切换(手动方式)

当 MASTER 宕机时,最简单的恢复方式就是使用手动切换的方式,手动的将一台从节点切换成主节点。手动方式显然是不推荐的,但我们不放了解一下。

  1. 关闭 MASTER。
systemctl stop redis
  1. 手动将 SLAVE 设置成主节点。
$ redis-cli
127.0.0.1:6379> slaveof no one
127.0.0.1:6379> INFO
...
# Replication
role:master
connected_slaves:0
master_repl_offset:1177
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
  1. 新的数据写入到 SLAVE。
127.0.0.1:6379> SET test4 123
OK
127.0.0.1:6379> SET test5 123
OK
127.0.0.1:6379> SET test6 123
OK

下面为执行数据恢复的步骤:

  1. 持久化 SLAVE 的数据。
127.0.0.1:6379> save
OK
  1. 拷贝从节点的 dump.rdb 文件到主节点中。
scp /var/lib/redis/dump.rdb root@<master>:/var/lib/redis/dump.rdb
  1. 重启 MASTER 和 SLAVE,发现 MASTER 和 SLAVE 的数据又保持了同步,而且身份角色也恢复到了初始状态。这是因为:当 Redis 重启时,手动执行的主从切换设置将会失效,还原为初始状态,因为我们在上面已写入了 SLAVE 的配置文件。

SLAVEOF 指令

SLAVEOF 指令指定了当前 Redis 实例是从属于某个 MASTER 的 SLAVE。如果这个指令在配置文件中写死,那么实例重启后就永远是 SLAVE,除非有哨兵将它提升为 MASTER,或手动执行指令 SLAVEOF NO ONE。

在本文讨论的 M/S 场景中,则需要脚本或手动执行。而在哨兵模式的场景中,这个指令会被哨兵动态地从配置文件中添加或删除,它的存在与否最好交由哨兵决定。需要注意的是,该指令不应该写死在 “子文件” 中,因为子文件中写死的指令是无法被哨兵移除的,这将导致 SLAVE 每次重启后都是 SLAVE。这个问题很难排查。

Redis M/S + Keepalived

M/S + Keepalived 是一个非常经典的 Redis 高可用方案,是哨兵模式出现之前的主流方案。现在常见与双节点的 Redis 高可用需求场景(哨兵模式需要三节点)。此方案使用了 Redis 原生的主从复制机制结合 Keepalived 的 VRRP 技术:Redis M/S 提供数据持久化和备份策略,Keepalived 提供了健康检查、监控告警、故障切换以及统一的 VIP 访问接口

优点

  • 高可靠性。双机主备架构、数据持久化以及备份策略。
  • 秒级切换。
  • 故障切换对应用透明。
  • 部署简单,维护成本低。

缺点

  • Redis 主从切换需要自定义脚本实现。
  • Keepalived 存在主从脑裂风险。

故障的 3 种情况

  1. Keepalived 挂了,同时 Redis 也挂了,这样的话 VIP 飘走之后,是不需要进行 Redis 数据同步的,因为 Redis 已经挂了,你也无法去 MASTER 上同步,会损失已经写在 MASTER 上但还没同步到 SLAVE 上面的这部分数据。

  2. Keepalived 挂了,Redis 没挂,这时 VIP 飘走后,Redis 的 MASTER/SLAVE 还是老的对应关系。默认情况下,会把数据写入 Redis SLAVE 中,而不会同步到 MASTER 上去,这时就要借助监控脚本反转 Redis 的身份关系了。并且需要预留一点时间里进行数据同步,然后切换主从关系。

  3. Keepalived 没挂,Redis 挂了,这时根据监控脚本检测到 Redis 挂了,马上降低 Keepalived Master 的优先级,导致 VIP 飘走,情况和第二种一样,也是需要进行数据同步,然后 Redis 主从切换。

主节点配置 keepalived.conf

! Configuration File for keepalived

global_defs {
   notification_email {
     [email protected]
     [email protected]
     [email protected]
   }
   notification_email_from [email protected]
   smtp_server 192.168.200.1
   smtp_connect_timeout 30
   router_id redis01
}

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

vrrp_instance VI_1 {
    state MASTER
    interface eno16777984
    virtual_router_id 51
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }

    track_script {
        chk_redis
    }
    virtual_ipaddress {
        172.16.81.139
    }
    
    notify_master /etc/keepalived/script/redis_master.sh
    notify_backup /etc/keepalived/script/redis_backup.sh
    notify_fault  /etc/keepalived/script/redis_fault.sh  
    notify_stop   /etc/keepalived/script/redis_stop.sh
}
  • notify_master:Keepalived 切换为主节点时执行的脚本。
  • notify_backup:Keepalived 切换为从节点时执行的脚本。
  • notify_fault:Keepalived 进程故障时执行的脚本。
  • notify_stop:keepalived 进程停止前执行的脚本。
  • nopreempt:设置不抢占,这里只能设置在 state 为 backup 的节点上,而且这个节点的优先级必须别另外的高。

从节点配置 keepalived.conf

! Configuration File for keepalived

global_defs {
   notification_email {
     [email protected]
     [email protected]
     [email protected]
   }
   notification_email_from [email protected]
   smtp_server 192.168.200.1
   smtp_connect_timeout 30
   router_id redis02
}

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

vrrp_instance VI_1 {
    state BACKUP
    interface eno16777984
    virtual_router_id 51
    priority 99
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }

    track_script {
        chk_redis
    }
    virtual_ipaddress {
        172.16.81.139
    }
    
    notify_master /etc/keepalived/script/redis_master.sh
    notify_backup /etc/keepalived/script/redis_backup.sh
    notify_fault  /etc/keepalived/script/redis_fault.sh  
    notify_stop  /etc/keepalived/script/redis_stop.sh
}

redis_check.sh

#!/bin/bash

CHECK=`/usr/local/bin/redis-cli PING`
if [ "$CHECK" == "PONG" ] ;then
      echo $CHECK
      exit 0
else 
      echo $CHECK
      service keepalived stop # 确保让出 MASTER
      exit 1
fi

keepalived 会根据该监控脚本的返回码来调整优先级:

  • 如果脚本返回码为 0,并且 weight > 0,则优先级相应的增加;
  • 如果脚本返回码为非 0,并且 weight < 0,则优先级相应的减少;
  • 其他情况,原本配置的优先级不变,即配置文件中 priority 对应的值。

NOTE:

  • 优先级不会不断的提高或者降低;
  • 可以编写多个检测脚本并为每个检测脚本设置不同的 weight(在配置中列出就行);
  • 不管提高优先级还是降低优先级,最终优先级的范围是在 [1, 254],不会出现优先级小于等于 0 或者优先级大于等于 255 的情况;
  • 配置 nopreempt ,避免正常情况下做无谓的切换

redis_master.sh

#!/bin/bash
REDISCLI="/usr/local/redis/bin/redis-cli -a 123456"
LOGFILE="/var/log/keepalived-redis-state.log"

sleep 15

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

$REDISCLI SLAVEOF 172.16.81.141 6379 >>$LOGFILE  2>&1 # 先同步数据
if [ $? -ne 0 ];then
    echo "data rsync fail." >>$LOGFILE 2>&1
else
    echo "data rsync OK." >> $LOGFILE  2>&1
fi

sleep 10 # 延迟 10 秒以后待数据同步完成后再取消同步状态 

echo "Run SLAVEOF NO ONE cmd ...">> $LOGFILE

$REDISCLI SLAVEOF NO ONE >> $LOGFILE 2>&1            # 切换为 MASTER
if [ $? -ne 0 ];then
    echo "Run SLAVEOF NO ONE cmd fail." >>$LOGFILE 2>&1
else
    echo "Run SLAVEOF NO ONE cmd OK." >> $LOGFILE  2>&1
fi

redis_backup.sh

#!/bin/bash 
REDISCLI="/usr/local/redis/bin/redis-cli -a 123456"
LOGFILE="/var/log/keepalived-redis-state.log"

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

echo "Being slave...." >>$LOGFILE 2>&1
sleep 15 # 延迟 15 秒待数据被对方同步完成之后再切换主从角色 
echo "Run SLAVEOF cmd ...">> $LOGFILE

$REDISCLI SLAVEOF 172.16.81.141 6379 >>$LOGFILE  2>&1    # 切换为 BACKUP

redis_fault.sh

#!/bin/bash 

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

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

redis_stop.sh

#!/bin/bash 

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

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

猜你喜欢

转载自blog.csdn.net/Jmilk/article/details/106639803