面灵AI→

中科曙光 AI应用研发秋招凉经:Redis 连环追问

时间
2026-09
来源
牛客网

《面试题目》

  1. 自我介绍。
  2. Redis 主从切换后出现数据丢失,应该怎么排查和处理?
  3. 令牌桶限流和漏桶限流的区别是什么?
  4. Redis 缓存雪崩、击穿和穿透分别是什么?
  5. Redis 发生缓存击穿时,互斥锁应该如何设计?
  6. Redis 缓存和数据库如何保证一致性?
  7. Redis 缓存穿透时,布隆过滤器会产生什么问题?
  8. Redis 的缓存淘汰策略有哪些,如何选择?
  9. Redis 的 RDB 和 AOF 有什么区别?
  10. Redis 为什么会出现大 Key,如何发现和治理?
  11. 核心业务中 Redis 挂掉了,服务应该如何继续运行?

《参考解析》

主从切换丢数据:先定性,再定量

第一步要区分丢的是「已经返回客户端成功的写」还是「本来就没复制完的写」。Redis 主从复制默认是异步的:主节点执行完写命令并给客户端返回成功时,从节点可能还没同步到,此时主节点宕机、哨兵把从节点提升为主,复制延迟窗口内的数据就没了。排查要看这几项:INFO replication 里 master_repl_offset 与 slave_repl_offset 的差值(能反映复制延迟)、主节点是否开了持久化、故障时 AOF 是否完整刷盘、哨兵 down-after-milliseconds 与切换耗时、以及切换期间客户端有没有继续往旧主节点写。降低风险可以配 min-replicas-to-write 1 和 min-replicas-max-lag 10,表示至少一个从节点在 10 秒内正常复制时才允许写,代价是复制异常时主节点直接拒写——用可用性换一致性;也可以用 WAIT numreplicas timeout 做同步等待,但会增加写延迟。结论是:对绝对不能丢的数据,Redis 不能作为唯一存储,最终事实来源要落在数据库或强持久化存储上。

令牌桶和漏桶:允许突发 vs 严格匀速

漏桶把请求先放进桶里排队,再以固定速率流出,桶满就丢弃——出口速率恒定,适合保护下游,但突发流量会被削平甚至丢掉。令牌桶以固定速率往桶里放令牌,桶有容量上限,请求拿到令牌才能执行,桶里攒下的令牌允许一定程度的突发,长期平均速率仍然受限,更适合接口限流。单机实现不需要定时器:用「上次补充时间 + 速率」算出当前应该有多少令牌,取 min(capacity, tokens + elapsed * rate),不足 1 就拒绝;并发下这个读-算-写必须加锁或用原子操作,否则限流不准。分布式场景不能各实例用本地内存(N 个实例等于把阈值放大 N 倍),要用 Redis + Lua 保证取令牌的原子性,或者把限流下沉到网关、Sentinel 这类组件,并明确拒绝策略是快速失败、排队等待还是降级返回。

穿透、击穿、雪崩:三个问题三套解法

穿透是查根本不存在的数据,请求绕过缓存每次都打到库——解法是缓存空值(配较短过期时间)、布隆过滤器前置拦截,以及参数校验挡住明显非法的 key。击穿是某个热点 key 恰好过期,同一时刻大量并发直接压到库——解法是互斥锁只放一个请求回源、逻辑过期(返回旧值 + 异步刷新)或后台提前续期。雪崩是大量 key 在相近时间集中失效,或者缓存实例整体挂掉——解法是过期时间加随机抖动、分批预热、多级缓存、限流熔断降级。布隆过滤器的代价要提前说清:它由多个哈希函数和一个位数组组成,说不存在一定准确、说存在可能误判;不支持简单删除(要删就用计数布隆过滤器,拿内存换能力),元素变多误判率会上升、需要重建,重建期间要保证新旧版本兼容,多个业务混用还会互相污染。

击穿时的分布式锁:锁要过期,删锁要认主人

流程是:查缓存,命中直接返回;未命中则抢分布式锁,抢到的回源查库并回填缓存,没抢到的短暂等待或直接返回降级结果,最后释放锁。两个必须处理的细节:一是锁一定要设过期时间,否则持锁进程崩了就是永久阻塞;二是释放锁不能直接 DEL,因为锁可能已经超时自动释放、又被别的请求抢到,这时旧的 DEL 会误删别人的锁——用随机值标识持有者,再用 Lua 保证「比对 + 删除」的原子性。还要注意锁只是把并发收敛成串行,并不能降低总压力:要配合超时、重试次数上限、等待队列长度限制和降级策略,否则大量线程在锁外排队,反而把线程池耗光。

缓存与数据库的一致性

最常用的是旁路缓存:读先读缓存、未命中读库并回填;写先更新数据库、再删除缓存(而不是更新缓存,也不是先删缓存再写库——后者在并发读下会把旧值重新写回缓存)。删除失败不能悄悄吞掉,要进重试队列或补偿任务;一致性要求更高时可以订阅 binlog 或通过 MQ 异步失效缓存,把「删缓存」与业务事务解耦,做到最终一致。需要清醒认识的是:只要存在并发读写,缓存与库之间就总有短暂不一致的窗口,所以强一致场景里缓存只能当加速层,不能当权威数据源。

淘汰策略、持久化,以及 Redis 挂了之后

maxmemory 必须设,否则 Redis 会一直申请内存直到 OOM 被杀。策略上:只当缓存用 allkeys-lru 或 allkeys-lfu(长期稳定的热点更适合 LFU,访问模式变化快的更适合 LRU);同一实例既存缓存又存不可丢数据时用 volatile-lru,并保证缓存 key 都设了过期时间;noeviction 在内存满时直接拒绝写入,等于把故障转移给上游。持久化方面,RDB 是某个时间点的全量快照,文件紧凑、恢复快,适合备份和灾备;AOF 记录写命令,appendfsync everysec 是常见折中,通常最多丢一秒左右的数据,恢复慢但更完整;两者可以同时开,恢复时优先用 AOF。大 key(超大 String、元素过多的 Hash/List/Set/ZSet)会阻塞主线程、拖慢主从同步,用 redis-cli --bigkeys、慢查询日志和采样监控发现,治理上按业务分片拆分,删除时用 UNLINK 或 SCAN 分批删,别直接 DEL。最后是「Redis 挂了服务怎么活」:先分清它承担的是缓存、锁、计数器还是权威状态——纯缓存可以穿透到库,但必须开限流熔断和请求合并,防止把库打垮;锁失效靠业务幂等兜底;计数器丢失要有对账和补算。可用的兜底还有本地缓存抗热点读、多级降级开关,以及提前演练过的降级预案。