Redisson分布式锁——踩坑记录

1. 解铃还须系铃人

1.1 锁执行超时后却还释放锁

Caused by: java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: 32caba49-5799-491b-aa7b-47d789dbca93 thread-id: 1

先来看一段代码:

@Test
public void testSimpleLock2() {
    String key = "srm-sync-test-lock";
    try {
        // 获取锁,持有8秒后,自动释放
        tryLock(key, 1000L, 8000L);
        TimeUnit.SECONDS.sleep(10);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        unlock(key);
    }
}

public boolean tryLock(String lockKey, long waitTime, long leaseTime) {
    boolean suc;
    try {
        RLock lock = redissonClient.getLock(lockKey);
        // 第一个参数是等待时间,n毫秒内获取不到锁,则直接返回
        // 第二个参数 m毫秒后强制释放锁
        suc = lock.tryLock(waitTime, leaseTime, TimeUnit.MILLISECONDS);
    } catch (Throwable e) {
        String msg = String.format("LOCK FAILED: key=%s||tryLockTime=%s||lockExpiredTime=%s", lockKey, waitTime, leaseTime);
        throw new IllegalStateException(msg, e);
    }
    return suc;
}

public void unlock(String lockKey) {
    try {
        RLock lock = redissonClient.getLock(lockKey);
        if (lock != null) {
            lock.unlock();
        }
    } catch (Throwable e) {
        String msg = String.format("UNLOCK FAILED: key=%s", lockKey);
        throw new IllegalStateException(msg, e);
    }
}

这块执行完之后,报错

Caused by: java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: 32caba49-5799-491b-aa7b-47d789dbca93 thread-id: 1
	at org.redisson.RedissonLock.lambda$unlockAsync$3(RedissonLock.java:581)
	at org.redisson.misc.RedissonPromise.lambda$onComplete$0(RedissonPromise.java:187)
	at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:577)
	at io.netty.util.concurrent.DefaultPromise.notifyListeners0(DefaultPromise.java:570)
	at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:549)
	at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:490)
	at io.netty.util.concurrent.DefaultPromise.setValue0(DefaultPromise.java:615)
	at io.netty.util.concurrent.DefaultPromise.setSuccess0(DefaultPromise.java:604)
	at io.netty.util.concurrent.DefaultPromise.trySuccess(DefaultPromise.java:104)
	at org.redisson.misc.RedissonPromise.trySuccess(RedissonPromise.java:82)
	at org.redisson.command.RedisExecutor.handleReference(RedisExecutor.java:481)
	at org.redisson.command.RedisExecutor.handleSuccess(RedisExecutor.java:474)
	at org.redisson.command.RedisExecutor.handleResult(RedisExecutor.java:459)
	at org.redisson.command.RedisExecutor.checkAttemptPromise(RedisExecutor.java:445)
	at org.redisson.command.RedisExecutor.lambda$execute$3(RedisExecutor.java:163)
	at org.redisson.misc.RedissonPromise.lambda$onComplete$0(RedissonPromise.java:187)
	at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:577)
	at io.netty.util.concurrent.DefaultPromise.notifyListeners0(DefaultPromise.java:570)
	at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:549)
	at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:490)
	at io.netty.util.concurrent.DefaultPromise.setValue0(DefaultPromise.java:615)
	at io.netty.util.concurrent.DefaultPromise.setSuccess0(DefaultPromise.java:604)
	at io.netty.util.concurrent.DefaultPromise.trySuccess(DefaultPromise.java:104)
	at org.redisson.misc.RedissonPromise.trySuccess(RedissonPromise.java:82)
	at org.redisson.client.handler.CommandDecoder.completeResponse(CommandDecoder.java:444)
	at org.redisson.client.handler.CommandDecoder.handleResult(CommandDecoder.java:439)
	at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:370)
	at org.redisson.client.handler.CommandDecoder.decodeCommand(CommandDecoder.java:196)
	at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:134)
	at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:104)
	at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:498)
	at io.netty.handler.codec.ReplayingDecoder.callDecode(ReplayingDecoder.java:366)
	at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:377)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:355)
	at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:377)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363)
	at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
	at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163)
	at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:714)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:650)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:576)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
	at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
	at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.lang.Thread.run(Thread.java:748)

原因分析:
获取锁成功之后,持有的时间是8秒,而如果你的锁内业务执行时间超过8秒后,锁会自动释放,锁自动释放后,而在你的finally里面又手动去释放锁,就导致了这个错误;

解决方法:

public void unlock(String lockKey) {
    try {
        RLock lock = redissonClient.getLock(lockKey);
        if (lock != null && lock.isHeldByCurrentThread()) { 
            lock.unlock();
        }
    } catch (Throwable e) {
        String msg = String.format("UNLOCK FAILED: key=%s", lockKey);
        throw new IllegalStateException(msg, e);
    }
}

解锁时,加了lock.isHeldByCurrentThread(),它的意思是查询当前线程是否持有此锁定,如果还持有,则释放,如果未持有,则说明已被释放;

1.2 线程并发时,线程B释放线程A的锁

我需要先来描述一下这种场景:
tryLock(key, 1000L, 8000L); 如果线程A获取到锁之后,此时线程B也来获取锁,从此行代码可看出,最多等待1秒,如果1秒后未获取到锁,会执行锁内的代码吗?

@Test
public void testConcurrentLock() {
    String key = "srm-sync-test-lock";
    LockThread lockThreadA = new LockThread(key, 5);
    LockThread lockThreadB = new LockThread(key, 1);

    try {
        new Thread(lockThreadA, "Thread-A").start();
        TimeUnit.SECONDS.sleep(1);

        new Thread(lockThreadB, "Thread-B").start();
        TimeUnit.SECONDS.sleep(20);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

public boolean tryLock(String lockKey, long waitTime, long leaseTime) {
    boolean suc;
    try {
        RLock lock = redissonClient.getLock(lockKey);
        suc = lock.tryLock(waitTime, leaseTime, TimeUnit.MILLISECONDS);
    } catch (Throwable e) {
        String msg = String.format("LOCK FAILED: key=%s||tryLockTime=%s||lockExpiredTime=%s", lockKey, waitTime, leaseTime);
        throw new IllegalStateException(msg, e);
    }
    return suc;
}

public void unlock(String lockKey) {
    try {
        RLock lock = redissonClient.getLock(lockKey);
        if (lock != null) {
            lock.unlock();
        }
    } catch (Throwable e) {
        String msg = String.format("UNLOCK FAILED: key=%s", lockKey);
        throw new IllegalStateException(msg, e);
    }
}

class LockThread implements Runnable {
    private String lockKey;
    private int sleepTime;

    public LockThread(String lockKey, int sleepTime) {
        this.lockKey = lockKey;
        this.sleepTime = sleepTime;
    }

    @Override
    public void run() {
        try {
            if (tryLock(lockKey, 1000L, 8000L)) {
                TimeUnit.SECONDS.sleep(sleepTime);
                System.out.println("===============" + Thread.currentThread().getName());
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            unlock(lockKey);
        }
    }
}
输出结果:
Caused by: java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: 9b42c695-220d-4194-a020-131309ebcd87 thread-id: 56
	at org.redisson.RedissonLock.lambda$unlockAsync$3(RedissonLock.java:581)
	at org.redisson.misc.RedissonPromise.lambda$onComplete$0(RedissonPromise.java:187)
	at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:577)
	at io.netty.util.concurrent.DefaultPromise.notifyListeners0(DefaultPromise.java:570)
	at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:549)
	at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:490)
	at io.netty.util.concurrent.DefaultPromise.setValue0(DefaultPromise.java:615)
	at io.netty.util.concurrent.DefaultPromise.setSuccess0(DefaultPromise.java:604)
	at io.netty.util.concurrent.DefaultPromise.trySuccess(DefaultPromise.java:104)
	at org.redisson.misc.RedissonPromise.trySuccess(RedissonPromise.java:82)
	at org.redisson.command.RedisExecutor.handleReference(RedisExecutor.java:481)
	at org.redisson.command.RedisExecutor.handleSuccess(RedisExecutor.java:474)
	at org.redisson.command.RedisExecutor.handleResult(RedisExecutor.java:459)
	at org.redisson.command.RedisExecutor.checkAttemptPromise(RedisExecutor.java:445)
	at org.redisson.command.RedisExecutor.lambda$execute$3(RedisExecutor.java:163)
	at org.redisson.misc.RedissonPromise.lambda$onComplete$0(RedissonPromise.java:187)
	at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:577)
	at io.netty.util.concurrent.DefaultPromise.notifyListeners0(DefaultPromise.java:570)
	at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:549)
	at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:490)
	at io.netty.util.concurrent.DefaultPromise.setValue0(DefaultPromise.java:615)
	at io.netty.util.concurrent.DefaultPromise.setSuccess0(DefaultPromise.java:604)
	at io.netty.util.concurrent.DefaultPromise.trySuccess(DefaultPromise.java:104)
	at org.redisson.misc.RedissonPromise.trySuccess(RedissonPromise.java:82)
	at org.redisson.client.handler.CommandDecoder.completeResponse(CommandDecoder.java:444)
	at org.redisson.client.handler.CommandDecoder.handleResult(CommandDecoder.java:439)
	at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:370)
	at org.redisson.client.handler.CommandDecoder.decodeCommand(CommandDecoder.java:196)
	at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:134)
	at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:104)
	at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:498)
	at io.netty.handler.codec.ReplayingDecoder.callDecode(ReplayingDecoder.java:366)
	at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:377)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:355)
	at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:377)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363)
	at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
	at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163)
	at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:714)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:650)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:576)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
	at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
	at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	... 1 more
===============Thread-A

原因分析:
在这里插入图片描述
解决办法: 和上面的案例一的解决办法相同,在释放锁时,加上lock.isHeldByCurrentThread()的判断,虽然线程B没有获取到锁,也没有执行锁内的业务,但是,它一定执行finally里面的unlock方法;

2. 使用公司自研的redis加锁遇到的问题

Caused by: org.redisson.client.RedisException: ERR running script (call to f_3ffe249c16dee540ac8ab32e39bd408626b9aff7): @user_script:1: ERR fusion r2 unknown cmd [publish. channel: [id: 0x5e3c4063, L:/172.30.39.133:55920 - R:10.179.133.91/10.179.133.91:3303] command: (EVAL), params: [if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then return nil;end; local counter = redis.call('h..., 2, lock_key, redisson_lock__channel:{lock_key}, 0, 30000, 6494e66f-11c1-45f5-821f-52b33275b11a:55]
	at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:355)
	at org.redisson.client.handler.CommandDecoder.decodeCommand(CommandDecoder.java:196)
	at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:134)
	at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:104)
	at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:498)
	at io.netty.handler.codec.ReplayingDecoder.callDecode(ReplayingDecoder.java:366)
	at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:377)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:355)
	at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:377)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363)
	at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
	at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163)
	at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:714)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:650)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:576)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
	at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
	at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	... 1 more

原因:
公司自研的缓存集群不支持publish命令;

  • 8
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值