本文是大数据系列第 49 篇,系统讲解 Redis 高并发场景下的五类经典问题及其解决方案。
缓存穿透(Cache Penetration)
问题描述
请求的数据在缓存和数据库中均不存在,导致每次请求都绕过缓存直接打到数据库,形成无效的 DB 查询压力。常见于恶意攻击(使用随机/无效的 ID 大量请求)。
解决方案
方案一:缓存空值
数据库查询结果为空时,将 null 或空标记值写入缓存,设置较短 TTL(30-60 秒),防止重复打库。
方案二:布隆过滤器(Bloom Filter)
在缓存层前部署布隆过滤器,将所有合法 key 提前写入。请求到来时先查布隆过滤器,确定不存在的直接拦截,不查缓存和数据库。
布隆过滤器原理:使用长度为 m 的 bit 数组和 k 个独立哈希函数。写入元素时将 k 个哈希位置置 1;查询时若任意位置为 0,则该元素一定不存在;若全为 1,则大概率存在(存在误判率)。
误判率公式:p ≈ (1 - e^(-kn/m))^k,可通过增大 m 或调整 k 降低误判率。
方案三:参数校验 + 限流
在业务层对请求参数合法性做强校验,配合接口限流和异常请求监控,提前拦截非法请求。
缓存雪崩(Cache Avalanche)
问题描述
大量 key 同时过期,或 Redis 集群故障,导致海量请求同时穿透到数据库,极端情况引发系统级联崩溃。
解决方案
方案一:过期时间打散
避免批量写入时设置相同的 TTL,在基础时间上叠加随机偏移:
int expireTime = baseTime + ThreadLocalRandom.current().nextInt(0, 300);
redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
方案二:多级缓存
构建本地缓存(Caffeine/Guava)→ 分布式缓存(Redis)→ 数据库的三级防护,Redis 故障时本地缓存可兜底。
方案三:高可用架构
- 部署 Redis Sentinel 或 Redis Cluster,避免单点故障
- 业务层集成熔断降级(如 Sentinel、Hystrix),Redis 不可用时降级到数据库或返回兜底数据
- 关键数据在低峰期预热加载
缓存击穿(Cache Breakdown)
问题描述
单个热点 key 过期的瞬间,大量并发请求同时 cache miss,全部打到数据库重建缓存,形成”惊群效应”。
解决方案
方案一:互斥锁(Mutex Lock)
缓存 miss 时,只允许一个线程获取分布式锁去查询数据库重建缓存,其余线程等待或自旋重试。保证数据库只被查询一次。
方案二:永不过期(逻辑过期)
热点 key 不设置物理过期时间,而是在 value 中存储逻辑过期时间。后台异步任务在逻辑过期前提前刷新缓存,用户始终从缓存中读取(可能短暂读到旧数据)。
方案三:提前续期
监控 key 的剩余 TTL,在过期前主动刷新,从根本上消除过期间隙。
数据一致性问题
延迟双删策略
1. 更新数据库前删除缓存
2. 更新数据库
3. 等待约 200ms~2s(等其他线程的读请求完成)
4. 再次删除缓存(清理可能被回写的旧数据)
5. 为缓存设置合理 TTL 作为最终兜底
更彻底的方案是监听数据库 Binlog(如 Canal),在数据变更事件触发时精准失效对应缓存,实现准实时的缓存一致性。
热 Key 问题(Hot Key)
问题描述
大量请求集中访问同一个 key,超出单个 Redis 节点的网络带宽或 CPU 处理能力,可能导致节点宕机并引发雪崩。
检测方法
- 线下:
redis-cli --hotkeys(基于 LFU 统计,需开启相应配置) - 线上:
MONITOR命令抓取请求流量(有性能损耗,谨慎使用) - 流式计算:接入 Flink/Spark 实时统计访问频次,将热 key 信息写入 ZooKeeper 通知应用层
解决方案
- 本地缓存兜底:将热 key 复制到应用进程内存(Caffeine),接受短暂数据不一致的代价
- 分片读取:将热 key 复制到多个 Redis 节点(key_1、key_2…key_N),读取时随机选择节点
- 限流 + 熔断:对异常高频访问的 key 进行请求限流,熔断保护后端
大 Key 问题(Big Key)
问题描述
单个 key 的 value 体积过大(String 超过 10KB,集合元素超过 5000),会导致:
- 内存不均匀,影响集群数据迁移和 rebalance
- 读写操作阻塞时间长,其他请求延迟上升
DEL删除大 key 时直接阻塞主线程(同步操作)
检测方法
redis-cli --bigkeys:扫描整个 keyspace(数据量大时耗时较长)- RDB 文件分析工具(如 rdbtools、redis-rdb-tools):离线分析,不影响生产
解决方案
- 拆分大 key:将大 String 拆分为多个 key(如分片存储),将大 Hash/List/Set 按 hash 分桶
- 外部存储:超大 value(图片、文档、序列化对象)存 MongoDB 或 CDN,Redis 中只存引用 ID
- 惰性删除:使用
UNLINK替代DEL,异步后台删除大 key,避免阻塞主线程
# 安全删除大 key
UNLINK big_key_name
总结
| 问题 | 核心成因 | 首选方案 |
|---|---|---|
| 缓存穿透 | 请求不存在的 key | 布隆过滤器 |
| 缓存雪崩 | 大量 key 同时过期 | 过期时间随机化 + 多级缓存 |
| 缓存击穿 | 热点 key 到期瞬间并发 | 互斥锁 / 永不过期 |
| 热 Key | 流量集中单节点 | 本地缓存 + 分片读取 |
| 大 Key | 单 key 数据体积过大 | 拆分 + UNLINK 删除 |