完整代码实现
Redis+定时任务原生实现
Redis Lua 工具
lua
local value = redis.call('get', KEYS[1]);
if value == ARGV[1] then
return redis.call('del', KEYS[1]);
else
return 0;
end
local value = redis.call('get', KEYS[1]);
- 获取KEYS[1]键对应的值并存储到局部变量value中
if value == ARGV[1] then
- 判断获取的值是否等于ARGV[1](传入的参数)
return redis.call('del', KEYS[1]);
- 如果值匹配,则删除该键并返回删除操作的结果(通常是删除的键数量)
else return 0;
- 如果值不匹配,则返回0表示未执行删除操作
end
- 结束if语句
java
Long result = redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(prefixed_key),
expectedValue
);
-
script
- 定义的Lua脚本字符串 -
Long.class
- 脚本返回值的类型为Long -
Collections.singletonList(prefixed_key)
- 对应脚本中的KEYS
数组prefixed_key
变量的值被传递为KEYS[1]
- 这里只传递了一个键,所以只有
KEYS[1]
可用
-
expectedValue
- 对应脚本中的ARGV
数组expectedValue
变量的值被传递为ARGV[1]
在Lua脚本中:
-
KEYS[1]
就是prefixed_key
的值 -
ARGV[1]
就是expectedValue
的值
代码展示:
java
@Component
public class RedisLuaUtil {
...
/**
* 比较并删除 - Compare And Delete
* 只有当key存在且value匹配时才删;
* @param key Redis键
* @param expectedValue 期望的值
* @return 1-删除成功;0-值不匹配或key不存在
*/
public Long cad(String key, String expectedValue) {
// 确保使用String序列化器
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
String prefixed_key = DEMO_FIX + key;
String script =
"local value = redis.call('get', KEYS[1]); " +
"if value == ARGV[1] then " +
" return redis.call('del', KEYS[1]); " +
"else " +
" return 0; " +
"end";
Long result = redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(prefixed_key),
expectedValue);
if (result == 0) {
throw new RuntimeException("释放锁失败:key=" + prefixed_key + ",expectedValue=" + expectedValue + ",实际值=" + redisTemplate.opsForValue().get(prefixed_key));
}
return result;
}
}
流程图演示

Redisson 实现
关键代码展示
java
public class ArticleController2 {
@GetMapping("/queryById2/{articleId}")
public ApiResponse queryById(@PathVariable Long articleId) {
String cacheKey = ARTICLE_PREFIX + articleId;
// 检查缓存
RBucket<String> bucket = redissonClient.getBucket(cacheKey);
String article = bucket.get();
if (article != null) return ApiResponse.success(article); //命中缓存,直接返回
String lockKey = LOCK_PREFIX + articleId; // 定义分布式锁的key
RLock lock = redissonClient.getLock(lockKey); // 获取Redisson的锁实例
try {
// 尝试获取锁,在 lockWaitTime 时间内自旋重试,如果超过这个时间还设置失败就返回false
// Redisson的锁会自动续期,默认锁TTL = 30,自动续期的时候也重置TTL = 30,所以不需要手动实现看门狗
boolean isLocked = lock.tryLock(lockWaitTime, TimeUnit.SECONDS);
if (isLocked) {
// 双重检查,防止在获取锁的过程中其他线程已经设置了缓存
article = bucket.get();
if (article != null) return ApiResponse.success(article);
try {
String queryResult = selectById(articleId); // 查询数据库
bucket.set(queryResult); // 写入缓存
return ApiResponse.success(queryResult);
} finally {
if (lock.isHeldByCurrentThread()) lock.unlock(); // 释放当前线程持有的锁
}
} else {
return ApiResponse.success(selectById(articleId)); // 获取锁失败时,直接查询数据库并返回结果
}
} catch (InterruptedException e) {
...
} catch (Exception e) {
...
}
}
}
注意:当显式指定 lockLeaseTime 时,Redisson 会禁用看门狗自动续期,锁将在 leaseTime 后自动释放。
只有不传 leaseTime 或设置为 -1 时才会启用自动续期。
boolean isLocked = lock.tryLock(lockWaitTime, lockLeaseTime, TimeUnit.SECONDS);将不会自动续约