Redis分布式锁的两种实现方式(Redis+定时任务/Redisson)

完整代码实现

github.com/blueboySalv...

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]); - 获取KEYS1键对应的值并存储到局部变量value中

if value == ARGV[1] then - 判断获取的值是否等于ARGV1(传入的参数)

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
);
  1. script - 定义的Lua脚本字符串

  2. Long.class - 脚本返回值的类型为Long

  3. Collections.singletonList(prefixed_key) - 对应脚本中的KEYS数组

    1. prefixed_key变量的值被传递为KEYS[1]
    2. 这里只传递了一个键,所以只有KEYS[1]可用
  4. expectedValue - 对应脚本中的ARGV数组

    1. 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);将不会自动续约

相关推荐
IT大白鼠2 天前
Redis 系列 · 第 01 篇——认知入门:Redis 是什么
redis·nosql
彧azz2 天前
Linux 环境下 Redis 学习总结:数据类型、持久化、锁、事务、主从与缓存问题
linux·redis·笔记·学习·面试
lbb 小魔仙2 天前
OpenClaw + cpolar 实战:远程 NAS、分享小游戏、RDP,再配置公网 AI 入口
数据库·人工智能·redis·oracle·prometheus
夕除2 天前
redis--018
redis
两锭子2 天前
Redis基础
redis
烟沙九洲2 天前
Redis 缓存穿透、缓存击穿、缓存雪崩
数据库·redis
字节探索2 天前
Redis 只会当缓存用?这 10 大实战场景,让你的系统快到飞起
redis·后端
于樱花森上飞舞2 天前
【Redis】哨兵详解
java·开发语言·数据库·redis
Rain的Java大神之路2 天前
如何快速上传10G文件
java·spring boot·redis·后端·mysql·spring cloud·面试
Wang's Blog2 天前
Java 接入Redis: 通用命令与键管理
java·服务器·redis