缓存系统是现代互联网架构中不可或缺的一环,而Redis作为高性能的内存数据存储,广泛应用于各类缓存场景。在高并发系统中,缓存可能面临穿透、击穿和雪崩三大问题,这些问题若处理不当,会导致系统性能急剧下降甚至崩溃。
缓存穿透原理与应对
原理
缓存穿透指的是查询不存在的数据,请求绕过缓存直接访问数据库,导致数据库压力过大。常见场景有恶意攻击、错误查询或业务逻辑漏洞。
解决方案
缓存空对象
public Object get(String key) {
// 先查缓存
Object value = redis.get(key);
if (value != null) {
return value.equals("NULL") ? null : value;
}
// 缓存未命中,查数据库
value = db.query(key);
if (value == null) {
// 缓存空对象,设置较短过期时间
redis.setex(key, 60, "NULL");
} else {
redis.setex(key, 3600, value);
}
return value;
}布隆过滤器
// 使用RedisBloom插件创建布隆过滤器
BF.RESERT myfilter 0.01 1000000
// 添加已存在的key到布隆过滤器
BF.ADD myfilter "user:1001"
// 查询前先检查布隆过滤器
public Object get(String key) {
// 布隆过滤器检查
if (!BF.EXISTS myfilter key) {
return null;
}
// 后续缓存逻辑...
}参数校验与业务限制
- 对输入参数进行有效性校验
- 限制单IP/用户的查询频率
- 实现查询结果分页,避免全表查询
缓存击穿原理与应对
原理
缓存击穿指某个热点key突然失效,大量并发请求直接访问数据库,导致数据库压力激增。
解决方案
互斥锁/分布式锁
public Object getWithLock(String key) {
// 先查缓存
Object value = redis.get(key);
if (value != null) {
return value;
}
// 获取分布式锁
String lockKey = "lock:" + key;
try {
boolean locked = redis.setnx(lockKey, "1", 10); // 10秒过期
if (locked) {
// 查询数据库
value = db.query(key);
if (value != null) {
redis.setex(key, 3600, value);
}
return value;
} else {
// 未获取到锁,短暂休眠后重试
Thread.sleep(50);
return getWithLock(key);
}
} finally {
redis.del(lockKey);
}
}永不过期策略
// 使用Redis的过期时间监听
public void listenExpiredEvents() {
Jedis jedis = new Jedis("localhost");
jedis.psubscribe(new JedisPubSub() {
@Override
public void onPMessage(String pattern, String channel, String message) {
// 收到过期事件,异步更新缓存
asyncRefreshCache(message);
}
}, "__keyevent@*__:expired");
}热点数据预加载
- 系统启动时预加载热点数据
- 定时任务刷新热点数据
- 基于访问频率预测热点数据
缓存雪崩原理与应对
原理
缓存雪崩指大量key在同一时间失效,导致大量请求直接访问数据库,造成数据库压力过大甚至宕机。
解决方案
缓存过期时间随机化
// 设置随机过期时间
public void setWithRandomExpire(String key, Object value, int baseExpire) {
int expire = baseExpire + new Random().nextInt(300); // 增加0-5分钟随机时间
redis.setex(key, expire, value);
}多级缓存架构
客户端缓存 → 本地缓存 → Redis集群 → 数据库降级与熔断策略
// 使用Hystrix实现熔断
public Object getWithCircuitBreaker(String key) {
return HystrixCommandFactory.create()
.run(() -> {
// 正常缓存逻辑
return redis.get(key);
}, fallback -> {
// 熔断降级逻辑
if (fallback.isCircuitBreakerOpen()) {
return "系统繁忙,请稍后重试";
}
return db.query(key);
})
.execute();
}常见问题与注意事项
- 性能权衡:布隆过滤器有一定误判率,互斥锁可能增加响应时间,需根据业务场景选择合适方案。
- 实现陷阱:
- 互斥锁过期时间应大于业务处理时间
- 空对象缓存时间不宜过长
- 随机化过期时间范围需合理设置
- 监控与告警:
- 监控缓存命中率
- 监控数据库负载
- 设置合理的告警阈值
总结
缓存穿透、击穿和雪崩是Redis缓存系统中的常见问题。针对穿透问题,可采用缓存空对象、布隆过滤器或参数校验;针对击穿问题,可使用互斥锁、永不过期策略或热点数据预加载;针对雪崩问题,可采用过期时间随机化、多级缓存、降级熔断或缓存预热策略。实际应用中,应根据业务特点、数据访问模式和系统负载,选择合适的解决方案组合,并做好监控和应急预案,以确保系统在高并发场景下的稳定运行。
主楼