为什么你的高并发系统总崩溃?90%的Java程序员都栽在这三个Redis问题上!
作为Java程序员,Redis是你简历上的必备技能,但你真的能搞定缓存穿透、雪崩、击穿这三大“高并发杀手”吗?本文不仅教你原理,更给出可直接抄作业的Java代码方案,让你轻松应对大厂级高并发场景!
一、缓存穿透:黑客最爱攻击的漏洞!💥
1.1 现象与危害
-
场景:用户疯狂请求
userId=-1
的数据(数据库根本不存在) -
后果:每秒10万请求直接击穿Redis,MySQL被打挂!
1.2 解决方案(附Java代码)
🔥 方案1:布隆过滤器(Bloom Filter)—— 拦截非法请求
// 使用Guava布隆过滤器(适合单机场景)
BloomFilter<String> bloomFilter = BloomFilter.create(
Funnels.stringFunnel(Charset.defaultCharset()),
1000000, // 预期元素数量
0.01 // 误判率
);
// 预热合法数据
bloomFilter.put("validKey1");
bloomFilter.put("validKey2");
// 请求拦截
if (!bloomFilter.mightContain(key)) {
return Result.error("非法请求!");
}
🔥 方案2:空值缓存 + 熔断降级
public Object getData(String key) {
Object value = redisTemplate.opsForValue().get(key);
if (value != null) {
if ("NULL".equals(value)) { // 空值标识
return null; // 直接返回空,避免穿透
}
return value;
}
// 数据库查询
Object dbData = dbMapper.selectByKey(key);
if (dbData == null) {
// 空值缓存(5分钟过期)
redisTemplate.opsForValue().set(key, "NULL", 5, TimeUnit.MINUTES);
// 触发熔断:Hystrix降级
throw new ServiceDegradeException("数据不存在!");
}
redisTemplate.opsForValue().set(key, dbData, 1, TimeUnit.HOURS);
return dbData;
}
二、缓存雪崩:系统崩溃只需1秒钟!🌨️
2.1 现象与危害
-
场景:双11零点,所有商品缓存同时过期
-
后果:数据库瞬间QPS飙升到10万+,直接宕机!
2.2 解决方案(附Java代码)
🔥 方案1:随机过期时间 + 多级缓存
// 基础过期时间 + 随机偏移(防止集体失效)
int baseExpire = 3600;
int randomExpire = new Random().nextInt(600); // 0~10分钟随机
redisTemplate.opsForValue().set(
key,
value,
baseExpire + randomExpire,
TimeUnit.SECONDS
);
// 多级缓存:本地缓存(Caffeine) + Redis
@Bean
public Cache<String, Object> localCache() {
return Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(10000)
.build();
}
🔥 方案2:Redis集群 + Sentinel哨兵
# SpringBoot配置Redis哨兵
spring:
redis:
sentinel:
master: mymaster
nodes: 192.168.1.1:26379,192.168.1.2:26379,192.168.1.3:26379
三、缓存击穿:热点数据引发的血案!⚡
3.1 现象与危害
-
场景:微博热搜突然爆炸,但缓存刚好过期
-
后果:百万请求同时查询数据库,服务器直接冒烟!
3.2 解决方案(附Java代码)
🔥 方案1:Redisson分布式锁(强一致)
public Object getData(String key) {
Object value = redisTemplate.opsForValue().get(key);
if (value != null) return value;
RLock lock = redisson.getLock("LOCK:" + key);
try {
if (lock.tryLock(5, 30, TimeUnit.SECONDS)) { // 非阻塞锁
// 二次检查(Double Check)
value = redisTemplate.opsForValue().get(key);
if (value != null) return value;
// 查数据库并重建缓存
value = dbMapper.selectByKey(key);
redisTemplate.opsForValue().set(key, value, 1, TimeUnit.HOURS);
}
} finally {
lock.unlock();
}
return value;
}
🔥 方案2:逻辑过期 + 异步刷新(高并发最优解)
@Data
public class RedisData {
private Object data;
private LocalDateTime expireTime; // 逻辑过期时间
}
// 缓存数据时设置逻辑过期时间
RedisData redisData = new RedisData();
redisData.setData(dbData);
redisData.setExpireTime(LocalDateTime.now().plusMinutes(30));
redisTemplate.opsForValue().set(key, redisData);
// 异步线程池定时刷新热点Key
@Scheduled(fixedRate = 60000) // 每分钟检查
public void refreshHotKeys() {
// 获取热点Key列表,提前刷新缓存
}
四、大厂级解决方案对比表(建议保存!)
问题 | 适用场景 | 方案 | 优点 | 缺点 |
---|---|---|---|---|
缓存穿透 | 恶意请求攻击 | 布隆过滤器 + 空值缓存 | 拦截效率高,内存占用低 | 布隆过滤器有误判率 |
缓存雪崩 | 大规模缓存失效 | 随机过期 + 多级缓存 | 实现简单,可靠性高 | 本地缓存需要维护 |
缓存击穿 | 热点Key失效 | Redisson锁 + 逻辑过期 | 高并发性能好,无阻塞 | 实现复杂度较高 |
五、实战技巧:让面试官眼前一亮的骚操作!🚀
-
布隆过滤器误判率公式:
P ≈ (1 - e^{-k \cdot n / m})^kP≈(1−e−k⋅n/m)k
(调整k和m,平衡内存与误判率) -
Redisson看门狗原理:自动续期锁时间,避免死锁(默认30秒续期)
-
热点Key探测:使用Redis的
hotkeys
命令或监控报警系统