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

相关推荐
java1234_小锋2 小时前
REDIS集群会有写操作丢失吗?为什么
数据库·redis·缓存
向阳而生,一路生花5 小时前
redis离线安装
java·数据库·redis
hzk的学习笔记6 小时前
Redisson 的 Watchdog 机制
数据库·redis·分布式·缓存
hzk的学习笔记8 小时前
Redisson解锁失败,watchdog会不会一直续期下去?
数据库·redis·缓存
bing.shao9 小时前
如何降低redis哈希值冲突概率
数据库·redis·哈希算法
ckm紫韵9 小时前
redis查询速度快的原因?
数据库·redis·缓存
熊文豪20 小时前
openEuler 云原生实战:部署高性能 Redis 集群与压测分析
数据库·redis·云原生·openeuler
xrkhy1 天前
canal1.1.8+mysql8.0+jdk17+redis的使用
android·redis·adb
MuYiLuck1 天前
redis持久化与集群
java·数据库·redis
埃泽漫笔1 天前
Redis性能优化避坑指南
redis