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

相关推荐
YDS8298 小时前
大营销平台 —— 活动SKU库存扣减业务及其一致性处理
java·spring boot·redis·rabbitmq·ddd
爬也要爬着前进1 天前
redis主从搭建
数据库·redis·缓存
用户095367515831 天前
在系统的学习 redis 前的疑惑
redis·go
吴声子夜歌1 天前
Redis 3.x——集群故障转移
java·数据库·redis·集群
明豆1 天前
Redis 分布式缓存生产实战
redis·分布式·缓存
海兰1 天前
【高速缓存】RedisVL 指南:从向量搜索到 AI 应用落地
人工智能·redis
weixin_727535621 天前
双Token认证体系深度拆解:Spring Security + JWT + Redis
redis·spring·wpf
William Dawson1 天前
【华为云 MRS Redis 安全集群 Kerberos 认证接入实战(Spring Boot \+ Jedis)】
redis·安全·华为云
zhouhui0011 天前
AI帮我写了个Spring Boot校验,线上漏掉了这组边界条件
java·spring boot·redis·ai编程
吃饱了得干活1 天前
扫码登录如何实现?从原理到实战
java·spring boot·redis