基于 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 复制代码
    我的脚本没有输出日志,这里就不看了

总结

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

相关推荐
彧azz几秒前
Linux 环境下 Redis 学习总结:数据类型、持久化、锁、事务、主从与缓存问题
linux·redis·笔记·学习·面试
lbb 小魔仙2 小时前
OpenClaw + cpolar 实战:远程 NAS、分享小游戏、RDP,再配置公网 AI 入口
数据库·人工智能·redis·oracle·prometheus
夕除3 小时前
redis--018
redis
两锭子4 小时前
Redis基础
redis
烟沙九洲4 小时前
Redis 缓存穿透、缓存击穿、缓存雪崩
数据库·redis
字节探索4 小时前
Redis 只会当缓存用?这 10 大实战场景,让你的系统快到飞起
redis·后端
于樱花森上飞舞5 小时前
【Redis】哨兵详解
java·开发语言·数据库·redis
Rain的Java大神之路16 小时前
如何快速上传10G文件
java·spring boot·redis·后端·mysql·spring cloud·面试
Wang's Blog17 小时前
Java 接入Redis: 通用命令与键管理
java·服务器·redis
Wang's Blog17 小时前
Java 接入Redis: 列表集合与有序集合操作命令
java·服务器·redis