
1. 为什么单靠Redis主从复制扛不住真实故障很多人第一次搭Redis高可用脑子里想的都是主从复制配好主挂了从顶上——听起来很顺但真到线上跑一圈就会发现这套逻辑缺了最关键的一环谁来判定主挂了谁来通知从库切换谁来告诉客户端新主是谁。Redis自身的主从复制只负责数据同步它压根不管故障发现和自动切换。你手动SLAVEOF NO ONE当然可以但凌晨三点数据库挂了你确定能秒级爬起来操作这就是Keepalived要补的位。它本质上是一个VRRP协议虚拟路由冗余协议的实现通过竞选机制在一组节点里选出一个主并对外暴露一个虚拟IPVIP。客户端永远连VIP不关心背后到底是哪台机器在扛。当主节点宕机VIP漂移到备节点配合Redis的角色切换脚本整个切换过程对客户端几乎透明。我见过太多团队在这块踩坑有人只配了Keepalived不管Redis角色结果VIP漂过去了但新主还是从库状态写入全失败有人脚本写得太粗暴脑裂时两台都抢着当主数据直接分叉。所以这篇文章我不打算只给你一份配置文件而是把每个参数为什么这么设、脚本里每一行在防什么、切换后怎么验证都讲透。先明确适用场景这套方案适合中小规模、对可用性要求高但预算有限的业务比如日活几十万的内部系统、中小型电商的订单缓存层、或者对延迟敏感的会话存储。如果你的QPS已经上万、数据量到TB级那应该考虑Redis Cluster或哨兵集群方案Keepalived主从更适合读多写少、单主足够扛的场景。注意Keepalived方案的核心假设是同一时刻只有一个主。任何可能导致双主的配置都是定时炸弹后面会专门讲脑裂防护。2. 环境规划与基础组件安装的取舍细节2.1 节点角色与IP规划动手之前先把规划做清楚不然后面改配置改到怀疑人生。我建议至少三台机器两台跑RedisKeepalived做互备第三台做独立的健康检查观察者可选但强烈推荐用来从外部视角验证VIP是否真的可达。角色主机名虚构内网IP说明Redis主Keepalived主node-a192.168.10.11初始MASTER持有VIPRedis从Keepalived备node-b192.168.10.12初始SLAVE待命观察节点node-c192.168.10.13只跑监控脚本不参与竞选虚拟IP—192.168.10.100客户端统一入口这里有个容易忽略的点VIP必须和节点在同一网段否则VRRP广播包发不出去竞选直接失败。另外网卡名称要确认清楚CentOS 7之后普遍是ens33或eth0用ip addr看一眼别照着老教程写eth0结果网卡根本不存在。2.2 安装Redis与KeepalivedRedis建议用源码编译或者官方仓库装版本选5.0以上支持更完善的主从同步机制。Keepalived直接用系统包管理器装最省事# 两台节点都执行 yum install -y keepalived # 验证版本建议2.0以上 keepalived --versionRedis的安装我倾向于源码编译因为可以自定义安装路径、方便多实例管理wget https://download.redis.io/releases/redis-6.2.14.tar.gz tar -zxvf redis-6.2.14.tar.gz cd redis-6.2.14 make make install PREFIX/usr/local/redis装完后把redis.conf复制到/etc/redis/下统一管理两台机器的配置文件除了replicaof指向不同其余保持一致。关键配置项我列一下这些是踩过坑之后定下来的bind 0.0.0.0 protected-mode no port 6379 daemonize yes pidfile /var/run/redis_6379.pid logfile /var/log/redis/redis.log dir /data/redis # 主从同步密码必须设否则切换后认证失败 masterauth YourStrongPass requirepass YourStrongPass # 从库只读防止误写 replica-read-only yes # 同步超时网络抖动时别急着断 repl-timeout 60提示masterauth和requirepass设成一样是最省心的做法因为切换后原从库变主它需要用同一个密码接受新从库的连接。如果两个密码不同切换脚本里还得动态改配置复杂度陡增。2.3 为什么不用Redis自带的哨兵这里必须解释一下选型逻辑不然有人会问哨兵不香吗。哨兵Sentinel确实能自动切换但它需要至少三个哨兵节点才能保证选举可靠而且哨兵本身也要高可用。对于只有两台机器的场景Keepalived的资源占用更小、切换更快通常1~3秒而且VIP漂移对客户端完全透明——客户端连的还是那个IP连重连逻辑都不用改。代价是Keepalived方案没有哨兵那种多节点投票的防脑裂能力所以我们必须靠脚本和网络层做补偿。这就是后面要重点讲的双保险设计。3. Keepalived配置文件里每个参数的真实作用3.1 主节点配置逐行拆解先看node-a初始主的/etc/keepalived/keepalived.confglobal_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 -30 fall 2 rise 2 } vrrp_instance VI_REDIS { state MASTER interface ens33 virtual_router_id 51 priority 100 advert_int 1 authentication { auth_type PASS auth_pass RedisHA2024 } virtual_ipaddress { 192.168.10.100/24 dev ens33 } track_script { chk_redis } notify_master /etc/keepalived/notify.sh master notify_backup /etc/keepalived/notify.sh backup notify_fault /etc/keepalived/notify.sh fault }逐条说人话router_id节点标识两台不能一样否则日志里分不清谁是谁。vrrp_script chk_redis定义健康检查脚本interval 2表示每2秒跑一次。这个间隔别设太短我试过设1秒结果Redis偶尔的慢查询导致脚本超时误判切换反而制造了故障。weight -30脚本失败时优先级减30。主节点优先级100减完变70低于备节点的90备节点通常设90于是触发切换。这个数值设计有讲究后面细说。fall 2和rise 2连续失败2次才判定故障连续成功2次才恢复。这是防抖动的关键网络瞬断不会立刻切换。virtual_router_id 51同一组VRRP实例必须一致不同组不能冲突。如果内网有多套Keepalived这个值要规划好撞了会互相干扰。advert_int 1主节点每秒发一次心跳广播。备节点超过3秒收不到就认为主挂了。authenticationVRRP认证防止别的机器乱入竞选。密码别用默认的虽然内网相对安全但这是基本素养。notify_master/backup/fault状态切换时触发的脚本这是Redis角色切换的入口核心逻辑全在这里。3.2 备节点配置的差异点node-b的配置只有三处不同global_defs { router_id redis_node_b } vrrp_instance VI_REDIS { state BACKUP priority 90 # 其余完全一致 }state BACKUP和priority 90配合保证正常情况下node-a是主。这里有个新手常犯的错把备节点的state也写成MASTER以为这样能平等竞选。实际上Keepalived的state只是初始状态真正决定谁当主的是优先级。但初始状态写错会导致启动瞬间的抢占行为异常老老实实按角色写。3.3 优先级数值的设计逻辑为什么主100、备90、脚本失败减30算一下正常主100 备90主持有VIP。主节点Redis挂了主100-3070 备90备节点抢到VIP。主节点Redis恢复主优先级回到100 备90主抢回VIP。这里有个坑如果主节点Redis恢复后自动抢回VIP但此时它还是从库状态因为切换时它被降级了就会造成VIP在主节点但主节点是只读的诡异状态。所以notify_master脚本里必须包含把自己提升为主的逻辑或者干脆用nopreempt禁止抢占让切换单向进行。我倾向于禁止抢占因为频繁切换对业务伤害更大vrrp_instance VI_REDIS { nopreempt # 其余配置 }加了nopreempt后主节点恢复也不会抢回VIP需要人工介入或者等下次故障再切。这是可用性和一致性的权衡生产环境我更看重稳定。4. 健康检查与角色切换脚本的实战写法4.1 check_redis.sh怎么判断Redis真的不可用这个脚本是整套方案的眼睛写得太松会误判写得太严会漏判。我的版本#!/bin/bash # 检查Redis是否存活且角色正确 REDIS_CLI/usr/local/redis/bin/redis-cli PASSYourStrongPass # 先ping确认进程响应 PONG$($REDIS_CLI -a $PASS ping 2/dev/null) if [ $PONG ! PONG ]; then exit 1 fi # 再查角色主节点必须能写 ROLE$($REDIS_CLI -a $PASS info replication 2/dev/null | grep role: | cut -d: -f2 | tr -d \r) if [ $ROLE ! master ]; then # 如果本机应该是主但实际是从说明角色异常 exit 1 fi # 尝试写入一个探针key验证真的可写 $REDIS_CLI -a $PASS set __ha_probe__ $(date %s) EX 10 /dev/null 21 if [ $? -ne 0 ]; then exit 1 fi exit 0为什么要写探针key因为Redis可能处于能ping通但拒绝写入的状态比如磁盘满了触发stop-writes-on-bgsave-error或者被设成了只读。只ping不写会漏掉这类故障。探针key设10秒过期不会污染数据。注意脚本里的密码明文写在文件里权限必须设成600且属主root否则等于把数据库密码贴在墙上。4.2 notify.sh切换瞬间到底该做什么这个脚本接收master、backup、fault三个参数分别对应节点成为主、成为备、故障三种状态。核心逻辑#!/bin/bash REDIS_CLI/usr/local/redis/bin/redis-cli PASSYourStrongPass LOG/var/log/keepalived/notify.log log() { echo $(date %Y-%m-%d %H:%M:%S) $1 $LOG } case $1 in master) log 切换为MASTER提升Redis为主库 # 取消从属关系成为独立主库 $REDIS_CLI -a $PASS slaveof no one # 可选等待其他从库重新同步 sleep 1 log Redis已提升为主库 ;; backup) log 切换为BACKUP降级Redis为从库 # 指向当前持有VIP的节点 $REDIS_CLI -a $PASS slaveof 192.168.10.100 6379 log Redis已降级为从库 ;; fault) log 进入FAULT状态停止Redis写入 # 故障时把自己设为只读防止脑裂写入 $REDIS_CLI -a $PASS config set replica-read-only yes ;; esac这里最关键的细节backup分支里slaveof指向的是VIP而不是某个固定IP。因为VIP永远指向当前的主节点这样无论谁当主从库都能正确同步。如果写死IP切换后就同步错对象了。另一个细节master分支执行slaveof no one之前最好先确认自己数据是不是最新的。极端情况下如果原主还有未同步的数据新主直接提升会丢数据。生产环境可以在脚本里加一段等待复制偏移量追平的逻辑但会增加切换时间。这是可用性和数据完整性的经典权衡看业务能不能接受少量丢失。4.3 脚本权限与日志排查三个脚本都要chmod x并且放在/etc/keepalived/下。Keepalived默认以root运行脚本但如果你在global_defs里设了script_user要确保该用户有执行权限和访问Redis的权限。排查问题时日志是第一手资料tail -f /var/log/keepalived/notify.log tail -f /var/log/messages | grep Keepalived我遇到过脚本执行了但Redis没切换的情况最后发现是redis-cli路径写错脚本静默失败。所以脚本里每个关键操作后都要打日志别省这几行。5. 脑裂防护双主是怎么产生的怎么堵住5.1 脑裂的三种典型触发场景脑裂就是两台机器都认为自己是主同时接受写入数据分叉。触发场景主要有三个网络分区两台机器之间的心跳断了但各自和客户端还通。备节点收不到主的心跳以为主挂了自己升主主节点觉得自己好好的继续服务。两边都写完蛋。健康检查脚本误判主节点Redis短暂卡顿脚本判定失败备节点升主卡顿恢复后主节点又活了但它不知道自己已经被罢免。VIP漂移但角色没切Keepalived层面VIP切了但notify脚本没执行成功新主还是从库状态旧主还是主库状态。5.2 用双保险堵住脑裂第一道保险Redis层面的min-replicas。在主节点配置里加min-replicas-to-write 1 min-replicas-max-lag 10意思是主节点必须至少有1个从库且从库延迟不超过10秒才允许写入。这样当网络分区导致从库失联时主节点会自动拒绝写入避免孤独的主继续产生数据。第二道保险fence机制。在notify脚本的fault分支里除了设只读还可以直接关掉Redis或者封掉端口让故障节点彻底无法写入fault) log 进入FAULT执行fence # 方案一设为只读 $REDIS_CLI -a $PASS config set replica-read-only yes # 方案二更狠直接停掉Redis # $REDIS_CLI -a $PASS shutdown nosave ;;我一般用方案一因为直接shutdown太激进万一只是误判恢复起来麻烦。但如果业务对数据一致性要求极高方案二更保险。第三道保险VIP的ARP广播。Keepalived切换时会发送免费ARP刷新交换机MAC表。但如果网络设备有ARP缓存问题可能出现VIP漂了但流量还往旧机器走的情况。可以在notify脚本里手动触发ARP更新# 在master分支里 arping -c 3 -I ens33 -s 192.168.10.100 192.168.10.15.3 验证脑裂防护是否生效配完别急着上线做个破坏性测试手动在node-a上iptables封掉VRRP广播端口模拟网络分区观察两边行为。# 在node-a上封掉VRRP iptables -A INPUT -p vrrp -j DROP iptables -A OUTPUT -p vrrp -j DROP预期结果node-b收不到心跳升主并持有VIPnode-a因为min-replicas-to-write生效写入被拒绝。等解除封锁后node-a应该降级为从库重新同步。如果node-a还在接受写入说明防护没生效回去检查配置。6. 切换后的数据一致性验证与客户端适配6.1 怎么确认切换真的成功了切换完成后别只看VIP在谁身上要验证三件事# 1. VIP在哪台机器 ip addr show ens33 | grep 192.168.10.100 # 2. 当前Redis角色 redis-cli -h 192.168.10.100 -a YourStrongPass info replication | grep role # 3. 能否正常写入 redis-cli -h 192.168.10.100 -a YourStrongPass set test_key hello redis-cli -h 192.168.10.100 -a YourStrongPass get test_key三件事都通过才算切换成功。我见过VIP漂了、角色也对了但写入报READONLY的情况原因是replica-read-only配置在切换后没被重置。所以notify脚本的master分支里最好显式设一下$REDIS_CLI -a $PASS config set replica-read-only no6.2 客户端连接池的坑客户端连VIP有个隐藏问题连接池里的旧连接。切换瞬间旧主上的连接全部失效但客户端连接池可能还持有这些死连接导致一批请求失败。解决办法客户端配置连接超时和重试比如Jedis的timeout设2000msmaxAttempts设3。开启连接有效性检测定期ping。如果用的是Spring Bootspring.redis.lettuce默认有重连机制但要把timeout调短别用默认的无限等待。spring: redis: host: 192.168.10.100 port: 6379 password: YourStrongPass timeout: 2000ms lettuce: pool: max-active: 16 max-idle: 8 min-idle: 26.3 数据丢失的量化评估Keepalived主从是异步复制主节点写入后立刻返回不等从库确认。所以切换时主节点上还没同步到从库的数据会丢。丢多少取决于主从之间的网络延迟通常同机房1ms主节点的写入速率切换检测时间interval * fall 2*2 4秒按写入速率1000 QPS算最坏情况丢4000条左右。如果业务不能接受得改用半同步复制Redis 5.0的WAIT命令或者换用支持Raft的集群方案。但半同步会显著增加写入延迟这是CAP理论里绕不开的取舍。7. 日常运维中那些文档不会写的事7.1 定期演练比配置本身更重要配置搭好只是开始真正决定这套方案能不能扛事的是演练。我建议每月至少做一次切换演练流程记录当前主节点和VIP位置。手动停掉主节点Redisredis-cli shutdown。观察Keepalived日志和VIP漂移时间。验证新主可写、从库同步正常。恢复旧节点确认它正确降级为从库。记录整个过程的耗时和异常。演练时最好在业务低峰期并且提前通知相关方。我见过演练时忘了通知结果运维群里炸锅的场面。7.2 监控指标要盯哪些除了常规的Redis监控内存、连接数、命中率这套方案要额外盯指标采集方式告警阈值VIP所在节点定时ip addr检查与预期不符主从复制延迟info replication的master_repl_offset差值5秒Keepalived状态日志关键字Entering MASTER/BACKUP频繁切换切换次数统计notify.log中master出现次数1小时内2次频繁切换是最大的危险信号说明健康检查太敏感或者网络不稳定必须排查否则数据迟早出问题。7.3 配置文件版本管理keepalived.conf和几个脚本建议用Git管理起来。每次改动都提交出问题时能快速回滚。我吃过亏某次改了个参数忘了备份切换异常后想对比旧配置发现根本没留底只能凭记忆恢复。7.4 时间同步不能忘两台机器的时间必须同步否则日志对不上排查问题时能把人逼疯。用chronyd或ntpd都行timedatectl set-timezone Asia/Shanghai systemctl enable --now chronyd chronyc sources -v时间偏差超过几秒VRRP的心跳判断都可能出问题这是基础设施里的基础设施别省。8. 从这套方案延伸出去的几个方向跑通KeepalivedRedis主从之后如果业务继续增长有几个自然的演进方向。读写分离是最容易加的客户端把读请求路由到从库写请求走VIP到主库能显著降低主库压力。但要注意从库延迟对一致性敏感的读还是得走主库。再往上就是Redis Sentinel用三个哨兵节点替代Keepalived做故障检测和切换。哨兵的优势是能感知所有节点的角色切换逻辑更精细而且不需要VIP这种网络层技巧。代价是多了三个进程要维护配置也更复杂。如果数据量和并发到了单主扛不住的程度那就得上Redis Cluster了。分片、多主、自动reshard能力全面但运维复杂度也上了一个台阶。我的经验是别过早优化Keepalived方案能撑住的场景硬上Cluster只会增加故障面。最后分享一个我自己的判断标准如果这套主从环境半年内没发生过一次非计划切换说明它很稳继续用如果一个月切好几次先别急着换架构把健康检查脚本和网络质量查清楚很多时候问题不在Redis本身而在基础设施的抖动。