VibeLoop 系列Spring Boot × Redis 面试深度系列贯穿案例「VibeLoop」为虚拟的轻量级内容互动平台仅用于技术演示并非真实存在的产品。上期速递【Redis】分布式锁从青铜到王者本文覆盖 RDB/AOF 持久化原理、主从复制、哨兵集群、Cluster 分片、VibeLoop 生产部署、8 道面试题、必背速查表。目录开篇场景凌晨三点的宕机电话理论速览持久化与高可用的四层金字塔RDB定时快照的得与失AOF命令日志的取舍RDB vs AOF 选型决策树主从复制读流量分流哨兵集群自动故障转移Cluster数据分片与横向扩展VibeLoop 生产部署方案面试八连问 详解必背速查表开篇场景凌晨三点的宕机电话20xx年 6 月 12 日凌晨 3:17。VibeLoop 运维群里炸了——首页打不开了值班的张伟被电话叫醒打开监控一看Redis 进程没了。服务器半夜自动装了 Windows 安全补丁然后重启了。他火速重启 Redis。RDB 文件在数据恢复到了凌晨 3:00 的快照状态。用户登录 Session 没丢但最近 17 分钟的热门帖子点赞数、实时评论、最新关注关系——全没了。早上老板开会“恢复速度还行但整整 17 分钟的数据说没就没下次能不能做到一点不丢”张伟把appendonly yes加上了。AOF 开启后每条写命令都记到日志里。一个月后AOF 文件涨到了 8GB重启恢复要 4 分钟——老板又来问了“Redis 不是号称微秒级响应吗怎么重启越来越慢”加上运维组反馈读流量越来越大了Redis 单机 CPU 经常飙到 90%。张伟翻了翻 Redis 官方文档发现后面还有一大串东西——主从复制、哨兵集群、Cluster 分片。今天这篇文章就沿着张伟踩过的坑把 Redis 持久化到高可用这条路走通。理论速览持久化与高可用的四层金字塔Redis 的单机高性能架构像一辆赛车跑得快但不出事还好出一次事就致命。持久化和高可用就是给这辆赛车装上安全带 备用引擎 副驾驶 车队。层级能力解决的问题对应技术L1 持久化重启不丢数据进程崩溃/宕机RDB / AOF / 混合L2 主从复制读流量分流 数据冗余单机读瓶颈PSYNC 全量/增量L3 哨兵集群自动故障转移主节点宕机无人值守Sentinel RaftL4 Cluster数据分片 横向扩展单机写瓶颈 / 内存天花板哈希槽 16384类比L1 就像是给赛车装了行车记录仪出事了能回溯L2 是加了副驾驶能帮你分担L3 是自动驾驶切换司机晕了副驾自动接手L4 是组建车队一辆车装不下分到多辆车上跑。今晚张伟的事故每一层都能帮上忙。RDB定时快照的得与失类比拍照 vs 录像如果把 Redis 数据比作一个大家庭的全家福——RDB 是定时拍照每隔一段时间喊大家别动看镜头拍一张。优点是照片体积小、拿出来就能看。缺点也很明显——两张照片之间发生的事照片里没有。AOF 是全程录像从开机到关机一直录。丢了任何一秒都能回看。但录像文件越来越大看一遍要快进很久。fork Copy-On-Write不阻塞的快照是怎么做到的RDB 的核心命令是bgsave后台保存。这里最容易混淆的点是Redis 是单线程的bgsave 会不会卡住主线程不会。bgsave做了三件事fork() 子进程Linuxfork()会创建一个和父进程一模一样的子进程。此时父子进程共享同一块物理内存但操作系统并不真的拷贝数据——只是把页表标记为只读。子进程写 RDB 文件子进程遍历内存中的键值对逐个序列化写入磁盘。Copy-On-Write写时复制关键在这里。子进程写 RDB 期间主进程还在处理客户端请求。当主进程要修改某个 key 时操作系统发现这块内存是只读的就会把要修改的那一页内存复制一份给主进程子进程继续用原来的那页。只复制被修改的页不是整个内存。主进程: 修改 key-A → OS 检测到共享页 → 复制 key-A 所在页 → 主进程在新页上修改 子进程: 继续读原来的 key-A 页 → 写入 RDB 文件内容是 fork 时刻的快照内存消耗估算如果 fork 时有 10GB 内存fork 期间修改了其中 2GB 的数据COW 会产生约 2GB 额外内存开销——这在生产环境中必须留出余量。save 自动触发条件redis.conf中默认的三条触发规则save 900 1 # 900 秒内至少 1 次修改 → 触发 bgsave save 300 10 # 300 秒内至少 10 次修改 save 60 10000 # 60 秒内至少 10000 次修改三条规则是或关系——满足任意一条就触发。可以注释掉所有save行来禁用自动 RDB。RDB 优缺点速查维度RDB数据恢复完整性只能恢复到最近一次快照丢失窗口 两次 save 间隔恢复速度快二进制文件直接加载到内存文件体积小紧凑压缩的二进制格式fork 开销大内存实例 fork 耗时可能几百毫秒COW 额外占内存适用场景对数据完整性要求一般、需要快速冷备的场景AOF命令日志的取舍三种同步策略AOF 记录的是每一条写命令以 Redis 协议格式追加。关键参数appendfsync策略行为数据安全性能影响always每条写命令都 fsync 到磁盘最高最多丢 1 条命令性能最低磁盘瓶颈everysec每秒 fsync 一次独立线程较高最多丢 1 秒数据性能损耗可接受no不主动 fsync靠操作系统刷盘最低可能丢 30 秒数据几乎无损耗张伟选了everysec——这是 99% 场景下的最优解。always把 Redis 的 QPS 直接拉到机械硬盘级别no等于没开。AOF 重写给录像文件做剪辑AOF 文件会无限增长。比如你对同一个 key 反复INCR了 100 万次AOF 里有 100 万条INCR命令。重写就是把这些命令合并成一条SET key 1000000。bgrewriteaof 流程主进程 fork 子进程子进程扫描内存中的所有 key生成当前数据状态的最小命令集写入新 AOF 文件期间主进程的新写入同时记录到 AOF 重写缓冲区子进程完成后父进程把缓冲区的命令追加到新文件末尾原子地rename新文件覆盖旧文件fork → 子进程写新AOF → 父进程积累增量 → 追加重写缓冲 → rename 替换 ↑ ↑ COW 共享内存 主进程正常处理请求AOF 触发条件auto-aof-rewrite-percentage 100 # AOF 文件增长了 100% 后触发重写 auto-aof-rewrite-min-size 64mb # 且 AOF 文件至少 64MBAOF 优缺点速查维度AOF数据恢复完整性高everysec 最多丢 1 秒恢复速度慢需要逐条执行命令重建数据文件体积大是 RDB 的数倍重写后可压缩fork 开销重写时也需要 fork同样有 COW适用场景对数据安全性要求高的场景RDB vs AOF 选型决策树数据能接受丢失几分钟 ├─ 能 → 只用 RDB最省资源 │ └─ 实例内存超过 50GB→ 关闭自动 save改手动定时 bgsave ├─ 不能 → 开启 AOFeverysec │ └─ 还要考虑恢复速度 │ ├─ 恢复速度重要 → RDB AOF 同时开生产最常见 │ └─ 恢复速度无所谓 → 纯 AOF └─ Redis 4.0→ 开混合持久化aof-use-rdb-preamble yes RDB 做前缀快速加载 AOF 做增量完整数据 兼顾了恢复速度和数据完整性VibeLoop 的选择混合持久化。RDB 前缀保证重启恢复速度快老板能接受AOF 增量保证数据不丢用户不投诉。主从复制读流量分流为什么需要主从张伟的单机 Redis 读 QPS 到了 8 万CPU 90%。VibeLoop 的 Feed 流、帖子详情、用户主页全是读——读写比例约 91。加一个从节点读流量分过去一半主节点立刻降到 45%。PSYNC增量同步 vs 全量同步主从复制的核心是PSYNC命令。从节点连接主节点后发PSYNC replid offset从节点: PSYNC ? -1 → 首次连接全量同步 主节点: FULLRESYNC replid offset → 主节点 bgsave 生成 RDB → 发送给从节点 → 从节点清空旧数据 → 加载 RDB → 主节点把 RDB 生成期间的新写命令通过 replication buffer 发给从节点 → 从节点追完 buffer → 进入命令传播阶段 从节点: PSYNC replid offset → 断线重连增量同步 主节点: CONTINUE → offset 还在复制积压缓冲区范围内 → 只需补发断线期间的命令 → offset 不在范围内 → 退化为全量同步复制积压缓冲区replication backlog是一个固定大小的环形缓冲区默认 1MB。如果断线期间积压的命令超过了 1MB只能触发全量同步——这就是为什么生产环境中repl-backlog-size要调大建议 64MB。从节点处理过期 Key从节点不会主动删除过期 key。主节点删除 key 时会在命令流中发一条DEL从节点同步执行。主从复制的局限主从解决了读扩展问题但没有解决主节点挂了需要手动把从节点提升为主改配置 改应用连接地址写流量仍然只能走主节点这两个问题分别交给哨兵和 Cluster。哨兵集群自动故障转移张伟的第二件事故开篇事故两个月后。张伟给 Redis 配了主从1 主 3 从读压力解决了。某个周六下午主节点所在服务器磁盘满了Redis 进程 OOM 被杀。张伟在超市买菜手机报警响了。他远程连进去手动把从节点slaveof no one提为主节点改了应用配置重启了所有服务——整个流程 25 分钟。老板说“下次能不能自动处理”哨兵就是干这事的。主观下线 vs 客观下线哨兵节点定期PING主节点。如果一个哨兵在down-after-milliseconds默认 30s内没收到响应标记为SDOWN主观下线。一个哨兵判断 SDOWN 不可靠——可能是它自己和主节点之间的网络断了。需要多个哨兵达成共识这就是ODOWN客观下线哨兵1 → PING 主节点 → 超时 → SDOWN → 询问其他哨兵 哨兵2 → 回复我也连不上主节点 哨兵3 → 回复我也连不上 → 票数 quorum配置的法定人数→ ODOWN → 开始故障转移哨兵领导者选举Raft 协议ODOWN 触发后不是所有哨兵一起动手——需要选出一个 Leader 来执行故障转移。这个过程用 Raft 协议变体发现 ODOWN 的哨兵发起投票请求SENTINEL is-master-down-by-addr每个哨兵在一个纪元epoch内只能投一票得票数 max(quorum, N/21)的当选 LeaderLeader 负责选新主节点 通知其他哨兵 更新客户端选新主节点的逻辑Leader 从所有从节点中按优先级排序1. 过滤掉 SDOWN / ODOWN / 断线超过 5s 的从节点 2. 按 slave-priority 升序数字越小越优先 3. 同优先级按复制偏移量降序数据最新的优先 4. 偏移量相同按 runid 字典序唯一确定一个类比哨兵选举就像班级选班长——需要超半数投票才算当选。如果平票就重新投。落选者自动变成副班长普通哨兵继续参与下一轮。如果多个哨兵同时发起选举split vote大家随机等待不同时间重试避免活锁。故障转移完整时序SDOWN → 询问其他哨兵 → ODOWN → Raft 选举 Leader → Leader 选最优从节点 → SLAVEOF NO ONE 提主 → 其他从节点 SLAVEOF 新主 → 更新哨兵监控目标 → 发布 switch-master 通知客户端Cluster数据分片与横向扩展为什么还需要 Cluster哨兵解决了高可用没解决数据太多存不下和写 QPS 太高扛不住。VibeLoop 日活从 10 万涨到 100 万后单机 Redis 内存飙到 58GB物理内存只有 64GB写 QPS 接近 4 万。Cluster 把数据切成 16384 个槽分到多台机器上。每个节点负责一部分槽数据自动分散。哈希槽CRC16 % 16384// key 对应的槽号计算intslotCRC16.crc16(key.getBytes())%16384;和一致性哈希的区别Cluster 不维护哈希环维护的是槽-节点映射表。扩容时人工指定槽迁移——哪些槽从节点 A 迁移到节点 B。类比一致性哈希像环形跑道接力——新增选手只需交接相邻的那一棒其他选手的跑道路线完全不受影响。哈希槽则是一整排储物柜16384 个格子每个节点管一段连续的柜子。扩容就是把一部分柜子移到新节点去管迁移期间新旧节点共同服务被迁移的柜子。Gossip 协议节点间怎么通信Cluster 没有中心节点每个节点都通过 Gossip 协议和其他节点交换信息消息类型用途PING定期发送携带自身状态 已知的其他节点信息PONG对 PING 的回复 回应 MEET 握手MEET邀请新节点加入集群FAIL广播某个节点已下线UPDATE通知槽配置变更每个节点定期随机选几个其他节点发 PING通过病毒式传播让集群状态最终一致。请求重定向MOVED vs ASK客户端随便连集群中任意一个节点发请求。如果该 key 的槽不在这台机器上客户端: GET post:10001 节点A: -MOVED 8462 192.168.1.103:6379 // 槽 8462 在 103 上去那找 客户端: → 连接 192.168.1.103:6379 → GET post:10001 → 成功如果是槽正在迁移中客户端: GET post:10001 节点A: -ASK 8462 192.168.1.104:6379 // 槽正在迁试试去 104 查 客户端: → ASKING → GET post:10001 → 成功这次临时重定向关键区别MOVED是永久重定向客户端应更新槽映射表ASK是临时重定向槽还在迁移中下次可能回到原节点。扩容流程1. 新节点 CLUSTER MEET 加入集群 2. CLUSTER SETSLOT slot IMPORTING source_node // 目标节点标记槽为正在迁入 3. CLUSTER SETSLOT slot MIGRATING target_node // 源节点标记槽为正在迁出 4. MIGRATE 逐 key 迁移数据 5. CLUSTER SETSLOT slot NODE target // 完成迁移生产环境一般用redis-cli --cluster reshard自动化执行但理解底层命令对排查迁移卡住问题很重要。VibeLoop 生产部署方案从单机 Redis 到 ClusterVibeLoop 经历了三次架构升级阶段架构痛点升级原因初创期单机 Redis RDB半夜宕机丢 17 分钟数据数据安全成长期主从 混合持久化主节点挂了手动切 25 分钟高可用爆发期6 节点 Cluster单机内存 58GB 接近天花板横向扩展最终生产配置# 持久化混合模式 save 900 1 save 300 10 save 60 10000 appendonly yes appendfsync everysec aof-use-rdb-preamble yes auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 128mb # 主从复制 repl-backlog-size 64mb repl-diskless-sync yes # 无盘复制不落 RDB 文件直接网络传输 # 哨兵 sentinel monitor mymaster 192.168.1.101 6379 2 sentinel down-after-milliseconds mymaster 10000 sentinel parallel-syncs mymaster 1 # Cluster cluster-enabled yes cluster-node-timeout 5000部署拓扑3 主 3 从每对主从跨机架部署避免单机架断电全挂。哨兵 3 节点部署在独立机器上。面试八连问 详解Q1RDB 的 bgsave 会阻塞主线程吗为什么答不会。bgsave通过fork()创建子进程来执行磁盘写入主线程继续处理请求。fork()本身是阻塞的复制页表大内存实例可能耗时几十到几百毫秒。fork 之后父子进程通过 COW 共享内存主进程修改数据时才触发页面复制。所以fork 瞬间有短暂阻塞写 RDB 期间不阻塞。Q2AOF 重写期间重写缓冲区会不会无限增长答不会。如果 AOF 重写期间父进程累积的重写缓冲超过了限制Redis 会限流——暂停接收客户端请求直到子进程完成。生产环境中应确保有足够的写入余量避免触发限流。Q3混合持久化比纯 AOF 好在哪答Redis 4.0 引入。RDB 做文件头加载快AOF 做文件尾数据完整。恢复时先加载 RDB 部分二进制直接rdbLoad再回放尾部 AOF——恢复速度接近纯 RDB数据完整性达到 AOF 级别。解决了纯 RDB 丢数据和纯 AOF 恢复慢的 tradeoff。Q4主从切换期间会发生什么客户端会丢请求吗答主节点故障到哨兵完成切换之间通常 10-30s写请求会失败。读请求如果连的是从节点不受影响。客户端应实现重试 退避逻辑或使用哨兵/Cluster 感知的客户端如 Lettuce 的RedisURI配置哨兵地址自动感知切换。Q5哨兵集群为什么必须是奇数个答Raft 选举需要超半数投票N/21。3 个哨兵允许挂 1 个2/3 1/24 个哨兵也只允许挂 1 个需要 3 票没有比 3 个更可靠却多费一台机器。所以实践中哨兵集群都是 3、5、7 个。Q6Cluster 的 16384 个槽为什么是这个数字不能更多吗答16384 2^14。设计考量① 心跳消息中携带槽位图16384 位 2KB网络开销合理② 每个节点维护 16384 个槽的映射内存开销小③ 实际生产集群很少有超过 1000 个节点的16384 个槽足够均匀分配。理论上可以改但不建议——16384 是编译期常量。Q7Cluster 模式下Lua 脚本和事务怎么处理跨槽 key答Cluster 要求 Lua 脚本中涉及的所有 key 必须在同一个槽。使用hash tag把 key 写成{user:100}:profile和{user:100}:postsCRC16 只计算{}内的部分——保证它们落在同一个槽。MULTI/EXEC事务同理。没有 hash tag 的跨槽操作直接报CROSSSLOT错误。Q8集群扩容期间正在迁移的槽被写入了怎么办答槽迁移期间源节点和目标节点短暂共存。写请求到源节点先查 key 是否已被迁移lookupKey已迁移则返回-ASK重定向。读请求到目标节点ASKING命令告知目标节点暂时接管这个槽的请求。迁移完成后源节点的槽绑定解除所有请求正常走目标节点。必背速查表概念一句话面试关键词RDB bgsavefork 子进程 COW不阻塞主线程fork / COW / save 触发条件RDB 丢失窗口两次 bgsave 之间数据不在磁盘上save 60 10000AOF 三策略always/everysec/noeverysec 是平衡点fsync / 最多丢 1 秒AOF 重写扫描内存生成最小命令集解决文件膨胀bgrewriteaof / 重写缓冲混合持久化RDB 头 AOF 尾恢复快数据又完整aof-use-rdb-preamble/ Redis 4.0PSYNCreplid offset 实现断线增量同步CONTINUE/FULLRESYNC复制积压缓冲环形 buffer太小退化为全量同步repl-backlog-size64MBSDOWN/ODOWN主观下线 / 客观下线多数哨兵确认down-after-milliseconds/ quorum哨兵选举Raft 协议选 Leader 执行故障转移epoch / N/21 投票哈希槽crc16 % 16384数据分布靠槽不分 ringslot / hash tag{...}MOVED vs ASK永久重定向 vs 临时重定向迁移中-MOVED/-ASK/ASKINGGossip 协议去中心化节点状态传播PING/PONG/MEET/FAIL这是Redis系列的最后一期若有问题欢迎交流指正若有帮助麻烦各位支持支持