栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

redisson使用过程中异常记录-看门狗机制失效

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

redisson使用过程中异常记录-看门狗机制失效

异常信息:
ttempt to unlock lock, not locked by current thread by node id:*** thread-id: **
场景:
有一个耗时可能很长的业务方法,做了异步处理@Async放入线程池执行。在controller来创建了锁,Rlock作为参数传到异步方法内,异步方法执行完finally内unlock.这个时候controller已经直接返回,异步方法可能执行了10秒钟后进入finally执行lock.unlock(),这时候报错上边的异常信息

贴代码:
controller层

package com.example.study.redisson;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;


@Slf4j
@RestController
@RequestMapping("/test")
public class Controller {

    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private RedissonService redissonService;

    
    @SneakyThrows
    @PutMapping("/redisson")
    public void TestRedisson(){
        RLock rlock = redissonClient.getLock("lock1");
        // 不设置锁时长,利用redis的看门狗默认30s,10s自动续期到30s,等待异步方法执行结束释放锁
        rlock.tryLock(5, TimeUnit.SECONDS);
        // 这里是异步方法
        redissonService.testRedisson(rlock);
        System.err.println("结束");
    }
}

service方法

package com.example.study.redisson;

import lombok.SneakyThrows;
import org.redisson.api.RLock;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;


@Service
public class RedissonService {

    @SneakyThrows
    @Async
    public void testRedisson(RLock lock) {
        try {
            // 任何业务代码
            System.err.println("testRedisson");
            Thread.sleep(100000);
        }finally {
        // sleep过后这一行代码unlock报错
            lock.unlock();
        }
    }
}

分析原因有可能是:
A线程直接返回了,Rlock是在A线程创建,B线程虽然拿着锁但是在线程内执行逻辑耗时很长执行到finally释放锁的时候,但是这个时候锁已经不存在了。
那么问题来了,Rlock没有设置过期时间,按理来说redis的看门狗会自动续期。为什么锁直接消失了

经过查看redis的看门狗实现发现:

分析原因有可能是:
A线程直接返回了,Rlock是在A线程创建,B线程虽然拿着锁但是在线程内执行逻辑耗时很长执行到finally释放锁的时候,但是这个时候锁已经不存在了。
那么问题来了,Rlock没有设置过期时间,按理来说redis的看门狗会自动续期。为什么锁直接消失了

经过查看redis的看门狗实现发现:

看门狗机制其实就是一个后台定时任务线程,获取锁成功之后,会将持有锁的线程放入到一个 RedissonLock.EXPIRATION_RENEWAL_MAP里面,然后每隔 10 秒 (internalLockLeaseTime / 3) 检查一下,如果客户端 还持有锁 key(判断客户端是否还持有 key,其实就是遍历 EXPIRATION_RENEWAL_MAP 里面线程 id 然后根据线程 id 去 Redis 中查,如果存在就会延长 key 的时间),那么就会不断的延长锁 key 的存活时间。
看到这里我们就知道为什么会看门狗失效了,因为我们A线程已经执行完结束,看门狗的看到我们的线程ID已经不存在了,自动动释放了锁,导致B线程unlock报错。
解决方案两种:
1、

	package com.example.study.redisson;

import lombok.SneakyThrows;
import org.redisson.api.RLock;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.Objects;


@Service
public class RedissonService {

    @SneakyThrows
    @Async
    public void testRedisson(RLock lock) {
        try {
            // 任何业务代码
            System.err.println("testRedisson");
            Thread.sleep(100000);
        } finally {
            //如果锁被当前线程持有,则释放。多一个判断,减少报错信息的出现
            //在解锁之前先判断要解锁的key是否已被锁定并且是否被当前线程保持。如果满足条件时才解锁。
            if (Objects.nonNull(lock) && lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }

        }
    }
}

2、
在service的异步方法内加锁而不是在controller传入Rlock。

package com.example.study.redisson;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;


@Slf4j
@RestController
@RequestMapping("/test")
public class Controller {

    @Autowired
    private RedissonService redissonService;

    @Autowired
    private RedissonClient redissonClient;
    
    @SneakyThrows
    @PutMapping("/redisson")
    public void TestRedisson(){
        if(redissonClient.getBucket("test:1").isExists()){
            throw new Exception("线程在执行过程中,请稍后再试");
        };
        // 这里是异步方法
        redissonService.testRedisson();
        System.err.println("结束");
    }
}

package com.example.study.redisson;

import lombok.SneakyThrows;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;


@Service
public class RedissonService {

    @Autowired
    private RedissonClient redissonClient;

    @SneakyThrows
    @Async
    public void testRedisson() {
        RLock lock = redissonClient.getLock("test:1");
        // 不设置锁时长(tryLock如果设置锁时长看门狗会失效),利用redis的看门狗默认30s,10s自动续期到30s,等待异步方法执行结束释放锁
        lock.tryLock(5, TimeUnit.SECONDS);
        try {
            // 任何业务代码
            System.err.println("testRedisson");
            Thread.sleep(100000);
        } finally {
            lock.unlock();
        }
    }
}

另外还遇到一种可能导致看门狗失效:

如果本地调试过程中方法断点了很长时间,会认为服务宕机了,看门狗机制线程也就没有了,也不会延长 key 的过期时间,到了 30s 之后就会自动过期了,其他线程就可以获取到锁

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/867028.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号