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

相关推荐
今天多喝热水3 小时前
Redis适用场景
数据库·redis
小袁拒绝摆烂4 小时前
Redis-高级篇(分布式缓存/持久化)
redis·分布式·缓存
E___V___E4 小时前
黑马点评redis改 part 2
数据库·redis·缓存
JhonKI5 小时前
【从零实现高并发内存池】Central Cache从理解设计到全面实现
数据库·redis·缓存
洛神灬殇6 小时前
【Redis技术进阶之路】「原理分析系列开篇」探索事件驱动枚型与数据特久化原理实现(时间事件驱动执行控制)
redis·后端
没逻辑6 小时前
👀 Redis 实时调优与监控实践:基于 MONITOR、INFO、SLOWLOG
redis
没逻辑6 小时前
⏰ Redis 在支付系统中作为延迟任务队列的实践
redis·后端
遥夜人间7 小时前
Redis之缓存击穿
redis·缓存
小袁拒绝摆烂10 小时前
分布式锁+秒杀异步优化
redis·分布式
西门吹雪分身10 小时前
Redis之RedLock算法以及底层原理
数据库·redis·算法