java
//查询key是否存在缓存中?
String value = redis.getKey("key");
//如果缓存没有,查询数据库
if (StringUtils.isEmpty(value)){
value = remoteQueryDB(key);
//放入缓存中
redis.addKey(key,value, RandomUtils.nextInt(1000,5000));
}
return value;
以上是在开发中我们经常用到的缓存的写法,然而在高并发场景下会暴露缓存击穿、缓存过期时间随机但无兜底、缺乏异常处理、无并发控制 等关键问题。
下面给出解决方案:
一、核心前提:Redisson 依赖准备
首先需要引入 Redisson 依赖(以 Maven 为例),并配置 Redisson 客户端:
<!-- Redisson 核心依赖 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.23.3</version> <!-- 建议用最新稳定版 -->
</dependency>
二、重构后的完整代码(Redisson 实现分布式锁)
import org.apache.commons.lang3.StringUtils;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import java.util.Random;
import java.util.concurrent.TimeUnit;
// 基于 Redisson 的 Redis 工具类(简化版)
class RedisUtil {
private final RedissonClient redissonClient;
// 缓存操作的通用前缀(可选)
private final String CACHE_PREFIX = "cache:";
// 分布式锁前缀
private final String LOCK_PREFIX = "lock:";
// 初始化 Redisson 客户端(生产建议配置到Spring容器中)
public RedisUtil() {
Config config = new Config();
// 单机 Redis 配置(集群/哨兵可参考Redisson官方文档)
config.useSingleServer()
.setAddress("redis://127.0.0.1:6379")
.setPassword(null) // 无密码则设为null
.setDatabase(0);
this.redissonClient = Redisson.create(config);
}
// 查询缓存
public String getKey(String key) {
try {
return redissonClient.getBucket(CACHE_PREFIX + key).get();
} catch (Exception e) {
// Redis 异常时返回null,降级查库
System.err.println("Redis查询异常,key=" + key + ", error=" + e.getMessage());
return null;
}
}
// 写入缓存(带过期时间,单位:秒)
public boolean addKey(String key, String value, int expireSeconds) {
try {
redissonClient.getBucket(CACHE_PREFIX + key)
.set(value, expireSeconds, TimeUnit.SECONDS);
return true;
} catch (Exception e) {
System.err.println("Redis写入异常,key=" + key + ", error=" + e.getMessage());
return false;
}
}
// 获取 Redisson 分布式锁(核心:可重入、自动续期)
public RLock getLock(String key) {
return redissonClient.getLock(LOCK_PREFIX + key);
}
}
// 业务逻辑类(基于Redisson锁重构)
public class CacheService {
private final RedisUtil redis = new RedisUtil();
// 缓存过期策略:基础30分钟 + 随机0-10分钟偏移
private final int BASE_EXPIRE_SECONDS = 30 * 60;
private final int RANDOM_OFFSET_SECONDS = 10 * 60;
private final Random random = new Random();
// 锁等待时间(5秒):超过则放弃抢锁,降级查库
private final long LOCK_WAIT_TIME = 5;
// 锁自动释放时间(10秒):Redisson会自动续期,避免死锁
private final long LOCK_LEASE_TIME = 10;
public String queryData(String key) {
// 步骤1:查询缓存,命中则直接返回
String value = redis.getKey(key);
if (StringUtils.isNotEmpty(value)) {
return value;
}
// 步骤2:缓存未命中,获取Redisson分布式锁
RLock lock = redis.getLock(key);
boolean locked = false;
try {
// 核心:尝试获取锁(非阻塞,最多等5秒,拿到锁后10秒自动释放)
// Redisson 会自动续期(watch dog机制),只要线程没挂,锁不会过期
locked = lock.tryLock(LOCK_WAIT_TIME, LOCK_LEASE_TIME, TimeUnit.SECONDS);
if (locked) {
// 双重检查:防止抢锁期间其他线程已写入缓存
value = redis.getKey(key);
if (StringUtils.isNotEmpty(value)) {
return value;
}
// 步骤3:锁内查库(仅一个线程执行)
value = remoteQueryDB(key);
// 步骤4:缓存穿透防护:数据库无值则写入空标记+短过期
if (StringUtils.isEmpty(value)) {
redis.addKey(key, "", 5 * 60); // 空值缓存5分钟
return "";
}
// 步骤5:写入缓存(基础过期+随机偏移)
int expireSeconds = BASE_EXPIRE_SECONDS + random.nextInt(RANDOM_OFFSET_SECONDS);
redis.addKey(key, value, expireSeconds);
return value;
} else {
// 步骤6:未抢到锁,自旋重试(最多3次)
int retryCount = 0;
while (retryCount < 3) {
TimeUnit.MILLISECONDS.sleep(100);
value = redis.getKey(key);
if (StringUtils.isNotEmpty(value)) {
return value;
}
retryCount++;
}
// 重试失败,降级查库(兜底)
return remoteQueryDB(key);
}
} catch (Exception e) {
// 步骤7:异常处理,降级查库
System.err.println("缓存逻辑异常,key=" + key + ", error=" + e.getMessage());
return remoteQueryDB(key);
} finally {
// 步骤8:释放锁(必须在finally中,Redisson锁释放是幂等的)
if (locked && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
// 模拟远程查库(生产需加超时、重试)
private String remoteQueryDB(String key) {
try {
// 模拟查库耗时
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "db_value_" + key;
}
// 测试方法
public static void main(String[] args) {
CacheService service = new CacheService();
System.out.println(service.queryData("user_1001"));
}
}