基于 Keepalived 的 Redis 主备高可用架构设计与实现


目录


背景

Redis 高可用我们常用的是主备+哨兵cluster集群来实现高可用和高可靠,除此之外,我们也可通过Keepalived + Redis主备的方式来实现高可用。

具体实现功能:

  • 客户端统一 VIP 入口读写;
  • 主备切换(故障转移),保证业务可用性;
  • 主备节点重启后(任意节点重启或同时重启),仍可继续通过 VIP 对外提供服务。

Redis 版本:v7.0.9

Keepalived 版本:v2.3.2

OS 版本:openEuler 22.03 LTS

架构

主备架构图如下:

流程

主备高可用(故障转移)流程图如下:

实现

Redis

部署

主:192.168.1.113

备:192.168.1.114

部署过程跳过,不作为重点。需要注意的是:该方式与传统的主备部署有些许差别,主备节点部署时均以单机方式部署(即部署时主备均为 master 节点,可读写),不通过配置文件来配置主备,而是通过 keepalived 的钩子脚本来动态配置 redis 主备,这也是本次的重点。

配置

主备 redis 实例配置应用程序连接密码(主备配置保持完全一致):

sh 复制代码
vim redis.conf
sh 复制代码
...
requirepass Asen!@#123
...

注意和主备数据同步连接时的密码(masterauth)的区别,和 requirepass 不是一个东西,masterauth 是用于主备连接时的鉴权密码,这个密码会通过 keepalived 的钩子脚本动态配置,因此这种配置的主备是临时的,redis实例或服务器重启后主备就会失效,但由于 keepalived 的角色切换都会触发其钩子脚本(包括开关机),因此基于该特性,我们可在钩子脚本中通过命令动态配置 redis 主备,进而弥补 redis 临时主备配置时重启失效的问题。

Keepalived

部署

主:192.168.1.113

备:192.168.1.114

部署过程跳过,不作为重点,但是主备配置文件、启动配置文件需要作为重点。

配置
主节点配置
sh 复制代码
vim keepalived.conf
sh 复制代码
! Configuration File for keepalived
  
global_defs {
   router_id redis-master  # 主备唯一
}

vrrp_script chk_server {
    script "/data/keepalived/check/chk_redis.sh"  # redis状态检测脚本
    interval 5
    fall 3
    rise 3
}

vrrp_instance VI_1 {
    state BACKUP  # 非抢占模式下主备均设置为BACKUP(详情请查看官方文档)
    interface eth0  # VRRP检测网卡(即通过那个网卡发心跳,可自定义)
    virtual_router_id 91   # 虚拟路由IP,主备需保持一致
    priority 50  # 权重:主大于备,主50,备40
    advert_int 1
    nopreempt  # VIP非抢占模式
    authentication {
        auth_type PASS
        auth_pass Arsen@123
    }
    virtual_ipaddress {
        192.169.1.235/24 dev eth0  # 设置VIP,并绑定到eth0网卡
    }
    track_script {
        chk_redis  # 调用redis状态检测脚本
    }
    track_interface {
        eth0  # 网卡状态检测
    }
    notify_master /data/keepalived/notify/notify_master.sh  # keepalived为主时触发该脚本,redis主备切换的关键
    notify_backup /data/keepalived/notify/notify_backup.sh  # keepalived为备时触发该脚本,redis主备切换的关键
}
sh 复制代码
vim notify_master.sh
sh 复制代码
#!/bin/bash
# 脚本功能:
# 1.keepalived提升为MASTER时触发该脚本
# 2.配置主备鉴权密码(CONFIG SET masterauth)
# 3.将当前redis实例提升为master,等待对端redis实例同步当前实例的数据(REPLICAOF NO ONE)
# 脚本已经被我精简过了,生产中需要加上相关日志记录

redis-cli -h 192.168.1.113 -p 6379 -a Asen!@#123 CONFIG SET masterauth "Asen!@#456" 
redis-cli -h 192.168.1.113 -p 6379 -a Asen!@#123 REPLICAOF NO ONE
sh 复制代码
vim notify_backup.sh
sh 复制代码
#!/bin/bash
# 脚本功能:
# 1.keepalived提升为BACKUP时触发该脚本
# 2.配置主备鉴权密码(CONFIG SET masterauth)
# 3.将当前redis实例降为slave,并同步对端redis实例的数据(REPLICAOF)
# 脚本已经被我精简过了,生产中需要加上相关日志记录

redis-cli -h 192.168.1.113 -p 6379 -a Asen!@#123 CONFIG SET masterauth "Asen!@#456" 
redis-cli -h 192.168.1.113 -p 6379 -a Asen!@#123 REPLICAOF 192.168.1.114 6379
备节点配置
sh 复制代码
vim keepalived.conf
sh 复制代码
! Configuration File for keepalived
  
global_defs {
   router_id redis-slave
}

vrrp_script chk_server {
    script "/data/keepalived/check/chk_redis.sh"
    interval 5
    fall 3
    rise 3
}

vrrp_instance VI_1 {
    state BACKUP
    interface eth0
    virtual_router_id 91
    priority 40
    advert_int 1
    nopreempt
    authentication {
        auth_type PASS
        auth_pass Arsen@123
    }
    virtual_ipaddress {
        192.169.1.235/24 dev eth0
    }
    track_script {
        chk_redis
    }
    track_interface {
        eth0
    }
    notify_master /data/keepalived/notify/notify_master.sh
    notify_backup /data/keepalived/notify/notify_backup.sh
}
sh 复制代码
vim notify_master.sh
sh 复制代码
#!/bin/bash
# 脚本功能:
# 1.keepalived提升为MASTER时触发该脚本
# 2.配置主备鉴权密码(CONFIG SET masterauth)
# 3.将当前redis实例提升为master,等待对端redis实例同步当前实例的数据(REPLICAOF NO ONE)
# 脚本已经被我精简过了,生产中需要加上相关日志记录

redis-cli -h 192.168.1.114 -p 6379 -a Asen!@#123 CONFIG SET masterauth "Asen!@#456" 
redis-cli -h 192.168.1.114 -p 6379 -a Asen!@#123 REPLICAOF NO ONE
sh 复制代码
vim notify_backup.sh
sh 复制代码
#!/bin/bash
# 脚本功能:
# 1.keepalived提升为BACKUP时触发该脚本
# 2.配置主备鉴权密码(CONFIG SET masterauth)
# 3.将当前redis实例降为slave,并同步对端redis实例的数据(REPLICAOF)
# 脚本已经被我精简过了,生产中需要加上相关日志记录

redis-cli -h 192.168.1.114 -p 6379 -a Asen!@#123 CONFIG SET masterauth "Asen!@#456" 
redis-cli -h 192.168.1.114 -p 6379 -a Asen!@#123 REPLICAOF 192.168.1.113 6379
Systemd 管理配置

至此,已经可以实现在主备服务器不同时重启情况下的高可用了。

但是主备服务器同时重启的话,极有可能主备的 redis 实例的状态均为 master,为什么呢?试想一下:如果 keepalived 先于 redis 启动,那 keepalived 就会先提前执行其钩子脚本,此时钩子脚本对 redis 实例的一系列操作均是无效的(因为 redis 服务还没启动完成),等 redis 服务启动完成后,主备就没有生效。

因此为了实现真正的高可用,我们还需要设置一下 keepalived 和 redis 的启动顺序,保证 redis 先启动完成后再启动 keepalived,这样 keepalived 的钩子脚本就对 redis 起作用了(即完成 redis 主备的配置)。

但是 keepalived 不可能一直等待 redis 实例启动完成后再启动,如果 redis 本身就异常(如文件系统损坏)无法启动,这样的话 keepalived 将一直处于等待状态,因此我们在配置 keepalived 启动文件时,可加上一个超时时间,如 1 分钟,如果 1 分钟后 redis 实例仍未启动,则放弃等待直接进入后续启动。

具体配置如下:

sh 复制代码
vim /etc/systemd/system/keepalived.service
sh 复制代码
[Unit]
Description=LVS and VRRP High Availability Monitor
After=network-online.target syslog.target 
Wants=network-online.target 
Documentation=man:keepalived(8)
Documentation=man:keepalived.conf(5)
Documentation=man:genhash(1)
Documentation=https://keepalived.org

[Service]
Type=forking
PIDFile=/run/keepalived.pid
KillMode=process
EnvironmentFile=-/data/keepalived/etc/sysconfig/keepalived
ExecStartPre=/bin/bash -c 'for i in {1..60}; do if ss -tln | grep -q ":6379"; then echo "Port 6379 is ready"; break; fi; sleep 1; done'
ExecStart=/data/keepalived/sbin/keepalived -f /data/keepalived/etc/keepalived/keepalived.conf $KEEPALIVED_OPTIONS
ExecReload=/bin/kill -HUP $MAINPID

[Install]
WantedBy=multi-user.target

1 分钟过后不管 redis 实例是否启动成功,则会继续执行 ExecStart 部分,启动 keepalived,这种情况只能人工介入修复 redis 主备。

验证

场景:主 redis 实例异常、网卡/线异常或所在服务器宕机

初始状态:主 113, 备 114。

模拟故障:

  1. 关闭 113 的 redis 实例

    sh 复制代码
    systemctl stop redis
  2. 分别查看 113、114 的 keepalived 的日志

    sh 复制代码
    # 113日志(keepalived.log)
    # 日志分析:
    #   Dec 27 00:04:06 停止了redis实例 VRRP_Script(chk_redis) failed (exited with status 1)
    #   Dec 27 00:04:06 进入了FAULT状态,注意不是BACKUP状态哦 (VI_1) Entering FAULT STATE
    #   Dec 27 00:04:06 发送了优先级为 0 的 VRRP 通告包,表示"放弃主节点角色" (VI_1) sent 0 priority
    #   Dec 27 00:04:06 释放VIP (VI_1) removing VIPs.
    #   注意:此时并不会执行notify_backup脚本,而是只会执行notify_fault脚本(但我们并没有编写该脚步,因此略过)
    #   Dec 27 00:06:11 这个时间点我们恢复了redis实例,检测脚本返回值为0 Script `chk_server` now returning 0
    #   Dec 27 00:06:21 检查脚本正常 VRRP_Script(chk_server) succeeded
    #   Dec 27 00:06:21 随着进入BACKUP状态,此时就会触发notify_backup脚本 (VI_1) Entering BACKUP STATE
    ...
    Dec 27 00:04:06 tai Keepalived_vrrp[4261]: VRRP_Script(chk_redis) failed (exited with status 1)
    Dec 27 00:04:06 tai Keepalived_vrrp[4261]: (VI_1) Entering FAULT STATE
    Dec 27 00:04:06 tai Keepalived_vrrp[4261]: (VI_1) sent 0 priority
    Dec 27 00:04:06 tai Keepalived_vrrp[4261]: (VI_1) removing VIPs.
    ...
    Dec 27 00:06:11 tai Keepalived_vrrp[4261]: Script `chk_server` now returning 0
    Dec 27 00:06:21 tai Keepalived_vrrp[4261]: VRRP_Script(chk_server) succeeded
    Dec 27 00:06:21 tai Keepalived_vrrp[4261]: (VI_1) Entering BACKUP STATE
    ...
    
    # 114日志(keepalived.log)
    # 日志分析: 
    #    Dec 27 00:04:06 当前节点(备节点)收到了主节点发送的优先级为0的通告 (VI_1) Backup received priority 0 advertisement
    #    Dec 27 00:04:07 备份节点在预期时间内没有再收到任何VRRP通告(即超时了)(VI_1) Receive advertisement timeout
    #    Dec 27 00:04:07 进入MASTER状态,触发notify_backup脚本 (VI_1) Entering MASTER STATE
    #    Dec 27 00:04:07 绑定VIP (VI_1) setting VIPs.
    #    Dec 27 00:04:07 准备在eth0网卡上为IP地址192.168.1.235发送ARP(即正在发送或排队发送ARP包)Sending/queueing
    #    Dec 27 00:04:07 正在发送免费ARP包,通知整个网络段:"192.168.1.235 的MAC地址已经改变了" Sending gratuitous ARP
    ...
    Dec 27 00:04:06 tai Keepalived_vrrp[4301]: (VI_1) Backup received priority 0 advertisement
    Dec 27 00:04:07 tai Keepalived_vrrp[4301]: (VI_1) Receive advertisement timeout
    Dec 27 00:04:07 tai Keepalived_vrrp[4301]: (VI_1) Entering MASTER STATE
    Dec 27 00:04:07 tai Keepalived_vrrp[4301]: (VI_1) setting VIPs.
    Dec 27 00:04:07 tai Keepalived_vrrp[4301]: (VI_1) Sending/queueing gratuitous ARPs on eth0 for 192.168.1.235
    Dec 27 00:04:07 tai Keepalived_vrrp[4301]: Sending gratuitous ARP on eth0 for 192.168.1.235
    ...
  3. 分别查看 113、114 的 keepalived 的钩子脚本日志

    sh 复制代码
    我的脚本没有输出日志,这里就不看了

总结

实践是检验真理的唯一标准!欢迎前来探讨,共同学习,共同进步。

相关推荐
222you3 小时前
在云服务器上配置redis环境(OpenCloudOS)
数据库·redis·缓存
Zhen (Evan) Wang4 小时前
Docker 完整安装 Redis
redis·docker·容器
-Xie-4 小时前
Redis(十六)——底层数据结构(一)
java·数据结构·redis
MZWeiei15 小时前
Redis持久化机制中的 AOF机制简单介绍
数据库·redis
JoannaJuanCV18 小时前
自动驾驶—CARLA仿真(30)交通管理器(Traffic Manager)
java·redis·自动驾驶
java1234_小锋18 小时前
Redis的热Key问题如何解决?
数据库·redis·缓存
jmxwzy19 小时前
点赞系统问题
java·redis·tidb·pulsar
鸽鸽程序猿19 小时前
【Redis】事务
数据库·redis·缓存
东东的脑洞20 小时前
【面试突击】Redis 主从复制核心面试知识点
redis·面试·职场和发展