解决Redis缓存击穿问题的一种常见方法是使用互斥锁。以下是一个使用Java编写的简单示例,展示了如何利用Redis的SET命令加上NX(仅在键不存在时设置)和EX(设置键的过期时间)选项来实现分布式锁,以防止缓存击穿的情况。
步骤说明:
1. 检查缓存:首先尝试从Redis中获取数据。
2. 未命中处理:
* 如果数据不在缓存中,尝试获取一个分布式锁。
* 获取到锁的线程负责从数据库加载数据并写入缓存。
* 未能获取到锁的线程可以等待一段时间后重试或者返回特定错误信息/默认值。
3. 释放锁:确保在操作完成后释放锁,避免死锁。
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
@Component
public class CacheService {
private final StringRedisTemplate redisTemplate;
private static final String LOCK_PREFIX = "lock:";
private static final Long LOCK_EXPIRE_TIME = 5L; // 锁超时时间,单位:秒
private static final String PRODUCT_KEY = "product:1";
public CacheService(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
public String getProductInfo() {
// 1. 尝试从Redis中获取商品信息
String productInfo = redisTemplate.opsForValue().get(PRODUCT_KEY);
if (productInfo != null) {
return productInfo;
}
// 2. 获取分布式锁
boolean lockAcquired = acquireLock(LOCK_PREFIX + PRODUCT_KEY);
if (!lockAcquired) {
// 未获取到锁,可以返回错误信息或进行重试逻辑
return "Failed to acquire lock, please retry.";
}
try {
// 重新检查缓存,防止在等待锁的过程中其他线程已经填充了缓存
productInfo = redisTemplate.opsForValue().get(PRODUCT_KEY);
if (productInfo != null) {
return productInfo;
}
// 3. 从数据库加载数据
productInfo = loadFromDatabase();
// 4. 将数据写入Redis
redisTemplate.opsForValue().set(PRODUCT_KEY, productInfo);
} finally {
// 5. 释放锁
releaseLock(LOCK_PREFIX + PRODUCT_KEY);
}
return productInfo;
}
private boolean acquireLock(String key) {
return Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(key, "lock", LOCK_EXPIRE_TIME, TimeUnit.SECONDS));
}
private void releaseLock(String key) {
redisTemplate.delete(key);
}
private String loadFromDatabase() {
// 这里应该是实际的数据库查询逻辑,为了示例简化,直接返回模拟数据
return "Real product info from DB.";
}
}
注意事项:
1. 使用分布式锁时,务必确保锁的释放逻辑健壮,防止因异常导致锁未被正确释放。
2. 锁的超时时间应当合理设置,过长可能导致其他线程长时间等待,过短可能在操作未完成前锁已过期。
3. 上述示例使用了简单的字符串作为锁的标识,实际生产环境中可能需要更复杂的机制来确保锁的安全性,比如使用Redisson客户端提供的锁服务,它提供了更高级的功能,如自动续期、锁公平性等。