1. 为什么单机Redis迟早要出事
Redis这玩意儿,刚开始用的时候是真香。单机部署五分钟搞定,读写性能轻松上万QPS,拿来做缓存、做会话存储、做消息队列,几乎什么场景都能顶上去。我见过不少团队,项目初期直接apt install redis或者跑个Docker容器就上线了,业务跑得也挺欢。但问题在于,这种"裸奔"状态下的Redis,本质上就是一个单点。单点意味着什么?意味着只要这台机器宕机、网络抖动、磁盘写满、进程被OOM Killer干掉,你的整个缓存层就瞬间归零。
缓存雪崩这个词大家应该不陌生。当Redis挂掉的那一刻,所有原本打在缓存上的请求会像洪水一样直接冲到后端数据库。如果你的数据库扛得住还好,扛不住就是连锁反应——数据库被打挂,整个服务不可用。这不是危言耸听,我自己就经历过一次凌晨两点的告警:Redis所在的那台云主机因为宿主机故障重启,缓存全部失效,MySQL连接数瞬间飙到上限,业务停了将近二十分钟。那次之后我就下定决心,Redis的高可用必须做,而且要认真做。
这篇文章要聊的,就是怎么用Keepalived配合Redis主从复制,搭一套成本可控、切换自动、运维简单的高可用方案。核心思路很朴素:一台主库负责写,一台或多台从库负责读并实时同步数据,再用Keepalived在两者之上维护一个虚拟IP(VIP)。平时VIP飘在主库上,客户端连VIP就行;一旦主库出问题,Keepalived检测到之后自动把VIP漂移到从库,同时把从库提升为新主库,客户端几乎无感知。这套方案适合中小规模团队、预算有限但又不能接受Redis单点故障的场景。如果你正在为Redis的可用性发愁,或者想找一个比哨兵集群更轻量的方案,那接下来的内容应该对你有用。
2. 方案整体设计与核心思路拆解
2.1 为什么选Keepalived而不是Redis Sentinel
提到Redis高可用,很多人第一反应是官方自带的Sentinel哨兵。哨兵确实成熟,能自动做故障检测、主从切换、配置中心,功能很完整。但它也有几个让人头疼的地方。第一,哨兵本身又是一组需要维护的进程,通常至少三个节点才能形成多数派,部署和运维成本上去了。第二,哨兵切换主从之后,客户端需要支持哨兵协议去重新发现主库地址,如果你的客户端是老代码或者用的库不支持,就得改代码。第三,哨兵切换过程中,配置传播和客户端感知存在一定延迟,某些场景下会出现短暂的写入失败。
Keepalived的方案逻辑完全不同。它工作在更底层的位置,通过VRRP协议(虚拟路由冗余协议)在多个节点之间选举出Master,由Master持有VIP。对客户端来说,它只知道一个VIP地址,根本不关心背后到底是哪台机器在提供服务。这种"IP漂移"的方式,对应用层是完全透明的,不需要改任何客户端代码。而且Keepalived本身非常轻量,一个配置文件加一个健康检查脚本就能跑起来,学习曲线平缓。
当然,Keepalived方案也有它的取舍。它不像哨兵那样自带Redis集群拓扑管理,主从关系的维护、切换后的角色调整,需要我们自己写脚本来完成。换句话说,哨兵是"Redis原生高可用",Keepalived是"基础设施层高可用",后者更通用,但需要我们自己把Redis那一层的逻辑补齐。理解了这一点,后面的脚本设计就顺理成章了。
2.2 主从复制是整个方案的基石
Keepalived能漂移VIP,但它不会凭空变出数据。如果从库上没有主库的数据,VIP漂过去也是白搭。所以Redis主从复制是这套方案的地基。主从复制的原理不复杂:从库启动后,向主库发送PSYNC命令,主库执行一次全量同步(生成RDB快照发给从库),之后所有写命令通过增量方式持续推送给从库。从库默认是只读的,这样能保证数据一致性。
这里有个关键点必须说清楚:Redis主从复制是异步的。主库写完内存就返回成功,不会等从库确认。这意味着在主库宕机的瞬间,可能有一小部分最新写入的数据还没同步到从库。这部分数据在切换后会丢失。这是异步复制的固有代价,无法完全避免。我们能做的,是通过合理配置repl-backlog-size、监控主从延迟等手段,把丢失窗口压到最小。如果你的业务对数据一致性要求极高,那可能需要考虑Redis Cluster或者其他强同步方案,但那已经超出本文范围了。
2.3 VIP漂移与主从角色切换的联动逻辑
整套方案最精妙的地方,在于VIP漂移和主从角色切换这两个动作必须联动。设想一下:主库挂了,Keepalived把VIP漂到从库,但如果从库还是"从库"身份,它默认只读,客户端写进来会直接报错。所以VIP漂移的同时,必须有一个机制把从库提升为新的主库。
我的做法是在Keepalived的notify_master和notify_backup状态切换脚本里做文章。当某台机器被选举为Master(拿到VIP)时,触发notify_master脚本,脚本里执行SLAVEOF NO ONE把当前节点提升为主库;当某台机器变成Backup(失去VIP)时,触发notify_backup脚本,脚本里执行SLAVEOF <新主库IP> <端口>让它重新成为从库。这样,VIP在谁身上,谁就是主库,逻辑自洽。
注意:切换脚本里一定要加锁和幂等判断,避免Keepalived状态抖动导致脚本反复执行,把主从关系搞乱。我后面会给出带锁的实现。
2.4 整体架构与数据流向
把上面的思路串起来,最终架构是这样的:两台服务器(物理机或云主机都行),分别部署Redis和Keepalived。初始状态下,节点A是Redis主库且持有VIP,节点B是Redis从库。客户端统一连接VIP。节点A的写操作实时同步到节点B。Keepalived在两台机器之间通过VRRP心跳互相探测。当节点A的Redis进程或整机故障,节点B检测不到心跳,接管VIP,触发脚本提升自己为主库,客户端继续通过VIP读写,业务恢复。
这套架构的影响范围其实很广:它不只适用于Redis,换成MySQL、Nginx、RabbitMQ,只要是有主从概念的服务,都能套用这个"Keepalived+VIP+角色切换脚本"的模板。理解了Redis这个案例,你基本就掌握了一类高可用问题的通用解法。
3. 环境准备与核心配置实操
3.1 服务器规划与基础环境
先明确一下实验环境。我用两台配置相同的云主机来演示,操作系统选CentOS 7或者Rocky Linux 8都行,Redis版本用6.2(5.x和7.x的配置差异不大,核心参数通用)。规划如下:
| 角色 | 主机名 | IP地址 | 说明 |
|---|---|---|---|
| 初始主库 | redis-node-a | 192.168.1.101 | 持有VIP,负责写 |
| 初始从库 | redis-node-b | 192.168.1.102 | 同步数据,待命 |
| 虚拟IP | - | 192.168.1.100 | 客户端统一入口 |
两台机器需要做的准备工作:关闭防火墙或者放行相关端口(Redis的6379、Keepalived的VRRP协议需要放行),配置好主机名解析,确保两台机器时间同步(用chronyd或ntpd都行,时间不同步会导致Keepalived心跳判断异常)。另外,确保两台机器的网卡名称一致,因为Keepalived配置里要指定网卡,如果一台是eth0另一台是ens33,配置就得分别写,容易出错。
# 两台机器都执行,安装必要组件 yum install -y epel-release yum install -y redis keepalived systemctl enable redis keepalived提示:生产环境建议用官方源或编译安装Redis,系统自带的版本可能偏旧。这里为了演示方便用yum,实际部署时按团队规范来。
3.2 Redis主从复制配置详解
Redis的配置文件在/etc/redis.conf(yum安装)或/etc/redis/redis.conf(编译安装)。主库和从库的配置有区别,我分开说。
主库(redis-node-a)的关键配置:
bind 0.0.0.0 protected-mode no port 6379 daemonize yes requirepass YourStrongPassword123 masterauth YourStrongPassword123 appendonly yes appendfsync everysec repl-backlog-size 64mb这里有几个参数值得展开讲。bind 0.0.0.0让它监听所有网卡,因为VIP会漂移,不能只绑固定IP。requirepass和masterauth都要设,前者是客户端连接密码,后者是主从之间同步用的密码,两个设成一样最省事。appendonly yes开启AOF持久化,虽然对性能有一点影响,但能大幅降低宕机丢数据的风险,高可用场景强烈建议开。repl-backlog-size是复制积压缓冲区大小,默认1MB在高写入场景下很容易被写满,导致从库重连时触发全量同步,我一般调到64MB起步。
从库(redis-node-b)的配置,在主库基础上加一行:
replicaof 192.168.1.101 6379这行告诉从库去同步哪个主库。注意Redis 5.0之后用replicaof,老版本用slaveof,两者都兼容,但新配置建议用replicaof。
配置改完,先启动主库,再启动从库。然后在主库上执行redis-cli -a 密码 info replication,看到role:master和connected_slaves:1就说明主从通了。在从库上执行同样的命令,应该看到role:slave和master_link_status:up。
3.3 Keepalived配置文件逐行拆解
Keepalived的配置文件在/etc/keepalived/keepalived.conf。这是整套方案的核心,我把它拆开讲。先看主库节点(redis-node-a)的配置:
global_defs { router_id redis_node_a script_user root enable_script_security } vrrp_script chk_redis { script "/etc/keepalived/check_redis.sh" interval 2 weight -20 fall 2 rise 2 } vrrp_instance VI_REDIS { state MASTER interface eth0 virtual_router_id 51 priority 100 advert_int 1 authentication { auth_type PASS auth_pass 123456 } virtual_ipaddress { 192.168.1.100/24 } track_script { chk_redis } notify_master "/etc/keepalived/redis_master.sh" notify_backup "/etc/keepalived/redis_backup.sh" notify_fault "/etc/keepalived/redis_fault.sh" }逐行解释一下关键项。router_id是节点标识,两台机器必须不同。vrrp_script定义健康检查脚本,interval 2表示每2秒执行一次,weight -20表示检查失败时优先级减20,fall 2表示连续失败2次才认为真的故障(避免误判),rise 2表示连续成功2次才恢复。virtual_router_id是VRRP组ID,两台机器必须一致,否则组不成一个VRRP组。priority是优先级,数值大的当Master,主库设100,从库设90。advert_int 1是心跳间隔1秒。authentication是VRRP认证,两台必须一致。virtual_ipaddress就是那个VIP。track_script关联健康检查。notify_master/backup/fault是状态切换时触发的脚本。
从库节点(redis-node-b)的配置几乎一样,只有三处不同:router_id改成redis_node_b,state改成BACKUP,priority改成90。其他全部保持一致。
注意:
state MASTER/BACKUP只是初始状态,实际谁是Master由优先级和健康检查结果动态决定。如果主库健康检查失败,优先级降到80,从库的90就赢了,VIP自动漂过去。
3.4 健康检查脚本的编写要点
check_redis.sh这个脚本决定了Keepalived怎么判断Redis是否健康。最简单的写法是检测进程是否存在,但这样不够严谨——进程活着不代表能提供服务。更靠谱的做法是实际发一个PING命令看有没有响应:
#!/bin/bash REDIS_PASS="YourStrongPassword123" redis-cli -a $REDIS_PASS ping 2>/dev/null | grep -q "PONG" if [ $? -eq 0 ]; then exit 0 else exit 1 fi脚本返回0表示健康,非0表示故障。Keepalived根据返回值决定是否降权。这里有个细节:redis-cli -a会打印一行警告到stderr,所以用2>/dev/null屏蔽掉,避免干扰判断。脚本写完记得chmod +x加执行权限。
实操心得:健康检查脚本里不要做太重的操作,比如执行
INFO拉一大堆数据,高频执行会消耗资源。PING是最轻量的探测方式,够用。另外,脚本执行超时也会被Keepalived判定为失败,所以脚本逻辑要尽量简单快速。
4. 主从切换脚本与完整实操流程
4.1 notify_master脚本:提升自己为主库
当一台机器被选举为Master(拿到VIP)时,notify_master脚本被触发。它的任务是:把自己提升为Redis主库,同时确保其他从库指向自己。脚本内容如下:
#!/bin/bash REDIS_PASS="YourStrongPassword123" LOG_FILE="/var/log/keepalived_redis.log" LOCK_FILE="/tmp/redis_master.lock" # 加锁,防止脚本重复执行 exec 200>$LOCK_FILE flock -n 200 || exit 0 echo "$(date) - 本节点成为MASTER,开始提升为主库" >> $LOG_FILE # 提升自己为主库 redis-cli -a $REDIS_PASS SLAVEOF NO ONE >> $LOG_FILE 2>&1 # 可选:如果有多台从库,这里可以遍历通知它们指向新主库 # 本例只有两台,从库的notify_backup会自己处理 echo "$(date) - 主库提升完成" >> $LOG_FILESLAVEOF NO ONE是Redis的经典命令,作用是断开主从关系,把自己变成独立的主库。执行后,这台机器的role会变成master,可以接受写入了。加锁是为了防止Keepalived状态抖动时脚本被反复调用,导致主从关系混乱。
4.2 notify_backup脚本:降级为从库并指向新主库
当一台机器失去VIP变成Backup时,notify_backup脚本被触发。它要把自己降级为从库,并指向当前的新主库。问题来了:新主库的IP是什么?在VIP漂移的场景下,新主库就是持有VIP的那台机器,但脚本执行时VIP可能还没完全稳定。我的做法是用对端节点的真实IP来指定,而不是用VIP:
#!/bin/bash REDIS_PASS="YourStrongPassword123" PEER_IP="192.168.1.101" # 从库节点上,这里填主库节点的真实IP LOG_FILE="/var/log/keepalived_redis.log" LOCK_FILE="/tmp/redis_backup.lock" exec 200>$LOCK_FILE flock -n 200 || exit 0 echo "$(date) - 本节点成为BACKUP,降级为从库,指向 $PEER_IP" >> $LOG_FILE redis-cli -a $REDIS_PASS SLAVEOF $PEER_IP 6379 >> $LOG_FILE 2>&1 echo "$(date) - 从库配置完成" >> $LOG_FILE注意,从库节点上的PEER_IP填主库节点的真实IP,主库节点上的PEER_IP填从库节点的真实IP。这样无论谁变成Backup,都能正确指向对方。用真实IP而不是VIP,是因为VIP漂移过程中可能有短暂的不稳定,真实IP更可靠。
4.3 notify_fault脚本:故障状态的处理
notify_fault在节点进入故障状态时触发,通常用于记录日志和告警。这个脚本可以简单一点:
#!/bin/bash LOG_FILE="/var/log/keepalived_redis.log" echo "$(date) - 节点进入FAULT状态,请检查Redis和网络" >> $LOG_FILE # 这里可以接入告警系统,比如发邮件、调webhook三个脚本都要放在/etc/keepalived/目录下,并且chmod +x赋予执行权限。Keepalived配置里script_user root和enable_script_security这两行是为了让脚本以root身份执行,避免权限问题。
4.4 完整部署与验证流程
配置全部就绪后,按这个顺序操作:
- 两台机器分别启动Redis:
systemctl start redis - 验证主从关系:在主库执行
info replication确认从库已连接 - 两台机器分别启动Keepalived:
systemctl start keepalived - 在任意一台机器执行
ip addr,确认VIP在优先级高的节点上 - 用
redis-cli -h 192.168.1.100 -a 密码连接VIP,执行set test 1,成功说明主库正常 - 在从库上执行
get test,能读到值说明复制正常
接下来做故障切换演练,这是最关键的一步。在主库上执行systemctl stop redis模拟Redis故障。等待几秒(健康检查间隔2秒,fall 2次,大约4-6秒),观察VIP是否漂移到从库。然后在从库上执行info replication,应该看到role:master。再用客户端连VIP写入,应该成功。最后把原主库的Redis重新启动,它会自动通过notify_backup降级为从库,重新同步数据。
提示:演练时一定要盯着
/var/log/keepalived_redis.log和/var/log/messages,切换过程中的每一步都有日志,出问题了好排查。
5. 常见问题与排查技巧实录
5.1 脑裂问题:两台机器都认为自己是主库
脑裂是Keepalived方案最怕遇到的问题。现象是两台机器都持有VIP(或者都认为自己是Master),都接受写入,数据彻底乱套。产生原因通常是网络分区:两台机器之间的VRRP心跳断了,但各自和客户端还通着,于是都觉得自己该当Master。
防范脑裂有几个手段。第一,配置nopreempt(非抢占模式),避免节点恢复后抢回VIP导致抖动。第二,在健康检查脚本里加入对VIP的检测,如果发现自己没有VIP却还是Master状态,就主动降级。第三,也是最有效的,在Redis层面加保护:给主库配置min-replicas-to-write 1和min-replicas-max-lag 10,意思是至少要有1个从库且延迟不超过10秒,主库才允许写入。这样即使脑裂,那个没有从库的"假主库"会拒绝写入,避免数据分叉。
# 主库配置中加入 min-replicas-to-write 1 min-replicas-max-lag 105.2 VIP漂移了但Redis没切换角色
这种情况通常是notify_master脚本没执行或者执行失败。排查步骤:先看/var/log/messages里Keepalived有没有报脚本执行错误,再看/var/log/keepalived_redis.log有没有记录。常见原因有三个:脚本没有执行权限、脚本里的redis-cli路径不对(Keepalived执行环境PATH可能和登录shell不同,建议用绝对路径/usr/bin/redis-cli)、密码错误导致命令失败。我踩过的坑就是PATH问题,脚本里写redis-cli在手动执行时没问题,但Keepalived调用时找不到命令,改成绝对路径就好了。
5.3 主从同步延迟过大
如果从库的master_link_status是up但延迟很高,先看info replication里的master_repl_offset和slave_repl_offset差值。差值大说明从库跟不上。原因可能是网络带宽不足、从库磁盘IO慢、或者主库写入量太大。优化方向:调大repl-backlog-size避免全量同步、从库开启repl-diskless-load on用无盘加载加快同步、检查网络质量。如果长期延迟高,考虑升级从库硬件或者做读写分离把读压力分散。
5.4 切换后原主库数据比新主库新
这是异步复制的固有问题。原主库宕机前最后几条写入还没同步到从库,切换后这几条数据就丢了。如果业务不能接受,可以在应用层做补偿,比如写入时同时记录到消息队列,切换后重放。或者在切换脚本里,如果原主库还能连上,先尝试把它剩余的数据同步过来再降级。但这属于进阶操作,大多数场景下,接受秒级的数据丢失窗口是合理的。
5.5 常见问题速查表
| 问题现象 | 可能原因 | 排查方向 | 解决手段 |
|---|---|---|---|
| VIP不漂移 | 健康检查脚本误判 | 手动执行脚本看返回值 | 修正脚本逻辑,调整fall/rise |
| 切换后无法写入 | 从库未提升为主库 | 查notify_master日志 | 检查脚本权限和路径 |
| 主从关系混乱 | 脚本重复执行 | 查锁文件是否生效 | 加flock锁,做幂等判断 |
| 数据不一致 | 脑裂或异步丢失 | 查两台role和offset | 加min-replicas保护 |
| Keepalived启动失败 | 配置文件语法错误 | keepalived -t -f 配置 | 逐行核对配置 |
实操心得:每次改完配置,先用
keepalived -t -f /etc/keepalived/keepalived.conf做语法检查,能省掉很多重启失败的麻烦。另外,生产环境一定要给VIP配一个监控,定时探测VIP的连通性和Redis的读写状态,别等用户报障了才发现问题。
6. 生产环境加固与扩展思路
6.1 从两节点扩展到多从库
两节点方案能解决高可用,但读性能没有提升。如果读压力大,可以加从库。做法很简单:新加一台机器,Redis配置里replicaof指向当前主库,Keepalived配置里state BACKUP、priority设成比现有从库低(比如80),健康检查脚本和切换脚本照抄。这样读请求可以分散到多个从库,主库只负责写。需要注意的是,从库越多,主库的复制压力越大,repl-backlog-size要相应调大。
6.2 加入监控告警闭环
高可用方案不能没有监控。至少要监控这几个指标:VIP是否可达、Redis主从角色、主从复制延迟、Redis内存使用率、连接数。用Prometheus加redis_exporter采集指标,Grafana做面板,配Alertmanager告警。切换事件一定要告警,因为每次切换都意味着有节点出问题了,需要人工介入排查根因。我一般会配两条告警:一条是VIP漂移告警(说明发生了切换),一条是主从延迟超过阈值告警(说明同步有问题)。
6.3 定期演练与预案维护
高可用方案最怕的是"配了但没验证过"。我建议每个季度至少做一次切换演练,包括:模拟Redis进程崩溃、模拟整机断电、模拟网络分区。每次演练后更新预案文档,记录切换耗时、数据丢失情况、遇到的问题。演练时最好在业务低峰期做,并且提前通知相关方。别等到真出故障了才发现脚本有bug,那时候损失就大了。
6.4 这套方案的边界与替代选择
最后说句实在话,Keepalived+Redis主从这套方案,适合的是中小规模、预算有限、能接受秒级数据丢失的场景。如果你的业务数据量很大、写入非常频繁、对一致性要求极高,那Redis Cluster或者云厂商的托管Redis可能是更好的选择。Cluster原生支持分片和自动故障转移,运维负担小,但成本高、架构复杂。选型没有绝对的对错,关键看你的业务需求和团队能力。我个人的经验是,先把Keepalived这套跑通,理解高可用的本质,再去评估更复杂的方案,心里会更有底。
这套环境我在几个项目里都落地过,最久的一套跑了两年多,经历过几次真实的宿主机故障,切换都在10秒内完成,业务基本无感知。踩过的坑主要集中在脚本权限和脑裂防护上,把这两块做扎实,稳定性就有保障了。