← 返回题库 / 大纲

Redis 与缓存架构

第 51 题:缓存穿透 / 击穿 / 雪崩 三位一体治理 缓存

【真实企业业务场景】
某商品详情接口:黑客扫不存在的 ID(穿透)打挂 DB;大促一个爆款 Key 过期瞬间 10 万 QPS 回源(击穿);一次批量刷新缓存导致 大量 Key 同时失效,DB 连接池打满(雪崩)。要求:系统治理三类问题。
【面试官问题】
    1. 三者区别?2. 穿透怎么治?3. 击穿怎么治?4. 雪崩怎么治?5. 布隆过滤器误判?
【候选人的标准回答】

第一步:分析。穿透=查不存在的数据(无缓存)→每次打 DB;击穿=热点 Key 过期瞬间高并发回源;雪崩=大量 Key 同时失效/Redis 挂→DB 崩。

第二步:挑战。三类根因不同,方案不同:穿透用「空值缓存+布隆」;击穿用「互斥/逻辑过期」;雪崩用「错峰 TTL+高可用」。

第三步:架构。穿透:查不到也缓存空值(短 TTL)+ 布隆过滤器拦截非法 ID;②击穿:热点 Key 互斥重建(singleflight/分布式锁)或逻辑过期(异步刷新不删);③雪崩:TTL 加随机抖动 + Redis 集群高可用 + 限流降级。

第四步:选型。Redis + 布隆(Redisson/RoaringBitmap)+ singleflight + 错峰 TTL。

第五步:一致性。空值缓存与逻辑过期均最终一致;布隆有误判(放行不存在的,不误杀存在的)。

第六步:高可用。Redis 主从+哨兵;雪崩时限流保 DB。

第七步:优化。热点 Key 预热+逻辑过期免击穿;布隆误判率调优。

【架构设计】
请求 → 布隆(拦截非法ID) → Redis(空值缓存防穿透) 热点Key过期 → 互斥重建/逻辑过期(防击穿) 批量刷新 → TTL随机抖动(防雪崩) + Redis HA
【技术方案深度解析】

穿透 vs 击穿 vs 雪崩:穿透是「数据本不存在」,击穿是「单热点过期」,雪崩是「大面积失效」。逻辑过期:Key 不真删,值里带过期时间,过期后单线程异步刷新,期间返回旧值,彻底防击穿。布隆误判:说存在的可能不存在(放行查 DB),说不存在的一定不存在(拦截),故只挡穿透不误杀。

【关键技术点】
缓存穿透缓存击穿缓存雪崩布隆过滤器逻辑过期singleflight
【Java 实现示例】
// 逻辑过期:值带 expire 字段,过期后单线程异步刷新
public Item get(Long id) {
  Item it = redis.get(id);
  if (it != null && it.expire < now) {
    singleflight(id, () -> { Item n = db.load(id); redis.set(id, n.withExpire()); });
    return it; // 返回旧值,防击穿
  }
  return it;
}
// 空值缓存防穿透
Item v = db.load(id); redis.set(id, v==null? NULL_OBJ : v, v==null? 60s : 30min);
【面试官可能继续追问】
【常见错误回答】
  • ❌ 不存在的 ID 不缓存——穿透。正确:空值缓存+布隆。
  • ❌ 热点 Key 删了重建——击穿。正确:逻辑过期/互斥。
  • ❌ 统一 TTL——雪崩。正确:错峰抖动。
【架构师评分标准】
初级 0~40
三者混淆。
中级 40~60
知空值缓存,不懂击穿/雪崩。
高级 60~80
三问题分别治理+布隆+逻辑过期+错峰。
架构师 80~100
再加:①自动热点识别;②Redis HA 与降级;③监控(穿透率/回源QPS);④方案组合与成本。

第 52 题:缓存与数据库一致性 一致性

【真实企业业务场景】
商品改价后,缓存仍是旧价,用户下单按旧价,每千单约 3 单价格不一致引起投诉。要求:给出可落地的缓存一致方案并说明取舍。
【面试官问题】
    1. 先删缓存还是先更库?2. 为什么延迟双删?3. 强一致能做到吗?4. Binlog 方案?5. 并发写读怎么办?
【候选人的标准回答】

第一步:分析。缓存与 DB 是两个存储,无法原子,只能追求最终一致。经典结论:先更新 DB,再删缓存(Cache-Aside)+ 延迟双删 + binlog 兜底

第二步:挑战。删除失败、并发、强一致诉求。

第三步:架构。①写:更新 DB → 删缓存;②若删失败,靠延迟双删(500ms 后再删)+ 超时兜底;③binlog:Canal 订阅 DB binlog 异步删/更新缓存,做最终兜底(保证最终一致);④读:未命中回源并写回。

第四步:选型。Cache-Aside + 延迟双删 + Canal(binlog 同步)。

第五步:一致性。最终一致(秒级);强一致需「读写都走 DB 或分布式锁」性能差,仅极端场景。

第六步:高可用。binlog 同步失败有重试+告警;缓存删失败可重试。

第七步:优化。缓存设置合理 TTL 兜底;热点 Key 用 binlog 保证新鲜。

【架构设计】
写: DB更新 → 删缓存 → (延迟双删) binlog(Canal) → MQ → 更新/删缓存(兜底最终一致) 读: 未命中 → 回源DB → 写回缓存
【技术方案深度解析】

先更库再删缓存(推荐):若先删缓存再更库,删后到更新间有读会回填旧值。先更库再删,最多短暂脏(删除前读到的旧值),且 binlog 兜底最终修正。为什么双删:防「删缓存后、更新前」的并发读回填旧值,延迟再删一次。binlog 方案:与业务解耦,可靠保证最终一致,是主流做法。

【关键技术点】
Cache-Aside延迟双删binlogCanal最终一致读写穿透
【Java 实现示例】
// 写: 先更DB再删缓存 + 延迟双删
public void updatePrice(Long id, int price) {
  db.update(id, price);
  redis.delete(key(id));
  scheduled.deleteLater(key(id), 500); // 延迟双删
}
// Canal 监听 binlog 兜底
@RocketMQMessageListener(topic="binlog-price")
public void on(BinlogEvent e){ redis.delete(key(e.id)); } // 最终一致
【面试官可能继续追问】
【常见错误回答】
  • ❌ 先删缓存再更库——回填旧值。正确:先更库再删。
  • ❌ 更新缓存值——并发脏。正确:删缓存更稳。
  • ❌ 无兜底——删除失败即脏。正确:binlog 兜底。
【架构师评分标准】
初级 0~40
不知一致难点。
中级 40~60
知先更库再删,无兜底。
高级 60~80
Cache-Aside+双删+binlog最终一致。
架构师 80~100
再加:①强一致边界与代价;②binlog 可靠性;③延迟窗口量化;④监控(不一致率)。

第 53 题:热 Key 探测与本地缓存 热Key

【真实企业业务场景】
某明星带货,单个商品 Key QPS 80 万,落单分片,该分片 CPU 100%,其他分片空闲,Redis 整体吞吐腰斩。要求:热 Key 自动发现+本地缓存降压。
【面试官问题】
    1. 热 Key 怎么发现?2. 本地缓存怎么避免不一致?3. 本地缓存内存爆炸?4. 热 Key 迁移分片?5. 热点写?
【候选人的标准回答】

第一步:分析。热 Key 打破分片均衡,单分片成瓶颈。解法:发现→本地缓存(读多)+ 分片打散(写/读)

第二步:挑战。发现、本地不一致、内存、写热点。

第三步:架构。发现:代理/客户端统计 Key 访问频次(滑动窗口)上报;②本地缓存:热 Key 复制进 Caffeine(各实例本地),访问不触 Redis;③不一致:本地 TTL 短(如 1s)+ 失效广播(Redis pub/sub 或配置中心);④分片打散:热 Key 拆 N 个子 Key 分散到多分片(读时聚合/随机选)。

第四步:选型。Redis(统计/广播)+ Caffeine(本地)+ 监控(热 Key 榜)。

第五步:一致性。本地短 TTL + 失效广播保证最终一致;强一致走源。

第六步:高可用。本地缓存降级(Redis 挂仍可服务秒级);分片打散防单点。

第七步:优化。热 Key 自动识别 + 自动晋升本地;写热点用分桶。

【架构设计】
访问统计 → 识别热Key → 复制进本地Caffeine(各实例) 读: 本地命中即返回(零Redis); 失效: pub/sub广播 写热点: 拆N子Key分散多分片
【技术方案深度解析】

为什么本地缓存?把 80 万 QPS 从 Redis 单分片分散到各应用本地内存,Redis 压力趋零。不一致控制:本地 TTL 短 + 更新时广播失效,窗口内可能读到旧值(秒级),适合读多写少展示。写热点:本地缓存不适用写,需分桶(同 Key 拆成 key#1..N 轮询写)。

【关键技术点】
热Key探测Caffeine本地缓存失效广播分片打散分桶
【Java 实现示例】
// 本地缓存 + 失效广播
LoadingCache<Long, Item> local = Caffeine.newBuilder()
  .expireAfterWrite(1, SECONDS).build(k -> redis.get(k));
// 更新时广播失效
public void update(Long id, Item v){
  db.update(id,v); redis.set(id,v);
  redis.publish("cache-invalidate", id.toString()); // 各实例本地清
}
// 订阅失效
redis.subscribe("cache-invalidate", id -> local.invalidate(Long.parseLong(id)));
【面试官可能继续追问】
【常见错误回答】
  • ❌ 加 Redis 节点——单 Key 仍单分片。正确:本地/分片打散。
  • ❌ 本地缓存永不失效——脏数据。正确:TTL+广播。
  • ❌ 全量本地——内存爆。正确:只热 Key。
【架构师评分标准】
初级 0~40
只会加机器。
中级 40~60
知本地缓存,无失效机制。
高级 60~80
探测+本地+广播+分片打散+写分桶。
架构师 80~100
再加:①自动识别与晋升;②不一致窗口量化;③本地容量治理;④与多级缓存整合。

第 54 题:大 Key 治理与拆分 大Key

【真实企业业务场景】
一个 Hash 存全站用户标签,2 亿 field、12GB。一次 HGETALL 阻塞 Redis 800ms,删除时主线程卡顿 3s,集群抖动。要求:识别并治理大 Key。
【面试官问题】
    1. 大 Key 危害?2. 怎么发现?3. 怎么拆分?4. 删除卡顿怎么破?5. 热大 Key 同时有?
【候选人的标准回答】

第一步:分析。大 Key=体积/元素数过大,导致阻塞主线程(删除/序列化)+ 网络拥塞 + 内存不均

第二步:挑战。发现、拆分、安全删除、热大 Key。

第三步:架构。发现redis-cli --bigkeys、监控内存/元素数;②拆分:Hash 按 field hash 分 N 个桶(user_tag:{i});List 分片;③删除:用 UNLINK(异步)或分批 HSCAN+HDEL,避免 DEL 阻塞;④热大 Key:拆分+本地缓存。

第四步:选型。bigkeys 扫描 + 分桶拆分 + UNLINK + HSCAN 分批。

第五步:一致性。拆分时双写过渡;读聚合多桶。

第六步:高可用。异步删除不阻塞;拆分降单 Key 风险。

第七步:优化。设计期限制单 Key 大小(如 Hash < 1 万 field);监控预警。

【架构设计】
大Key: user_tag(2亿field) → 拆 64 桶 user_tag:{0..63} (按 uid%64) 删除: UNLINK(异步) / HSCAN+HDEL 分批 读: 聚合多桶; 热: +本地缓存
【技术方案深度解析】

为什么阻塞?Redis 单线程,DEL 大 Key 同步释放内存耗时;HGETALL 大 Key 一次性序列化占网络。UNLINK:异步释放,主线程只标记,后台线程回收,不卡。拆分原则:单 Key field/元素控制在上万内,按分桶打散,读写并行。

【关键技术点】
大Keybigkeys分桶拆分UNLINKHSCAN异步删除
【Java 实现示例】
// 分桶: 按 uid%64 选桶
int bucket = (uid % 64);
redis.hset("user_tag:"+bucket, uid, tag);
// 安全删除大 Key(分批)
public void safeDel(String key){
  var cur = ScanOptions.SCAN_POINTER_START;
  do { List<String> f = redis.hscan(key, cur, 1000);
       redis.hdel(key, f.toArray()); // 分批删
  } while (!cur.isComplete());
  redis.unlink(key); // 异步最终删
}
【面试官可能继续追问】
【常见错误回答】
  • ❌ DEL 大 Key——主线程卡 3s。正确:UNLINK/分批。
  • ❌ HGETALL 大 Key——阻塞网络。正确:HSCAN 分批。
  • ❌ 不做拆分——持续风险。正确:分桶。
【架构师评分标准】
初级 0~40
不知大 Key 危害。
中级 40~60
知拆分,用 DEL 删。
高级 60~80
发现+分桶+HSCAN/UNLINK+监控。
架构师 80~100
再加:①设计期约束;②热大 Key 组合治理;③迁移安全;④容量与成本。

第 55 题:Redis Cluster 扩容与槽迁移 Cluster

【真实企业业务场景】
Redis Cluster 3 主 3 从(每主 8G),内存用到 85%,大促预估翻倍。需扩到 6 主,要求扩容期间读写不受影响、不丢数据。要求:设计在线扩容方案。
【面试官问题】
    1. Cluster 槽模型?2. 扩容怎么迁移槽?3. 迁移中访问怎么办?4. 大 Key 迁移阻塞?5. 扩容失败回滚?
【候选人的标准回答】

第一步:分析。Cluster 把数据分 16384 槽,扩节点=把部分槽从老节点迁到新节点,迁移期间数据双写过渡。

第二步:挑战。迁移阻塞、访问路由、大 Key、回滚。

第三步:架构。①加新主从节点;②用 CLUSTER ADDSLOTS/工具(redis-cli --cluster reshard)把约一半槽迁到新节点;③迁移中:ASK 重定向——访问正在迁移的 Key,老节点返回 ASK,客户端去新节点取;④大 Key:迁移单 Key 阻塞,需先拆分(见 Q54);⑤失败:槽可迁回。

第四步:选型。Redis Cluster + reshard 工具 + 客户端支持 ASK/MOVED。

第五步:一致性。迁移中 Key 在源和目标都有,源删前目标已写,保证不丢。

第六步:高可用。每主有从;迁移限速防影响。

第七步:优化。槽迁移限速(cluster migrate 限速);低峰执行;监控迁移进度。

【架构设计】
3主 → 加3主(新) → reshard 迁移约一半槽(8192) 迁移中: 源保留+目标写入; 访问→ASK重定向新节点 大Key先拆分; 失败槽迁回
【技术方案深度解析】

槽模型:16384 槽均匀分给主节点,Key 经 CRC16%16384 落槽。ASK 重定向:迁移中的 Key,源节点标记「迁移中」,客户端访问返回 ASK,引导到目标节点;目标已有则返回。为什么限速:槽迁移逐 Key 复制,过快占带宽/CPU 影响在线。

【关键技术点】
Redis Cluster16384槽reshardASK重定向在线扩容迁移限速
【Java 实现示例】
// 客户端需支持 MOVED/ASK 重定向(Lettuce/Jedis Cluster 自带)
// 迁移命令(运维):
// redis-cli --cluster add-node new:6379 old:6379
// redis-cli --cluster reshard old:6379 --cluster-from {src} --cluster-to {dst}
//   --cluster-slots 8192 --cluster-yes
// 迁移限速(避免阻塞):
// 配置 cluster-migration-barrier / 控制 reshard 并发
【面试官可能继续追问】
【常见错误回答】
  • ❌ 直接停服扩——业务中断。正确:在线 reshard。
  • ❌ 不拆大 Key 迁移——阻塞。正确:先拆分。
  • ❌ 用单机客户端——不重定向。正确:Cluster 客户端。
【架构师评分标准】
初级 0~40
不知槽模型。
中级 40~60
知扩容,不懂 ASK/大Key。
高级 60~80
槽迁移+ASK+限速+大Key前置+回滚。
架构师 80~100
再加:①容量规划(扩主/从判断);②迁移监控;③故障恢复;④与业务峰值错峰。

第 56 题:Redis 持久化与恢复 持久化

【真实企业业务场景】
Redis 作缓存+部分计数(非纯缓存)。一次宕机后重启,RDB 恢复丢 10 分钟计数;AOF 又太慢。要求:选对持久化策略,平衡恢复速度与数据安全。
【面试官问题】
    1. RDB vs AOF?2. 混合持久化?3. 恢复速度?4. 缓存要不要持久化?5. fork 阻塞?
【候选人的标准回答】

第一步:分析。持久化=数据安全 vs 性能。缓存可丢(丢了解释回源),计数/队列不能丢需持久化。

第二步:挑战。恢复速度、数据丢失窗口、fork 阻塞。

第三步:架构。缓存:可关持久化或仅 RDB(丢了解释回源,恢复快);②重要数据:AOF(everysec)+ 混合持久化(RDB+AOF)——重启先加载 RDB 快,再重放 AOF 增量,兼顾速度与安全;③fork:bgsave 时 COW 可能阻塞,控制内存/低频。

第四步:选型。混合持久化(Redis 5+ 默认 aof-use-rdb-preamble yes)+ everysec。

第五步:一致性。持久化保证重启后数据可恢复;缓存丢可回源。

第六步:高可用。主从+哨兵,宕机切从,持久化兜底。

第七步:优化。缓存禁用持久化提性能;重要数据混合持久化。

【架构设计】
缓存: 可关持久化(丢→回源) 或 RDB 重要数据: AOF(everysec) + 混合持久化(RDB头+AOF尾) 宕机: 从节点接管 / 重启混合恢复
【技术方案深度解析】

RDB:快照,恢复快,但丢上次快照后数据,bgsave fork 有阻塞。AOF:记命令,everysec 最多丢 1s,但文件大恢复慢。混合:RDB 头(快速载入)+ AOF 增量尾(补丢失),鱼与熊掌。缓存若可回源,关持久化性能最佳。

【关键技术点】
RDBAOF混合持久化everysecfork/COW缓存可丢
【Java 实现示例】
// redis.conf 关键配置
// 重要数据:
appendonly yes
appendfsync everysec
aof-use-rdb-preamble yes   // 混合持久化
// 纯缓存(可丢):
save ""            // 关 RDB
appendonly no
// 应用侧: 缓存丢→回源DB重建
【面试官可能继续追问】
【常见错误回答】
  • ❌ 缓存也开 AOF——浪费 IO。正确:可关,回源。
  • ❌ 重要数据只 RDB——丢快照后。正确:混合/AOF。
  • ❌ 用 always——太慢。正确:everysec。
【架构师评分标准】
初级 0~40
不知 RDB/AOF。
中级 40~60
知两者,不分场景。
高级 60~80
场景选择+混合+缓存可丢+主从。
架构师 80~100
再加:①恢复速度量化;②fork 影响与调优;③数据分级(可丢/不可丢);④与高可用配合。

第 57 题:Redis 限流器实现 限流

【真实企业业务场景】
开放 API 需对「每用户 100 次/分钟」「全局 1 万 QPS」限流,且要平滑(不秒空令牌)。要求:用 Redis 实现分布式限流,支持滑动窗口与令牌桶。
【面试官问题】
    1. 固定窗口 vs 滑动窗口?2. 滑动窗口怎么用 Redis?3. 令牌桶 Lua?4. 集群限流?5. 限流误杀?
【候选人的标准回答】

第一步:分析。限流保护系统,Redis 实现分布式共享计数。滑动窗口更平滑,令牌桶支持突发。

第二步:挑战。边界突刺、原子性、集群、误杀。

第三步:架构。滑动窗口:ZSET 存请求时间戳,统计窗口内数量(Lua 原子);②令牌桶:Lua 计算当前令牌(按速率补充),够则放行;③集群:单 Key 限流(如按用户/接口 Hash 到固定节点)或 Redis Token Server;④误杀:阈值留余量 + 多维限流(用户/IP/接口)。

第四步:选型。Redis + Lua(原子)+ ZSET/计数器。

第五步:一致性。Lua 保证计数原子,限流决策一致。

第六步:高可用。Redis 故障 fail-open(放行但有监控)或 fail-close(按风险)。

第七步:优化。本地令牌桶做第一道(降 Redis 压力),Redis 做全局。

【架构设计】
请求 → 本地令牌桶(粗) → Redis滑动窗口/Lua(全局原子) 滑动窗口: ZSET 统计窗口内次数; 令牌桶: Lua补令牌 故障: fail-open(监控) / fail-close(高风险)
【技术方案深度解析】

固定窗口缺陷:窗口边界突刺(59s+1s 各满,瞬间 2 倍)。滑动窗口:ZSET 按时间计数,平滑但内存随请求数增。令牌桶 Lua:维护 last_refill + tokens,每次按 elapsed×rate 补,原子扣减,支持突发且平滑。fail 策略:限流组件故障不能阻断正常业务(fail-open),高风险的(如防刷)fail-close。

【关键技术点】
滑动窗口令牌桶Redis LuaZSET集群限流fail-open
【Java 实现示例】
// 滑动窗口限流(Lua, 原子)
private static final String SLIDE = """
    redis.call('ZREMRANGEBYSCORE', KEYS[1], 0, ARGV[1]-tonumber(ARGV[2]))
    local n = redis.call('ZCARD', KEYS[1])
    if n >= tonumber(ARGV[3]) then return 0 end
    redis.call('ZADD', KEYS[1], ARGV[1], ARGV[1])
    return 1""";
// 用户维度: key=user:limit:uid, 窗口=60000ms, 限100
Long ok = redis.eval(SLIDE, List.of("user:limit:"+uid),
    now+"", "60000", "100");
【面试官可能继续追问】
【常见错误回答】
  • ❌ 固定窗口——边界突刺。正确:滑动/令牌桶。
  • ❌ 计数非原子——超放。正确:Lua。
  • ❌ 限流故障全断——雪崩。正确:fail-open。
【架构师评分标准】
初级 0~40
只用固定窗口。
中级 40~60
知滑动窗口,非原子。
高级 60~80
Lua原子+滑动/令牌+集群+fail策略。
架构师 80~100
再加:①本地+全局双层;②多维限流防误杀;③故障降级设计;④阈值与监控。

第 58 题:Redis vs Caffeine 多级缓存取舍 选型

【真实企业业务场景】
商品详情页每秒 50 万读,全走 Redis 单分片压力高、RT 2ms 仍占大量连接。团队争论:是否加本地缓存 Caffeine。要求:给出多级缓存架构与一致性方案。
【面试官问题】
    1. 两级缓存职责?2. 一致性怎么保?3. 本地内存爆?4. 何时只用 Redis?5. 多级读流程?
【候选人的标准回答】

第一步:分析。多级=本地(Caffeine,纳秒级,容量小)+ Redis(分布式,毫秒,容量大)+ DB。就近优先,降远端压力。

第二步:挑战。一致性、内存、失效广播。

第三步:架构。①读:本地→Redis→DB,逐级回源;②写:更 DB→删 Redis→广播删本地;③本地用 Caffeine(LRU/窗口淘汰),短 TTL(如 1~5s)+ 失效广播;④只热数据进本地,控内存。

第四步:选型。Caffeine(本地)+ Redis(分布式)+ 广播(Redis pub/sub/配置中心)。

第五步:一致性。本地短 TTL+广播近实时;强一致走源。

第六步:高可用。本地降级(Redis/DB 仍可用);Redis 挂本地撑短窗。

第七步:优化。本地只缓存热点;监控命中率调参。

【架构设计】
读: 本地Caffeine → Redis → DB (逐级回源) 写: DB → 删Redis → 广播删本地(pub/sub) 本地: 短TTL+LRU+失效广播
【技术方案深度解析】

为什么多级?本地命中省网络+序列化,RT 从 2ms→微秒,Redis 压力骤降。一致性权衡:本地是「牺牲一点新鲜换性能」,短 TTL+广播把脏窗口压到秒级,适合读多写少展示。何时只用 Redis:数据强一致/写频繁/单机内存小,本地反而添乱。

【关键技术点】
Caffeine多级缓存失效广播逐级回源LRU命中率
【Java 实现示例】
// 多级读
public Item load(Long id){
  Item v = local.getIfPresent(id);          // 1 本地
  if (v != null) return v;
  v = redis.get(id);                          if (v != null) { local.put(id,v); return v; } // 2 Redis
  v = db.load(id); redis.set(id,v,30min); local.put(id,v); return v; // 3 DB
}
// 失效广播
redis.publish("inv", id+""); // 各实例 local.invalidate
【面试官可能继续追问】
【常见错误回答】
  • ❌ 全量本地——内存爆+脏。正确:只热点+短TTL。
  • ❌ 本地不失效——永久脏。正确:TTL+广播。
  • ❌ 强一致也本地——错。正确:直源。
【架构师评分标准】
初级 0~40
只用 Redis。
中级 40~60
知本地,无失效。
高级 60~80
多级+逐级回源+广播+容量治理。
架构师 80~100
再加:①一致性窗口量化;②命中率优化;③场景取舍(何时不用本地);④广播成本控制。

第 59 题:多级缓存落地与一致性收敛 综合

【真实企业业务场景】
综合 Q51~Q58:电商商品域需同时解决穿透/击穿/雪崩/热 Key/大 Key/一致性,形成一套端到端多级缓存体系,命中率 ≥ 99%、DB 回源 ≤ 1%。要求:给出整体方案。
【面试官问题】
    1. 整体分层?2. 各层职责与阈值?3. 一致性总策略?4. 监控看什么?5. 故障降级链?
【候选人的标准回答】

第一步:分析。把分散的缓存技术编排为「本地→Redis→DB」三级 + 防护层(布隆/限流)+ 兜底(降级)。

第二步:挑战。多层一致、阈值联动、可观测、降级链。

第三步:架构。本地 Caffeine(热 Key,1~5s)+ Redis(分布式,分片打散热/大 Key,错峰 TTL)+ DB(裁判);②防护:布隆防穿透、逻辑过期/互斥防击穿、错峰防雪崩、滑动窗口限流;③一致:写更 DB→删 Redis→广播本地+binlog 兜底;④监控:命中率/回源 QPS/热 Key/大 Key;⑤降级:Redis 挂→本地+限流+默认值。

第四步:选型。见前述各题组合 + Prometheus/Grafana 监控。

第五步:一致性。多级最终一致,强一致走源;binlog 兜底。

第六步:高可用。逐层降级;Redis 集群 HA;限流保 DB。

第七步:优化。命中率驱动调参;自动热 Key 晋升。

【架构设计】
读: 本地Caffeine → Redis(分片/错峰/逻辑过期) → DB 防护: 布隆(穿透) + 互斥(击穿) + 错峰TTL(雪崩) + 限流 写: DB → 删Redis → 广播本地 + binlog兜底 降级链: Redis挂→本地+限流+默认
【技术方案深度解析】

为什么收敛为体系?单点方案各自为政易冲突;体系化按「命中率目标」反向设计每层阈值。一致性总策略:删缓存(非更新)+ 广播 + binlog 兜底,保证最终一致。降级链:明确每层故障时的行为,DB 始终是最后裁判但被限流保护。

【关键技术点】
多级缓存防护层binlog兜底降级链命中率阈值联动
【Java 实现示例】
// 体系封装: 读模板(本地→Redis→DB) + 防护
public <T> T getCached(Long id, Loader<T> db) {
  if (!bloom.mightContain(id)) return null;  // 防穿透
  T v = local.get(id, k -> redis.get(k, db));     // 多级
  return v;
}
// 写: DB → 删Redis → 广播本地 → binlog兜底
【面试官可能继续追问】
【常见错误回答】
  • ❌ 各层方案零散——冲突。正确:体系化联动。
  • ❌ 无降级链——单点故障全崩。正确:逐层降级。
  • ❌ 无监控——黑盒。正确:命中率/回源可观测。
【架构师评分标准】
初级 0~40
零散无体系。
中级 40~60
多级但无防护/监控。
高级 60~80
三级+防护+一致+降级+监控。
架构师 80~100
再加:①命中率目标驱动设计;②阈值联动推导;③降级 SOP;④成本与容量规划。

第 60 题:Redis 延迟调优与慢查询 调优

【真实企业业务场景】
Redis P99 从 1ms 涨到 20ms,业务超时。排查:有大 Key、慢命令(KEYS *)、fork 阻塞、网络抖。要求:系统定位并优化 Redis 延迟。
【面试官问题】
    1. 延迟来源?2. 慢命令怎么治?3. fork 阻塞?4. 网络/客户端?5. 大 Key 与延迟?
【候选人的标准回答】

第一步:分析。Redis 延迟=命令自身(大 Key/慢命令)+ 阻塞(fork/同步操作)+ 网络/客户端(连接、序列化)。

第二步:挑战。多源定位、慢命令、fork、网络。

第三步:架构。监控:slowlog、latency-monitor 定位;②慢命令:禁 KEYS/FLUSHALL,用 SCAN/HSCAN;大 Key 拆分(Q54);③fork:控制内存、低频 bgsave、用大页;④网络:pipeline/批量、连接池复用、就近部署;⑤客户端:避免大批量一次性读。

第四步:选型。slowlog + latency-monitor + SCAN + pipeline + 连接池。

第五步:一致性。延迟优化不影响语义;pipeline 注意原子性。

第六步:高可用。慢命令隔离(从节点执行);主从降阻塞。

第七步:优化。命令时间复杂度 O(1);热数据本地缓存降 Redis 调用。

【架构设计】
延迟源: 慢命令(KEYS/大Key) / fork阻塞 / 网络 / 客户端 治: SCAN替代KEYS + 拆大Key + 控fork + pipeline + 连接池
【技术方案深度解析】

O(1) 原则:KEYS 是 O(N) 全扫,必慢;用 SCAN 游标分批。fork 阻塞:bgsave/rewrite 时主进程 fork 子进程,内存大时 COW 卡顿,控制实例内存(<10G)、低频。pipeline:合并多次 RT 往返,但单次数据量别太大(避免阻塞+网络)。

【关键技术点】
slowloglatency-monitorSCANfork/COWpipeline连接池
【Java 实现示例】
// 用 SCAN 替代 KEYS(分批, 不阻塞)
var cur = ScanOptions.SCAN_POINTER_START;
do { List<String> ks = redis.scan(cur, 1000); process(ks); }
while (!cur.isComplete());
// pipeline 批量(降RT往返)
redis.pipelined(p -> { keys.forEach(k -> p.get(k)); }); // 单次量控
// 配置: slowlog-log-slower-than 10000(10ms) + latency-monitor
【面试官可能继续追问】
【常见错误回答】
  • ❌ 用 KEYS 全扫——阻塞。正确:SCAN。
  • ❌ 实例内存过大——fork 卡。正确:控内存。
  • ❌ 不查 fork/网络——漏因。正确:全源排查。
【架构师评分标准】
初级 0~40
不知慢命令危害。
中级 40~60
知 SCAN,不论证延迟源。
高级 60~80
多源定位+slowlog+SCAN+拆大Key+fork。
架构师 80~100
再加:①latency-monitor 体系;②网络/客户端优化;③容量与 fork 调优;④监控与告警。
第五部分 · Redis 与缓存架构(第 51~60 题) · 返回大纲