什么是缓存击穿
缓存击穿是指在高并发的情况下,某个热点数据在缓存中失效,导致所有请求直接访问数据库。这样会造成对数据库的瞬时大量请求,可能导致数据库压力过大,甚至崩溃。
场景示例
假设我们有一个热门商品的详情页面,许多用户同时访问这个商品的信息。当该商品的缓存过期时,所有用户的请求都会直接打到数据库,造成数据库的负载骤增。
例如:
- 商品信息存储在 Redis 中,设置了一个过期时间。
- 当缓存过期时,所有请求都尝试访问数据库。
- 由于并发请求量大,数据库可能无法承受,导致性能下降或崩溃。
解决办法
为了解决缓存击穿问题,可以采用以下几种策略:
- 加锁机制:
- 当某个数据的缓存失效时,使用分布式锁来确保只有一个请求能去查询数据库并更新缓存。其他请求在此期间可以等待或返回一个默认值。
代码示例(伪代码):
public User getUserById(String userId) { // 查询 Redis 缓存 User user = redis.get("user:" + userId); if (user != null) { return user; // 缓存命中 } // 加锁,确保只有一个请求去数据库查询 String lockKey = "lock:user:" + userId; if (tryLock(lockKey)) { try { // 再次查询 Redis 缓存,可能已经被其他请求更新 user = redis.get("user:" + userId); if (user != null) { return user; // 缓存命中 } // 查询数据库 user = database.getUserById(userId); if (user != null) { // 将用户信息存入 Redis 缓存 redis.set("user:" + userId, user); } } finally { releaseLock(lockKey); // 释放锁 } } else { // 等待一段时间后重试或返回默认值 return waitForUser(userId); } return user; // 返回用户信息 }
- 当某个数据的缓存失效时,使用分布式锁来确保只有一个请求能去查询数据库并更新缓存。其他请求在此期间可以等待或返回一个默认值。
- 使用预先加载:
- 在缓存即将失效之前,提前加载数据并更新缓存,以避免缓存失效后瞬间大量请求打到数据库。
代码示例(伪代码):
public void refreshCache(String userId) { // 查询数据库 User user = database.getUserById(userId); if (user != null) { // 更新 Redis 缓存 redis.set("user:" + userId, user); } } public User getUserById(String userId) { // 查询 Redis 缓存 User user = redis.get("user:" + userId); if (user != null) { return user; // 缓存命中 } // 缓存未命中,可以调用刷新方法 refreshCache(userId); return redis.get("user:" + userId); // 重新获取缓存 }
- 在缓存即将失效之前,提前加载数据并更新缓存,以避免缓存失效后瞬间大量请求打到数据库。
- 设置随机过期时间:
- 在设置缓存时,为热点数据设置一个随机的过期时间,避免因为所有请求同时过期而导致缓存击穿。
代码示例(伪代码):
public void setUserCache(String userId, User user) { // 设置一个随机的过期时间 int randomExpireTime = 60 + (int)(Math.random() * 60); // 60到120秒 redis.set("user:" + userId, user, randomExpireTime); }
- 在设置缓存时,为热点数据设置一个随机的过期时间,避免因为所有请求同时过期而导致缓存击穿。
总结
缓存击穿是高并发情况下缓存失效导致的性能问题。通过加锁机制、预先加载、设置随机过期时间等策略,可以有效地避免缓存击穿现象,保护数据库的稳定性和性能。以上代码示例展示了如何在实际开发中应用这些策略来应对缓存击穿问题。