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

相关推荐
sbjdhjd11 小时前
Redis 主从复制、哨兵高可用与 Cluster 集群部署实验手册
运维·前端·redis·云原生·开源·bootstrap·html
Trouvaille ~12 小时前
【Redis篇】Redis 哨兵(Sentinel):高可用自动故障转移
数据库·redis·缓存·中间件·sentinel·高可用·哨兵
giaz14n9X13 小时前
Redis 分布式锁进阶第五十七篇
数据库·redis·分布式
WyCAGy8ij14 小时前
Redis 分布式锁进阶第二篇讲解
数据库·redis·分布式
学Linux的语莫16 小时前
redis的数据类型和使用
数据库·redis·缓存
超梦dasgg18 小时前
Redis ZSet(有序集合)底层数据结构
数据结构·数据库·redis
齐潇宇1 天前
Redis数据库基础
linux·数据库·redis·缓存
轻刀快马1 天前
黑马点评复盘
redis
WyCAGy8ij1 天前
Redis 分布式锁进阶第四篇讲解
数据库·redis·分布式
kishu_iOS&AI1 天前
Mac —— Docker Desktop(Milvus和Redis)部署
redis·docker·milvus