Redisson分布式锁实现 原子性加解锁、续租

198 阅读2分钟

前情提要:在使用公司组件时发现redisUtil中的解锁并没有判断锁是否已经失效了,所以给组件补充了Redisson原子性加解锁,续租的方法。

Redisson加解锁,续租

加锁,并返回拥有者信息

        /**
         * 获取redis锁
         *
         * @param key    锁名称
         * @param expire 锁失效时间
         * @return 锁的唯一标识符
         * @throws InterruptedException
         */
        public String getLockOwner(String key, Long expire) throws InterruptedException {
            Assert.hasLength(key, "key required");
​
            RLock lock = redissonClient.getLock(key);
            String lockOwner = UUID.randomUUID().toString();
            boolean isLocked = lock.tryLock(expire, TimeUnit.SECONDS);
​
            if (isLocked) {
​
                // 获取 RedissonClient 时使用 StringCodec,存储与获取的编码要一致
                RMap<String, String> lockMap = redissonClient.getMap(key, StringCodec.INSTANCE);
​
                if (isLocked) {
​
                    //  清理hash中的固定值
                    lockMap.clear();
​
                    // 存储 lockOwner 到 Redis 的哈希表中
                    lockMap.put("lockOwner", lockOwner);
​
                    return lockOwner;
                }
            }
            return null;
        }

Redisson分布式锁原子性删除锁

/**
         * 原则性校验 释放分布式锁
         *
         * @param key       Redis的键,表示要释放的锁的唯一标识符。不能为空。
         * @param lockOwner 锁的唯一标识符。不能为空。
         * @throws IllegalArgumentException 如果键或锁的唯一标识符为空或长度为0。
         */
        public void releaseLock(String key, String lockOwner) {
​
            Assert.hasLength(key, "key required");
            Assert.hasLength(lockOwner, "lockOwner required");
            
            // Lua 脚本:如果哈希表 KEYS[1] 中的 'lockOwner' 字段的值等于 ARGV[1],则删除该字段,否则返回 0
            String luaScript = "if redis.call('hget', KEYS[1], 'lockOwner') == ARGV[1] then " +
                    "return redis.call('hdel', KEYS[1], 'lockOwner') " +
                    "else return 0 end";
​
            RScript rScript = redissonClient.getScript(StringCodec.INSTANCE);
​
            Object result = rScript.eval(
                    RScript.Mode.READ_WRITE,
                    luaScript,
                    RScript.ReturnType.INTEGER,
                    Collections.singletonList(key),
                    lockOwner
            );
​
            if (result instanceof Long && (Long) result == 0L) {
                log.info("锁未找到或已失效,无需释放");
            }
        }

续租

    /**
     * 续租分布式锁的方法。
     *
     * @param key 分布式锁的键
     * @param lockOwner 锁的所有者标识
     * @param expire 锁的过期时间(秒)
     */
    public void renewLock(String key, String lockOwner, Long expire) {
        Assert.hasLength(key, "key required");
        Assert.hasLength(lockOwner, "lockOwner required");
​
        // Lua 脚本:如果哈希表 KEYS[1] 中的 'lockOwner' 字段的值等于 ARGV[1],则重新续租,否则返回 0
        String luaScript = "if redis.call('hget', KEYS[1], 'lockOwner') == ARGV[1] then " +
                "return redis.call('expire', KEYS[1], ARGV[2]) " +
                "else return 0 end";
​
        // 获取 Redisson 的脚本执行器
        RScript rScript = redissonClient.getScript(StringCodec.INSTANCE);
​
        // 执行 Lua 脚本
        Object result = rScript.eval(
                RScript.Mode.READ_WRITE,
                luaScript,
                RScript.ReturnType.INTEGER,
                Collections.singletonList(key),
                lockOwner,
                expire.toString()
        );
​
        // 记录续租操作的结果和锁的键
        log.info("renewLock结果: {}, 键: {}", result, key);
    }

用例

调用详情《Redis有序队列+事件 实现异步执行业务》

 /**
     * 处理事件数据
     * @param eventData
     * @throws InterruptedException
     */
    @Async()
    public void handleEventData(FlightJobAdjustEvent eventData) throws InterruptedException {
        String lockName = "wsbJobConflictLog_" + IdUtil.getSnowflakeNextIdStr();
        String lockOwner;
​
        // 最大续租次数
        final int maxRenewals = 3;
        
        final AtomicInteger renewalCount = new AtomicInteger(0);
​
        try {
​
            // 尝试获取分布式锁
            lockOwner = redisUtil.getLockOwner(lockName, 60L);
            if (ObjUtil.isNotEmpty(lockOwner)) {
                try {
​
                    // 启动续租虚拟线程
                    Future<?> future = Executors.newVirtualThreadPerTaskExecutor().submit(() -> {
                        try {
                            if (!Thread.currentThread().isInterrupted()) {
                                log.warn("任务被中断,停止续租:" + lockName);
                                return;
                            }
                            while (Thread.currentThread().isInterrupted() && renewalCount.incrementAndGet() <= maxRenewals) {
                                
                                // 每30秒续租一次锁
                                Thread.sleep(30000); 
                                redisUtil.renewLock(lockName, lockOwner, 60L);
                            }
​
                            log.warn("达到最大续租次数,停止续租:" + lockName);
                        } catch (Exception e) {
                            log.error("续租锁失败:" + e.getMessage(), e);
                        }
                    });
​
                    // 业务处理逻辑
                    log.debug("开始处理日志业务");
                    wsbJobConflictLogService.logBusiness(eventData);
​
​
                    if (ObjUtil.isNotEmpty(future)) {
                        log.debug("关闭续约");
                        future.cancel(true);
                    }
                } finally {
​
                    // 释放分布式锁
                    redisUtil.releaseLock(lockName, lockOwner);
                }
            } else {
                log.warn("正在加锁中:" + lockName);
            }
        } catch (Exception e) {
            log.error("处理日志业务出错:" + e.getMessage(), e);
​
            // 抛出异常,让外层捕获并删除元素
            throw e;
        }
    }