第五部分:Redis 与缓存架构(第 51~60 题)
缓存是高并发基石。本部分覆盖穿透/击穿/雪崩、一致性、热 Key、大 Key、Cluster 扩容、多级缓存、延迟调优。每题需给出:现象→根因→方案→代码→兜底。
第 51 题:缓存穿透 / 击穿 / 雪崩 三位一体治理 缓存
- 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 预热+逻辑过期免击穿;布隆误判率调优。
穿透 vs 击穿 vs 雪崩:穿透是「数据本不存在」,击穿是「单热点过期」,雪崩是「大面积失效」。逻辑过期:Key 不真删,值里带过期时间,过期后单线程异步刷新,期间返回旧值,彻底防击穿。布隆误判:说存在的可能不存在(放行查 DB),说不存在的一定不存在(拦截),故只挡穿透不误杀。
// 逻辑过期:值带 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);
追问 1:布隆误杀?不会误杀存在的(只可能误放不存在的)。误放由空值缓存二次兜底。
追问 2:逻辑过期数据太旧?设最大新鲜度,超阈值强制同步刷新;监控旧值占比。
追问 3:雪崩 Redis 全挂?限流+降级(返回默认/排队)+ DB 保护;Redis 恢复后预热。
追问 4:空值缓存被刷?空值 TTL 短 + 布隆拦截;防止恶意批量打空值占内存。
追问 5:热点 Key 不打热点标记?运行时统计访问频次自动识别 + 提前逻辑过期保护。
- ❌ 不存在的 ID 不缓存——穿透。正确:空值缓存+布隆。
- ❌ 热点 Key 删了重建——击穿。正确:逻辑过期/互斥。
- ❌ 统一 TTL——雪崩。正确:错峰抖动。
第 52 题:缓存与数据库一致性 一致性
- 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 保证新鲜。
先更库再删缓存(推荐):若先删缓存再更库,删后到更新间有读会回填旧值。先更库再删,最多短暂脏(删除前读到的旧值),且 binlog 兜底最终修正。为什么双删:防「删缓存后、更新前」的并发读回填旧值,延迟再删一次。binlog 方案:与业务解耦,可靠保证最终一致,是主流做法。
// 写: 先更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)); } // 最终一致
追问 1:能不能强一致?难且贵:用分布式锁让读写串行,或读写都走 DB(弃缓存)。一般接受最终一致+补偿。
追问 2:binlog 延迟导致脏读?延迟秒级,业务可接受;价格类关键用双删+短 TTL 缩短窗口。
追问 3:删缓存失败?重试 + binlog 兜底;或本地消息表保证最终删除。
追问 4:更新缓存还是删缓存?删更通用(下次读回源),避免复杂计算写入;除非计算贵才更新。
追问 5:并发写冲突?按 DB 为准,缓存删即可,最后写赢;关键顺序靠 binlog 保。
- ❌ 先删缓存再更库——回填旧值。正确:先更库再删。
- ❌ 更新缓存值——并发脏。正确:删缓存更稳。
- ❌ 无兜底——删除失败即脏。正确:binlog 兜底。
第 53 题:热 Key 探测与本地缓存 热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 自动识别 + 自动晋升本地;写热点用分桶。
为什么本地缓存?把 80 万 QPS 从 Redis 单分片分散到各应用本地内存,Redis 压力趋零。不一致控制:本地 TTL 短 + 更新时广播失效,窗口内可能读到旧值(秒级),适合读多写少展示。写热点:本地缓存不适用写,需分桶(同 Key 拆成 key#1..N 轮询写)。
// 本地缓存 + 失效广播 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)));
追问 1:本地不一致投诉?缩短 TTL + 失效广播近实时;关键字段读源。
追问 2:本地内存爆?只晋升热 Key(限量 LRU)+ 短 TTL 自动淘汰。
追问 3:写热点怎么治?分桶(key#0..N)轮询写,读聚合/随机选一。
追问 4:热 Key 转移快?滑动窗口统计 + 秒级识别 + 自动晋升。
追问 5:Redis 挂本地能撑?本地仅撑短窗口(TTL 内);Redis 恢复后回源,需配合降级。
- ❌ 加 Redis 节点——单 Key 仍单分片。正确:本地/分片打散。
- ❌ 本地缓存永不失效——脏数据。正确:TTL+广播。
- ❌ 全量本地——内存爆。正确:只热 Key。
第 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);监控预警。
为什么阻塞?Redis 单线程,DEL 大 Key 同步释放内存耗时;HGETALL 大 Key 一次性序列化占网络。UNLINK:异步释放,主线程只标记,后台线程回收,不卡。拆分原则:单 Key field/元素控制在上万内,按分桶打散,读写并行。
// 分桶: 按 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); // 异步最终删 }
追问 1:拆分后聚合慢?并行读多桶(多 pipeline);或本地缓存热子集。
追问 2:集群迁移大 Key?大 Key 迁移慢且阻塞,拆分后再迁移;用 UNLINK。
追问 3:String 大值(10MB)?压缩 + 分块存储;或改对象存储。
追问 4:监控怎么预警?内存/元素数阈值告警;bigkeys 定时扫描。
追问 5:设计期预防?单 Key 大小上限 + Code Review + 压测暴露。
- ❌ DEL 大 Key——主线程卡 3s。正确:UNLINK/分批。
- ❌ HGETALL 大 Key——阻塞网络。正确:HSCAN 分批。
- ❌ 不做拆分——持续风险。正确:分桶。
第 55 题:Redis Cluster 扩容与槽迁移 Cluster
- 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 限速);低峰执行;监控迁移进度。
槽模型:16384 槽均匀分给主节点,Key 经 CRC16%16384 落槽。ASK 重定向:b>迁移中的 Key,源节点标记「迁移中」,客户端访问返回 ASK,引导到目标节点;目标已有则返回。为什么限速:槽迁移逐 Key 复制,过快占带宽/CPU 影响在线。
// 客户端需支持 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 并发
追问 1:迁移中写丢?源与目标双写过渡,源删前目标已存在,不丢;客户端重试。
追问 2:大 Key 迁移卡?先拆分大 Key(Q54)再迁;否则单 Key 迁移阻塞该槽。
追问 3:客户端不重定向?必须用 Cluster 客户端(自动 MOVED/ASK),否则拿到错误节点。
追问 4:迁移一半崩?槽状态可恢复;重新 reshard 续迁;源仍完整。
追问 5:扩还是加从?内存满扩主(分片);读多扩从(读副本)。先判断瓶颈。
- ❌ 直接停服扩——业务中断。正确:在线 reshard。
- ❌ 不拆大 Key 迁移——阻塞。正确:先拆分。
- ❌ 用单机客户端——不重定向。正确:Cluster 客户端。
第 56 题:Redis 持久化与恢复 持久化
- 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:快照,恢复快,但丢上次快照后数据,bgsave fork 有阻塞。AOF:记命令,everysec 最多丢 1s,但文件大恢复慢。混合:RDB 头(快速载入)+ AOF 增量尾(补丢失),鱼与熊掌。缓存若可回源,关持久化性能最佳。
// redis.conf 关键配置 // 重要数据: appendonly yes appendfsync everysec aof-use-rdb-preamble yes // 混合持久化 // 纯缓存(可丢): save "" // 关 RDB appendonly no // 应用侧: 缓存丢→回源DB重建
追问 1:everysec 丢 1s 能接受?缓存/计数可接受;金融核心用 always(性能差)或不在 Redis 存。
追问 2:fork 阻塞严重?控制实例内存(< 10G)、低频 bgsave、用大页优化。
追问 3:AOF 文件太大?定期 BGREWRITEAOF 压缩(重写)。
追问 4:缓存持久化浪费 IO?缓存关持久化,宕机回源,性能最优。
追问 5:主从都持久化?从可关(主有即可),从重启从主全量同步;省从 IO。
- ❌ 缓存也开 AOF——浪费 IO。正确:可关,回源。
- ❌ 重要数据只 RDB——丢快照后。正确:混合/AOF。
- ❌ 用 always——太慢。正确:everysec。
第 57 题: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 做全局。
固定窗口缺陷:窗口边界突刺(59s+1s 各满,瞬间 2 倍)。滑动窗口:ZSET 按时间计数,平滑但内存随请求数增。令牌桶 Lua:维护 last_refill + tokens,每次按 elapsed×rate 补,原子扣减,支持突发且平滑。fail 策略:限流组件故障不能阻断正常业务(fail-open),高风险的(如防刷)fail-close。
// 滑动窗口限流(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");
追问 1:滑动窗口内存大?ZSET 元素=窗口内请求数,高频需控窗口/降精度(计数桶)。
追问 2:集群多实例计数不一?限流 Key 固定到单节点(hash tag)或独立 Token Server 保证全局一致。
追问 3:突发流量令牌桶空?桶容量=突发上限;平滑用滑动窗口。
追问 4:限流误杀正常?多维(用户+IP)+ 阈值余量 + 白名单;监控误杀率。
追问 5:Redis 挂?fail-open 放行(监控)+ 本地兜底限流,防雪崩。
- ❌ 固定窗口——边界突刺。正确:滑动/令牌桶。
- ❌ 计数非原子——超放。正确:Lua。
- ❌ 限流故障全断——雪崩。正确:fail-open。
第 58 题:Redis vs 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 挂本地撑短窗。
第七步:优化。本地只缓存热点;监控命中率调参。
为什么多级?本地命中省网络+序列化,RT 从 2ms→微秒,Redis 压力骤降。一致性权衡:本地是「牺牲一点新鲜换性能」,短 TTL+广播把脏窗口压到秒级,适合读多写少展示。何时只用 Redis:数据强一致/写频繁/单机内存小,本地反而添乱。
// 多级读 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
追问 1:本地脏窗口投诉?缩短 TTL + 即时广播;关键字段读源。
追问 2:多实例广播风暴?只广播 Key(小),不广播值;或用配置中心批量。
追问 3:本地内存失控?容量上限 + LRU/窗口淘汰 + 只热数据。
追问 4:强一致数据?不走本地,直读 Redis/DB。
追问 5:命中率怎么调?监控各级命中率,调 TTL/容量/晋升策略。
- ❌ 全量本地——内存爆+脏。正确:只热点+短TTL。
- ❌ 本地不失效——永久脏。正确:TTL+广播。
- ❌ 强一致也本地——错。正确:直源。
第 59 题:多级缓存落地与一致性收敛 综合
- 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 晋升。
为什么收敛为体系?单点方案各自为政易冲突;体系化按「命中率目标」反向设计每层阈值。一致性总策略:删缓存(非更新)+ 广播 + binlog 兜底,保证最终一致。降级链:明确每层故障时的行为,DB 始终是最后裁判但被限流保护。
// 体系封装: 读模板(本地→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兜底
追问 1:命中率不达标?查漏点(穿透/大 Key/本地容量),调 TTL/晋升策略。
追问 2:多层一致冲突?以「删缓存+binlog」为准,本地短 TTL 兜底,不追求强一致。
追问 3:降级后数据旧?降级是保护手段,窗口短;恢复后回源刷新。
追问 4:监控告警项?命中率、回源 QPS、热/大 Key、Redis RT、限流拒绝率。
追问 5:容量规划?按峰值 QPS×本地比例算本地内存;Redis 按命中率反推。
- ❌ 各层方案零散——冲突。正确:体系化联动。
- ❌ 无降级链——单点故障全崩。正确:逐层降级。
- ❌ 无监控——黑盒。正确:命中率/回源可观测。
第 60 题:Redis 延迟调优与慢查询 调优
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 调用。
O(1) 原则:KEYS 是 O(N) 全扫,必慢;用 SCAN 游标分批。fork 阻塞:bgsave/rewrite 时主进程 fork 子进程,内存大时 COW 卡顿,控制实例内存(<10G)、低频。pipeline:合并多次 RT 往返,但单次数据量别太大(避免阻塞+网络)。
// 用 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
追问 1:slowlog 无慢命令但 RT 高?查 fork/网络/客户端(序列化大对象)。
追问 2:pipeline 数据太大?分批(每批几百),避免单次阻塞+网络拥塞。
追问 3:fork 频繁?合并 bgsave 周期;用 AOF 增量减少 RDB 频率。
追问 4:客户端连接数爆?连接池上限+复用;避免短连。
追问 5:集群跨 AZ 延迟?同 AZ 部署客户端与 Redis;或用本地缓存降调用。
- ❌ 用 KEYS 全扫——阻塞。正确:SCAN。
- ❌ 实例内存过大——fork 卡。正确:控内存。
- ❌ 不查 fork/网络——漏因。正确:全源排查。