前言
在分布式缓存与高可用架构中,Redis 的主从复制(Replication)是构建读写分离、数据冗余及故障自动转移的基石。尽管"主从同步"是一个被广泛讨论的话题,但在实际生产环境与深层技术原理中,大量开发者对 replid 的生命周期、repl_backlog 与客户端缓冲区的区别、以及 PSYNC2 协议中双 ID 机制的理解仍停留在表面,甚至存在严重的认知误区。
一、 核心概念正名:身份标识与复制状态的正交性
理解 Redis 复制机制的首要前提,是严格区分"节点身份标识"与"复制上下文状态"。这两个概念在逻辑上完全正交,混淆二者是理解 PSYNC2 协议的最大障碍。
1.1 Replication ID(replid):节点的世代身份证
replid 是一个 40 位的十六进制随机字符串,其本质是 Redis 实例的世代标识(Generation ID) 。每个 Redis 实例在启动时都会独立生成自己的 replid。无论该实例当前角色是 Master 还是 Slave,也无论它是否已配置复制关系,replid 始终存在且有效。
replid 并非专为"主从关系"而生,而是为故障转移(Failover) 预留的身份凭证。当 Slave 被 Sentinel 或 Cluster 提升为新 Master 时,它无需重新生成 ID,而是直接沿用自身的 replid 作为新世代的标识,从而保证其他 Slave 能够通过 PSYNC2 协议无缝衔接,避免不必要的全量同步。社区常有"Slave 首次绑定时没有 replid"的错误说法。事实上,Slave 在首次发送 PSYNC 请求前,早已拥有自己的 replid。它缺失的是 Master 的 replid,而非自身的身份标识。
1.2 复制上下文(Replication Context):同步进度的度量衡
复制上下文由两个要素组成:Master 的 replid 和复制偏移量 offset。
- master_replid:Slave 记录的、当前所跟随的 Master 的身份标识。仅在成功完成一次同步握手后才会被赋值。
- master_repl_offset:一个单调递增的整数,表示 Slave 已从 Master 接收并确认的命令流字节数。它不是时间戳,也不是命令条数,而是 RESP 协议序列化后的精确字节长度。
只有当 Slave 同时持有有效的 master_replid 和 master_repl_offset 时,才具备发起增量同步(Partial Resynchronization)的前提条件。首次绑定时,Slave 因缺乏此上下文,只能发送 PSYNC ? -1 触发全量同步。这是正常且预期的行为,与"异常"无关。
二、 PSYNC2 协议与双 Replication ID 机制
Redis 4.0 引入的 PSYNC2 协议是复制机制的分水岭,其核心贡献是解决了"Master 重启后 Slave 被迫全量同步"的历史顽疾。
2.1 传统 PSYNC 的缺陷
在 Redis 4.0 之前,Master 每次重启都会生成全新的 replid。Slave 重连时携带的旧 replid 必然与新 ID 不匹配,导致 Master 拒绝增量同步请求。即使 repl_backlog 中的数据仍然完整有效,Slave 也不得不经历耗时的全量同步,这在大规模数据集场景下是不可接受的。
2.2 双 ID 机制的工作原理
PSYNC2 引入了 Secondary Replication ID (replid2) 概念,使 Master 能够记住"上一代"的身份。
- Primary Replication ID (
replid):Master 当前使用的身份标识,每次启动时重新生成。 - Secondary Replication ID (
replid2) :Master 保存的上一次replid,以及对应的切换点偏移量second_replid_offset。
当 Slave 发起 PSYNC <slave_replid> <slave_offset> 请求时,Master 的判断逻辑如下:
- 若
slave_replid == server.replid,且slave_offset在repl_backlog有效范围内,则执行增量同步。 - 若
slave_replid == server.replid2,且slave_offset <= server.second_replid_offset,同时slave_offset仍在repl_backlog有效范围内,则执行增量同步(跨世代恢复)。 - 其余所有情况,均触发全量同步。
这一机制确保了 Master 重启后,只要 Slave 的复制进度未超出积压缓冲区的覆盖范围,即可安全地继续增量同步。replid2 本质上是一个"身份过渡窗口",它将 Master 重启事件对复制链路的影响降到了最低。
2.3 replid2 的失效场景
需要注意的是,replid2 并非永久有效。以下操作会清除或重置 replid2,使其失去保护作用:执行 REPLICAOF NO ONE 后再执行 REPLICAOF(手动角色切换会重置复制历史);从 RDB/AOF 文件恢复数据(外部数据加载意味着复制流的不连续,旧 ID 不再可信);执行 DEBUG RELOAD 等调试命令。在这些场景下,Master 会将 replid2 置为全零字符串,second_replid_offset 置为 -1,强制后续所有 Slave 进行全量同步以保证数据一致性。
三、 全量同步的七步闭环与缓冲区辨析
当增量同步条件不满足时,Redis 将启动全量同步流程。这是复制机制中最复杂、资源消耗最大的环节,其中涉及多种缓冲区的协同工作,极易混淆。
3.1 完整流程

- 同步请求 :Slave 发送
PSYNC <replid> <offset>。首次绑定时为PSYNC ? -1。 - Master 裁决 :Master 检查
replid与offset,判定无法增量同步,回复+FULLRESYNC <new_replid> <new_offset>。 - 上下文初始化 :Master 创建新的
repl_backlog(若不存在),生成或确认当前replid,并将新 ID 和 offset 发送给 Slave。 - RDB 快照生成 :Master 调用
BGSAVE,fork 子进程通过写时复制(Copy-on-Write)机制生成 RDB 文件。主线程继续处理客户端请求。 - RDB 传输 :Master 将 RDB 文件通过 Socket 流式发送给 Slave。根据
repl-diskless-sync配置,可选择磁盘模式(先落盘再发送)或无盘模式(直接通过 Socket 流式传输,不落盘)。 - Slave 数据重建 :Slave 接收完 RDB 后,清空本地所有数据(
FLUSHALL),将 RDB 加载到内存。加载期间 Slave 阻塞,不响应客户端查询(除非配置replica-serve-stale-data yes)。 - 命令补发与同步完成 :Master 将 RDB 生成及传输期间积累的写命令发送给 Slave。Slave 执行这些命令后,
master_repl_offset与 Master 对齐,进入正常的命令传播阶段。
3.2 关键缓冲区辨析:repl_backlog vs 客户端输出缓冲区
这是全量同步中最常被误解的技术点。两者在生命周期、作用域和功能上完全不同。
repl_backlog(复制积压缓冲区) 是全局共享的,所有 Slave 共用同一个环形缓冲区。它持久存在,随 Master 进程存活,数据被循环覆盖。其核心功能是支持断线重连后的部分重同步。Master 处理完每个写命令后,无条件写入该缓冲区。若发生溢出,旧数据被覆盖,可能导致后续断连 Slave 无法部分重同步。
客户端输出缓冲区(Client Output Buffer) 是每个 Slave 连接独占的,相互隔离。它仅在全量同步期间为该 Slave 分配,同步完成后释放。其核心功能是暂存全量同步期间(RDB 生成+传输)产生的新命令,用于命令补发 。它是动态增长的链表/缓冲区,受 client-output-buffer-limit replica 限制。仅当某个 Slave 处于全量同步状态时,Master 才向其专属缓冲区写入。若溢出,会触发保护机制,Master 主动断开该 Slave 连接,防止 OOM。
术语纠正 :社区中常见的 repl_baklog 是错误拼写,Redis 源码及官方文档中仅有 repl_backlog。此外,全量同步期间的命令暂存不依赖 repl_backlog,而是依赖 per-Slave 的客户端输出缓冲区。repl_backlog 虽然也在同步期间被写入,但其目的是为未来的断线重连做准备,而非服务于当前的全量同步流程。
3.3 BGSAVE 与 Copy-on-Write 的内存开销
BGSAVE 通过 fork() 系统调用创建子进程。Linux 内核采用写时复制机制,父子进程初始共享同一物理内存页。只有当父进程修改某页时,内核才会复制该页供子进程使用。这意味着 BGSAVE 期间的额外内存开销取决于写操作的强度 。若 Master 在快照期间几乎无写入,CoW 开销趋近于零;若写入频繁且涉及大量内存页修改,CoW 开销可能接近原始数据集大小,导致总内存占用翻倍。生产环境中应通过 INFO STATS 中的 latest_fork_usec 和操作系统层面的 CoW 监控指标评估风险。
四、 命令传播与心跳保活机制
全量同步完成后,主从进入稳态的命令传播阶段。这一阶段的可靠性依赖于精密的心跳与确认机制。
4.1 实时命令转发与异步复制本质
Master 在处理完每个写命令后,会将其序列化为 RESP 格式,异步写入所有已同步 Slave 的 Socket 输出缓冲区。这是一个非阻塞操作,Master 不会等待 Slave 的确认即返回客户端成功响应。因此,Redis 主从复制是异步复制,存在短暂的数据不一致窗口。这是 CAP 定理下选择 AP(可用性+分区容忍)的必然代价。
4.2 REPLCONF ACK 心跳机制
Slave 每秒向 Master 发送一次 REPLCONF ACK <current_offset> 命令,上报自身的复制偏移量。该机制承担三重职责:检测连接存活(若 Master 超过一定时间未收到 ACK,判定 Slave 失联);监控复制延迟(Master 通过对比自身 offset 与 Slave 上报的 offset,计算复制滞后字节数);保障最小副本数(配合 min-replicas-to-write 和 min-replicas-max-lag 配置,当健康 Slave 数量不足或延迟过大时,Master 可拒绝写入请求,防止脑裂或数据丢失风险)。
4.3 超时与断连处理
Master 对 Slave 的超时判定不仅依赖 ACK,还包括 Socket 读写超时。若 Slave 因网络抖动短暂断连,重连后将利用 repl_backlog 尝试部分重同步。只有当断连时间过长导致 offset 超出积压缓冲区范围,或 replid 无法匹配时,才会再次触发全量同步。
五、 故障转移中的 replid 传递与一致性保障
在 Sentinel 或 Redis Cluster 模式下,主从复制机制与故障转移协议深度耦合。replid 的正确传递是保证故障转移后数据一致性的关键。
当 Slave 被选举为新 Master 时,它将自己的 replid 保持不变,作为新世代的 Primary ID;同时将原来的 master_replid 保存为 replid2,并将当前的 master_repl_offset 设为 second_replid_offset;最后向所有其他 Slave 广播新的复制信息。其他 Slave 收到通知后,发现自己持有的 master_replid 等于新 Master 的 replid2,且 offset 在有效范围内,即可直接发起增量同步,无缝切换到新 Master,全程无需全量同步。
为缓解异步复制带来的数据丢失风险,可采取以下措施:启用 min-replicas-to-write 确保写入至少被一个 Slave 确认;使用 WAIT 命令在应用层实现同步复制语义;在故障转移后,通过日志比对或业务补偿机制修复可能的数据差异。
六、 生产环境配置建议
6.1 关键参数调优
repl-backlog-size:默认 1MB 在生产环境中通常过小。应根据写入 QPS 和预期最大断连时长计算。公式参考:写入速率(bytes/s) × 最大允许断连时间(s) × 安全系数(1.5~2)。对于高写入实例,建议设置为 64MB~256MB。repl-diskless-sync:在磁盘 IO 瓶颈或云盘环境下,强烈建议开启yes或swapdb模式,避免 RDB 落盘带来的性能抖动。client-output-buffer-limit replica:默认256mb 64mb 60。若频繁出现 Slave 因输出缓冲区超限被断开,应适当调大硬限制或延长软限制窗口,但同时需警惕 Master 内存溢出风险。repl-timeout:应大于repl-ping-replica-period(默认 10s),建议设为 60s 以上,避免因瞬时网络波动误判断连。
6.2 核心监控指标
master_repl_offset与slave_repl_offset差值:反映复制延迟的绝对字节数。connected_slaves与min-replicas-to-write的关系:预警写入拒绝风险。repl_backlog_active与repl_backlog_histlen:监控积压缓冲区使用率,预判部分重同步能力。latest_fork_usec:评估 BGSAVE fork 耗时,过高的值预示 CoW 内存压力或大 Key 问题。- Slave 日志中的
FULLRESYNC频率:频繁全量同步是系统不健康的明确信号,需排查网络、缓冲区配置或 Master 重启策略。
七、 Redis 高频面试题
一、主从复制与高可用机制
Q1:Slave 首次绑定 Master 时,发送的 PSYNC 参数是什么?为什么不是 PSYNC ? -1?
正常启动的 Redis 节点在 initServerConfig() 中已无条件生成非空、非全零的 40 位十六进制 replid。首次执行 SLAVEOF 进入 REPL_STATE_CONNECTING 状态时,源码会将自身 replid 复制到 master_replid 字段,因此实际发送的是 PSYNC <自身replid> <自身offset>。Master 收到后发现该 ID 既不等于当前 replid 也不等于 replid2,判定为新节点接入,触发全量同步。PSYNC ? -1 仅在 master_replid 为空字符串或等于 ZERO_REPLID 的异常边界状态下才会构造,并非标准生产行为。首次全量的根本原因是身份错配,而非"无身份"。面试中若被追问,可补充说明 replid 由 getRandomHexChars() 生成,保证全局唯一性。
Q2:Master 重启后,Slave 是否一定触发全量同步?PSYNC2 如何解决该问题?
不一定。Redis 4.0+ 的 PSYNC2 引入双 Replication ID 机制:Master 重启时调用 changeReplicationId() 生成新 Primary replid,同时将旧 replid 保存为 replid2 并记录 second_replid_offset。Slave 重连时若携带的旧 replid 匹配 Master 的 replid2,且 offset 未超出 second_replid_offset 及积压缓冲区有效范围,仍可增量同步。仅当两个 ID 均不匹配或 offset 失效时才触发全量,避免了 Master 重启导致的全量雪崩。此机制的核心价值在于将"Master 重启"与"数据全量重建"解耦,是 Redis 高可用架构的基石之一。
Q3:复制积压缓冲区和全量同步期间的客户端输出缓冲区有什么区别?
二者职责完全独立,源码路径与生命周期截然不同:
- 复制积压缓冲区 (
server.repl_backlog):全局共享的环形缓冲区,随 Master 进程持久存在,每条写命令处理后无条件写入,专用于支持断线重连后的部分重同步。其大小由repl-backlog-size控制,超出时覆盖最旧数据。 - 客户端输出缓冲区 (
client->buf/client->reply):每个 Slave 连接独占,仅在全量同步期间动态分配,用于暂存 RDB 传输过程中新产生的写命令,供同步完成后补发。其大小受client-output-buffer-limit replica限制,超限则断开连接。
全量同步的命令暂存依赖后者,而非前者。混淆二者会导致对内存使用、同步失败原因及配置调优的误判。面试中应强调"全局 vs 独占"、"持久 vs 临时"、"部分重同步 vs 全量补发"三组对立特征。
Q4:Slave 被提升为新 Master 时,如何处理 replid 以保证其他 Slave 无需全量同步?
Slave 提升时调用 replicaToMaster(),保持自身原有 replid 不变作为新 Primary ID,同时将原 master_replid 保存为 replid2,并将当前 master_repl_offset 设为 second_replid_offset,再通过 replicationCron() 广播给其他 Slave。其他 Slave 发现自己持有的 master_replid 等于新 Master 的 replid2 且 offset 有效,即可直接增量同步。这是 PSYNC2 支持高效故障转移的核心设计,也是 Redis Cluster 秒级切换的基础。关键点在于"身份继承"而非"身份重置",确保了复制链路的连续性。
Q5:主从复制是同步还是异步?如何缓解数据丢失风险?
Redis 主从复制是纯异步复制,Master 处理完写命令后立即返回客户端,不等待 Slave 确认。缓解措施包括:① 配置 min-replicas-to-write + min-replicas-max-lag 实现 Master 端写入保护(当健康 Slave 数不足或延迟过大时拒绝写入);② 应用层使用 WAIT numreplicas timeout 实现同步复制语义(阻塞直到指定数量 Slave 确认或超时);③ 结合 AOF always/everysec + RDB 定期快照做持久化兜底。需明确:Redis 不提供跨节点强一致性保证,异步复制是其高性能的必要代价。面试中应区分"缓解"与"消除",避免承诺绝对安全。
二、缓存高可用经典问题
Q6:缓存雪崩、击穿、穿透的区别是什么?Redis 层面如何应对?
- 雪崩:大量 Key 同时过期或缓存整体不可用,请求瞬时压垮 DB。Redis 层面应对:① 过期时间加随机偏移(如 TTL = base + random(0,300));② 采用 Redis Cluster / Sentinel 保障服务可用性;③ 对热点 Key 设置永不过期+逻辑过期异步刷新;④ 多级缓存(本地 Caffeine + Redis)降低单点依赖。
- 击穿 :单个热点 Key 过期瞬间高并发直达 DB。Redis 层面应对:① 互斥锁(
SETNX lock_key uuid EX 10)控制只有一个线程查库回写,其他线程重试或返回旧值;② 逻辑过期方案,Value 内嵌时间戳,读取时发现过期则异步刷新,当前请求返回旧值;③ 热点 Key 永不过期+后台定时刷新。 - 穿透 :查询不存在的数据,缓存永远未命中。Redis 层面应对:① 布隆过滤器(RedisBloom 模块)前置拦截,误判率可控(如 0.1%);② 空值缓存,对 DB 查询为空的 Key 缓存短 TTL(如 60s)空值;③ 接口层参数校验提前拒绝非法请求。
三者均需结合应用层策略,但 Redis 提供了原子操作、Pub/Sub、模块化扩展等底层能力支撑。面试中应强调"Redis 提供能力,业务决定策略",避免将解决方案完全归因于 Redis。
Q7:缓存预热与缓存降级分别是什么?何时使用?
- 缓存预热:系统上线或大促前,主动将热点数据加载到缓存中,避免冷启动时大量请求穿透到 DB。可通过脚本批量写入、RDB 导入或流量回放实现。适用于可预测流量高峰的场景。
- 缓存降级 :当缓存服务异常或负载过高时,临时绕过缓存直接访问 DB,或返回默认值/兜底数据,保障核心业务可用。通常配合熔断器(如 Sentinel/Hystrix)自动触发。适用于突发故障或容量超限的应急场景。
二者是缓存高可用的主动防御手段,与雪崩/击穿/穿透的被动应对形成互补。面试中应区分"事前预防"与"事中应急"的定位差异。
三、缓存与数据库一致性
Q8:缓存与数据库双写一致性有哪些方案?各自适用场景是什么?
- Cache Aside Pattern(旁路缓存):先更新 DB,再删除缓存。最常用,但存在极端并发下的短暂不一致窗口(读请求在删缓存后、写 DB 前读到旧值)。适用于读多写少、允许毫秒级不一致的场景。
- Read/Write Through:应用只与缓存交互,缓存负责同步 DB。一致性强,但实现复杂,Redis 原生不支持,需自研或借助中间件。适用于对一致性要求极高且愿意承担开发成本的场景。
- Write Behind(异步写回):只写缓存,异步批量刷入 DB。写入性能极高,但有数据丢失风险。适用于日志类、计数类等可容忍丢失的场景。
- Binlog 订阅(CDC) :通过 Canal/Flink CDC 监听 DB binlog 异步更新缓存。最终一致性有保障,解耦业务代码,适合对一致性要求较高且不愿侵入业务的场景。
Redis 本身不提供强一致性保证,选择方案需权衡一致性、性能、复杂度与业务容忍度。面试中应避免宣称"某方案完美",而应强调"权衡取舍"。
Q9:如何保证 Redis 缓存与数据库的最终一致性?
推荐组合策略:① 采用 Cache Aside + 重试机制(删除缓存失败时写入消息队列重试);② 对关键业务启用 Binlog 订阅兜底;③ 设置合理的缓存 TTL 作为最终一致性的安全边界;④ 监控缓存命中率与延迟指标,及时发现异常。避免追求强一致性而牺牲 Redis 的性能优势,除非业务明确要求(此时应评估是否适合使用 Redis)。面试中应强调"最终一致性是工程妥协,而非技术缺陷"。
Q10:延迟双删策略是否能解决一致性问题?有何缺陷?
延迟双删(先删缓存→更新 DB→延迟 N ms 再删缓存)试图消除"先更 DB 再删缓存"的并发不一致窗口。但其缺陷显著:① 延迟时间难以精确设定,过短无效、过长影响性能;② 第二次删除仍可能失败,仍需重试机制;③ 增加写操作延迟,违背缓存加速初衷。生产环境更推荐 Cache Aside + MQ 重试或 CDC 方案,延迟双删仅为理论探讨,不建议落地。面试中应明确指出其"理论可行、实践鸡肋"的定位。
四、持久化与数据安全
Q11:RDB 和 AOF 的区别是什么?生产环境如何选择?
- RDB:时间点快照,文件紧凑,恢复快,但可能丢失最后一次快照后的数据。适合备份、灾难恢复、冷数据迁移。
- AOF :追加写命令日志,数据更安全(可配置 everysec/always),但文件体积大、恢复慢。适合对数据丢失敏感的业务。
生产环境推荐 RDB + AOF 混合持久化(Redis 4.0+):AOF 重写时将 RDB 快照写入 AOF 文件头部,后续追加增量命令。兼顾快速恢复与数据安全,是当前最佳实践。面试中应强调"混合持久化是默认推荐",而非简单罗列两者优缺点。
Q12:AOF 重写(Rewrite)的原理是什么?是否会阻塞主线程?
AOF 重写通过 BGREWRITEAOF 触发,fork() 子进程遍历内存数据集生成最小命令集写入新 AOF 文件,主线程继续处理请求。重写期间新命令同时写入 AOF 缓冲区(aof_rewrite_buf_blocks)和正常 AOF 文件。子进程完成后,主线程将缓冲区内容追加到新文件末尾,再原子替换旧文件。全程主线程仅在 fork 和最终替换时有微秒级阻塞,不影响正常服务。面试中应强调"重写是增量合并,非全量重建",避免误解为重新生成所有命令。
Q13:RDB 快照期间写入的数据是否会丢失?
不会。BGSAVE 通过 fork() 创建子进程,依赖 Linux CoW 机制:父子进程初始共享物理内存页,仅当父进程修改某页时才复制该页。子进程看到的是 fork 时刻的一致性视图,父进程的后续写入不会影响快照内容,也不会被快照遗漏。快照完成后,新数据继续存在于内存中,并通过 AOF 或下一次 RDB 持久化。面试中应澄清"快照是时间点视图,非实时镜像",避免混淆。
五、内存管理与性能优化
Q14:Redis 内存淘汰策略有哪些?LRU 与 LFU 的原理与区别是什么?如何选择?
Redis 提供 8 种淘汰策略,分两类:针对设置了 TTL 的 Key(volatile-)和针对所有 Key(allkeys-)。其中 LRU 与 LFU 是最核心的两种算法:
- LRU(Least Recently Used,最近最少使用) :淘汰最久未被访问 的 Key。Redis 采用近似 LRU 算法:每次淘汰时随机采样 N 个 Key(
maxmemory-samples,默认 5),从中选择空闲时间最长的淘汰。采样数越大越接近理想 LRU,但 CPU 开销增加。LRU 的问题在于:一个长期热点 Key 若偶然长时间未被访问,可能被误淘汰;而一个短暂突发访问的冷 Key 可能因"最近访问"而被保留。 - LFU(Least Frequently Used,最不经常使用) :淘汰访问频率最低的 Key。Redis 4.0+ 引入,采用 Morris Counter 近似计数 + 时间衰减因子:每个 Key 维护一个 8-bit 计数器(最大 255),访问时以概率递增(避免饱和),同时根据上次访问时间衰减计数。LFU 能更好识别长期热点,避免 LRU 的"偶发访问污染"问题。但 LFU 对突发流量响应较慢,且计数器有精度损失。
选择原则:
- 纯缓存场景优先选
allkeys-lfu:更能保护长期热点,符合大多数业务访问模式。 - 部分 Key 有过期时间且希望优先淘汰过期 Key 时用
volatile-lfu。 - 若业务访问模式高度符合"最近访问即未来热点"(如会话缓存),可选
allkeys-lru。 - 不允许丢数据时用
noeviction+ 监控告警。 - 避免使用 random 策略,除非业务对淘汰顺序无要求。
面试中应强调"LFU 是 LRU 的改进版,但非万能替代",并结合业务访问模式说明选择依据。
Q15:什么是 BigKey?如何发现和处理?
BigKey 指 Value 过大(String > 10KB,集合元素 > 5000)或成员过多的 Key。危害包括:阻塞主线程、网络带宽打满、内存碎片、慢查询。
- 发现 :
redis-cli --bigkeys(采样扫描)、MEMORY USAGE命令、RDB 分析工具(如 redis-rdb-tools)、监控slowlog。 - 处理 :① 拆分大 Key(Hash 分片、List 分段);② 异步删除(
UNLINK替代DEL,Redis 4.0+);③ 避免KEYS *,改用SCAN;④ 业务层优化数据结构设计。
面试中应强调"预防优于治理",BigKey 是设计问题而非运维问题。
Q16:内存碎片是如何产生的?如何治理?
内存碎片分为外部碎片(空闲内存不连续)和内部碎片(分配器对齐浪费)。产生原因:频繁创建/删除小对象、jemalloc 分配策略、Key 大小差异大。
治理手段:① 启用自动碎片整理(activedefrag yes,Redis 4.0+,利用 jemalloc 的 malloc_usable_size 检测碎片并后台迁移);② 重启实例(最后手段);③ 优化数据结构,减少小对象;④ 使用 MEMORY PURGE 手动释放碎片(Redis 7.0+)。监控指标:mem_fragmentation_ratio > 1.5 需关注,> 2.0 需干预。面试中应区分"自动整理"与"手动清理"的适用场景。
六、集群与分布式架构
Q17:Redis Cluster 的数据分片原理是什么?扩容/缩容如何实现?
Redis Cluster 采用 16384 个哈希槽 (Hash Slot)分片,Key 通过 CRC16(key) % 16384 映射到槽,每个节点负责一部分槽。客户端通过 MOVED / ASK 重定向定位正确节点。
- 扩容 :新节点加入后,通过
CLUSTER SETSLOT ... IMPORTING/MIGRATING逐步迁移槽及对应 Key,迁移完成前客户端收到ASK重定向到新节点。 - 缩容 :反向迁移槽至剩余节点,完成后下线原节点。
全程在线,不中断服务,但迁移期间相关槽的请求有短暂延迟。面试中应强调"槽迁移是原子操作,但数据迁移是渐进式",避免误解为瞬时完成。
Q18:Redis Sentinel 和 Redis Cluster 的区别是什么?如何选择?
- Sentinel:专注高可用,监控 Master/Slave 状态,自动故障转移,通知客户端新 Master。数据仍集中在单 Master,不支持水平扩展。适合数据量不大、只需高可用的场景。
- Cluster :同时提供数据分片(水平扩展)和高可用(内置故障转移)。适合大数据量、高吞吐、需弹性伸缩的场景。
选择原则:数据量 < 单机内存上限且无需扩展 → Sentinel;否则 → Cluster。二者不可混用。面试中应明确"Sentinel 是高可用方案,Cluster 是分片+高可用方案",避免功能混淆。
Q19:Redis Cluster 为什么选择 16384 个槽?
官方解释:① 心跳包大小限制(Gossip 协议中槽位图占 2KB,16384 槽刚好 2KB);② 压缩比考量;③ 经验值平衡管理开销与扩展性。槽数固定,不可动态调整。若需更多分片,应增加节点而非修改槽数。面试中应强调"这是工程权衡结果,非理论最优解"。
七、线程模型与底层原理
Q20:Redis 是单线程模型,为什么还这么快?
Redis 6.0 前核心命令处理确实是单线程,但快在:① 纯内存操作;② 高效数据结构(SDS、ziplist、skiplist、hashtable 等);③ I/O 多路复用(epoll/kqueue);④ 无锁竞争。Redis 6.0+ 引入多线程 I/O(读写分离),但命令执行仍单线程,保证原子性。面试中需明确"单线程"仅指命令执行阶段,避免绝对化表述。
Q21:Redis 6.0 多线程 I/O 的工作原理是什么?是否破坏原子性?
Redis 6.0 引入 I/O 线程池,仅用于网络读写的并行处理(read/write syscall),命令解析与执行仍由主线程串行完成。流程:主线程分发读任务→I/O 线程并行读取→主线程收集并串行执行命令→主线程分发写任务→I/O 线程并行写回。全程命令执行阶段无并发,原子性不受影响。启用条件:io-threads-do-reads yes + io-threads N,N 建议为 CPU 核数的 3/4。面试中应强调"多线程仅加速 I/O,不改变执行模型"。
Q22:KEYS * 为什么危险?生产环境如何替代?
KEYS * 是 O(N) 操作,N 为全库 Key 数量,会阻塞主线程数秒甚至更久,导致服务不可用。生产环境必须使用 SCAN 命令,它通过游标增量迭代,每次返回少量结果,不阻塞主线程。注意 SCAN 不保证完整性和顺序,仅用于模糊查找或抽样统计。面试中应强调"SCAN 是生产唯一合法遍历方式"。
八、生产运维与综合陷阱
Q23:Redis 事务(MULTI/EXEC)能保证原子性吗?与关系型数据库事务有何区别?
Redis 事务仅保证命令顺序执行,不支持回滚。若某条命令执行失败(如语法错误),其余命令仍会继续执行。它不具备 ACID 中的隔离性和持久性(取决于持久化配置)。与关系型数据库事务本质不同,不应将其等同于传统事务。需要原子性时应使用 Lua 脚本(EVAL/EVALSHA),整个脚本在服务端原子执行。面试中应明确"Redis 事务是命令队列,非数据库事务"。
Q24:Lua 脚本在 Redis 中如何保证原子性?有何限制?
Lua 脚本通过 EVAL / EVALSHA 提交,Redis 将其作为一个整体命令执行,期间不插入其他命令,天然原子。限制:① 执行时间过长会阻塞主线程(建议 < 5ms);② 不能使用非确定性函数(如 math.random、os.time);③ 不能访问全局变量;④ 错误处理需谨慎,部分错误可能导致脚本中断。生产环境应预加载脚本(SCRIPT LOAD)并用 SHA 调用,减少网络开销。面试中应强调"原子性以阻塞为代价,需严格控制执行时间"。
Q25:如何安全地删除一个超大 Key?
禁止使用 DEL,因其同步删除会阻塞主线程。应使用 UNLINK(Redis 4.0+),它将 Key 从命名空间移除后,交由后台线程异步释放内存,主线程立即返回。若无 UNLINK,可考虑分批删除(如 Hash 用 HSCAN + HDEL 循环),但仍有阻塞风险。最佳实践是预防 BigKey 产生。面试中应强调"UNLINK 是异步删除的标准方案"。
Q26:Redis 慢查询如何排查和优化?
① 开启 slowlog-log-slower-than(默认 10ms)和 slowlog-max-len;② 定期查看 SLOWLOG GET;③ 结合 MONITOR(生产慎用)或 CLIENT LIST 定位具体命令;④ 优化方向:避免 KEYS *、拆分 BigKey、合理使用索引(如 ZSet 排序)、检查网络延迟、评估是否需要扩容。慢查询是性能瓶颈的直接信号,需建立常态化监控机制。面试中应强调"慢查询是结果,根因在数据模型或访问模式"。