RedisTemplate执行lua脚本抛出异常IllegalStateException

基于Redis的分布式锁的释放过程,为了防止释放错误,需要使用lua脚本实现原子释放,但是RedisTemplate在执行lua脚本时会抛出异常IllegalStateException

问题描述

  • 分布式锁的释放
/**
 *
 * 释放锁lua脚本
*/
private static final String RELEASE_LOCK_LUA_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
/**
 * 释放锁成功返回值
 */
private static final Long RELEASE_LOCK_SUCCESS_RESULT = 1L;

/**
 * 释放锁
 *
 * @param key      锁ID
 * @param clientId 客户端ID
 * @return 是否成功
 */
private boolean releaseLock(String key, String clientId) {
    log.info("release lock:{key:{},clientId:{}}", key, clientId);
    Long result = stringRedisTemplate.execute(RedisScript.of(RELEASE_LOCK_LUA_SCRIPT), Collections.singletonList(key), clientId);
    return Objects.equals(result, RELEASE_LOCK_SUCCESS_RESULT);
}
  • 异常信息
java.lang.IllegalStateException: Failed to execute ApplicationRunner
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:778) [spring-boot-2.2.0.RELEASE.jar:2.2.0.RELEASE]
    at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:765) [spring-boot-2.2.0.RELEASE.jar:2.2.0.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:322) [spring-boot-2.2.0.RELEASE.jar:2.2.0.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226) [spring-boot-2.2.0.RELEASE.jar:2.2.0.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1215) [spring-boot-2.2.0.RELEASE.jar:2.2.0.RELEASE]
    at com.tenmao.redislock.RedisLockApplication.main(RedisLockApplication.java:40) [classes/:na]
Caused by: org.springframework.data.redis.RedisSystemException: Redis exception; nested exception is io.lettuce.core.RedisException: java.lang.IllegalStateException
    at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:74) ~[spring-data-redis-2.2.0.RELEASE.jar:2.2.0.RELEASE]
    at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:41) ~[spring-data-redis-2.2.0.RELEASE.jar:2.2.0.RELEASE]
    at org.springframework.data.redis.PassThroughExceptionTranslationStrategy.translate(PassThroughExceptionTranslationStrategy.java:44) ~[spring-data-redis-2.2.0.RELEASE.jar:2.2.0.RELEASE]
    at org.springframework.data.redis.FallbackExceptionTranslationStrategy.translate(FallbackExceptionTranslationStrategy.java:42) ~[spring-data-redis-2.2.0.RELEASE.jar:2.2.0.RELEASE]
    at org.springframework.data.redis.connection.lettuce.LettuceConnection.convertLettuceAccessException(LettuceConnection.java:269) ~[spring-data-redis-2.2.0.RELEASE.jar:2.2.0.RELEASE]
    at org.springframework.data.redis.connection.lettuce.LettuceScriptingCommands.convertLettuceAccessException(LettuceScriptingCommands.java:236) ~[spring-data-redis-2.2.0.RELEASE.jar:2.2.0.RELEASE]
    at org.springframework.data.redis.connection.lettuce.LettuceScriptingCommands.evalSha(LettuceScriptingCommands.java:195) ~[spring-data-redis-2.2.0.RELEASE.jar:2.2.0.RELEASE]
    at org.springframework.data.redis.connection.DefaultedRedisConnection.evalSha(DefaultedRedisConnection.java:1440) ~[spring-data-redis-2.2.0.RELEASE.jar:2.2.0.RELEASE]
    at org.springframework.data.redis.connection.DefaultStringRedisConnection.evalSha(DefaultStringRedisConnection.java:1750) ~[spring-data-redis-2.2.0.RELEASE.jar:2.2.0.RELEASE]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_144]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_144]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_144]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_144]
    at org.springframework.data.redis.core.CloseSuppressingInvocationHandler.invoke(CloseSuppressingInvocationHandler.java:61) ~[spring-data-redis-2.2.0.RELEASE.jar:2.2.0.RELEASE]
    at com.sun.proxy.$Proxy51.evalSha(Unknown Source) ~[na:na]
    at org.springframework.data.redis.core.script.DefaultScriptExecutor.eval(DefaultScriptExecutor.java:77) ~[spring-data-redis-2.2.0.RELEASE.jar:2.2.0.RELEASE]
    at org.springframework.data.redis.core.script.DefaultScriptExecutor.lambda$execute$0(DefaultScriptExecutor.java:68) ~[spring-data-redis-2.2.0.RELEASE.jar:2.2.0.RELEASE]
    at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:228) ~[spring-data-redis-2.2.0.RELEASE.jar:2.2.0.RELEASE]
    at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:188) ~[spring-data-redis-2.2.0.RELEASE.jar:2.2.0.RELEASE]
    at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:175) ~[spring-data-redis-2.2.0.RELEASE.jar:2.2.0.RELEASE]
    at org.springframework.data.redis.core.script.DefaultScriptExecutor.execute(DefaultScriptExecutor.java:58) ~[spring-data-redis-2.2.0.RELEASE.jar:2.2.0.RELEASE]
    at org.springframework.data.redis.core.script.DefaultScriptExecutor.execute(DefaultScriptExecutor.java:52) ~[spring-data-redis-2.2.0.RELEASE.jar:2.2.0.RELEASE]
    at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:350) ~[spring-data-redis-2.2.0.RELEASE.jar:2.2.0.RELEASE]
    at com.tenmao.redislock.RedisLockApplication.run(RedisLockApplication.java:47) [classes/:na]
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:775) [spring-boot-2.2.0.RELEASE.jar:2.2.0.RELEASE]
    ... 5 common frames omitted
Caused by: io.lettuce.core.RedisException: java.lang.IllegalStateException
    at io.lettuce.core.LettuceFutures.awaitOrCancel(LettuceFutures.java:129) ~[lettuce-core-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at io.lettuce.core.FutureSyncInvocationHandler.handleInvocation(FutureSyncInvocationHandler.java:69) ~[lettuce-core-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at io.lettuce.core.internal.AbstractInvocationHandler.invoke(AbstractInvocationHandler.java:80) ~[lettuce-core-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at com.sun.proxy.$Proxy48.evalsha(Unknown Source) ~[na:na]
    at org.springframework.data.redis.connection.lettuce.LettuceScriptingCommands.evalSha(LettuceScriptingCommands.java:193) ~[spring-data-redis-2.2.0.RELEASE.jar:2.2.0.RELEASE]
    ... 23 common frames omitted
Caused by: java.lang.IllegalStateException: null
    at io.lettuce.core.output.CommandOutput.set(CommandOutput.java:85) ~[lettuce-core-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at io.lettuce.core.protocol.RedisStateMachine.safeSet(RedisStateMachine.java:357) ~[lettuce-core-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at io.lettuce.core.protocol.RedisStateMachine.decode(RedisStateMachine.java:138) ~[lettuce-core-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at io.lettuce.core.protocol.CommandHandler.decode(CommandHandler.java:714) ~[lettuce-core-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at io.lettuce.core.protocol.CommandHandler.decode0(CommandHandler.java:678) ~[lettuce-core-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at io.lettuce.core.protocol.CommandHandler.decode(CommandHandler.java:673) ~[lettuce-core-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at io.lettuce.core.protocol.CommandHandler.decode(CommandHandler.java:594) ~[lettuce-core-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at io.lettuce.core.protocol.CommandHandler.channelRead(CommandHandler.java:563) ~[lettuce-core-5.2.0.RELEASE.jar:5.2.0.RELEASE]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374) ~[netty-transport-4.1.42.Final.jar:4.1.42.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360) ~[netty-transport-4.1.42.Final.jar:4.1.42.Final]
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:352) ~[netty-transport-4.1.42.Final.jar:4.1.42.Final]
    at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1422) ~[netty-transport-4.1.42.Final.jar:4.1.42.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374) ~[netty-transport-4.1.42.Final.jar:4.1.42.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360) ~[netty-transport-4.1.42.Final.jar:4.1.42.Final]
    at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:931) ~[netty-transport-4.1.42.Final.jar:4.1.42.Final]
    at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163) ~[netty-transport-4.1.42.Final.jar:4.1.42.Final]
    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:700) ~[netty-transport-4.1.42.Final.jar:4.1.42.Final]
    at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:635) ~[netty-transport-4.1.42.Final.jar:4.1.42.Final]
    at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:552) ~[netty-transport-4.1.42.Final.jar:4.1.42.Final]
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:514) ~[netty-transport-4.1.42.Final.jar:4.1.42.Final]
    at io.netty.util.concurrent.SingleThreadEventExecutor$6.run(SingleThreadEventExecutor.java:1044) ~[netty-common-4.1.42.Final.jar:4.1.42.Final]
    at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.42.Final.jar:4.1.42.Final]
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.42.Final.jar:4.1.42.Final]
    at java.lang.Thread.run(Thread.java:748) ~[na:1.8.0_144]

原因分析

因为没有指定ReturnType,所以默认使用ReturnType.STATUS,返回值就是io.lettuce.core.output.StatusOutput,这个类并没有重写CommandOutput中的方法。所以抛出异常IllegalStateException

  • CommandOutput中的set(long integer)方法
/**
 * Set the command output to a 64-bit signed integer. Concrete {@link CommandOutput} implementations must override this
 * method unless they only receive a byte array value.
 *
 * @param integer The command output.
 */
public void set(long integer) {
    throw new IllegalStateException();
}

解决方法

/**
 *
 * 释放锁lua脚本
*/
private static final String RELEASE_LOCK_LUA_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
/**
 * 释放锁成功返回值
 */
private static final Long RELEASE_LOCK_SUCCESS_RESULT = 1L;

/**
 * 释放锁
 *
 * @param key      锁ID
 * @param clientId 客户端ID
 * @return 是否成功
 */
private boolean releaseLock(String key, String clientId) {
    log.info("release lock:{key:{},clientId:{}}", key, clientId);
    //指定ReturnType为Long.class,注意这里不能使用Integer.class,因为ReturnType不支持。只支持List.class, Boolean.class和Long.class
    DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(RELEASE_LOCK_LUA_SCRIPT, Long.class);
    Long result = stringRedisTemplate.execute(redisScript, Collections.singletonList(key), clientId);
    return Objects.equals(result, RELEASE_LOCK_SUCCESS_RESULT);
}
  • ReturnType与Java类型的对应关系
public static ReturnType fromJavaType(@Nullable Class<?> javaType) {

    if (javaType == null) {
        return ReturnType.STATUS;
    }
    if (javaType.isAssignableFrom(List.class)) {
        return ReturnType.MULTI;
    }
    if (javaType.isAssignableFrom(Boolean.class)) {
        return ReturnType.BOOLEAN;
    }
    if (javaType.isAssignableFrom(Long.class)) {
        return ReturnType.INTEGER;
    }
    return ReturnType.VALUE;
}

参考

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏程序员叨叨叨

【Cocos2d-x】RichText打字机效果思路分享

今天在开发游戏引导框架时,遇到这样的需求:人物对话文本支持打字机效果,且需要个别文字高亮。如果仅仅是前者的需求,是挺好实现的,创建一个Label,通过getLe...

7410
来自专栏java_python

java架构之路-(Redis专题)简单聊聊redis分布式锁

  这次我们来简单说说分布式锁,我记得过去我也过一篇JMM的内存一致性算法,就是说拿到锁的可以继续操作,没拿到的自旋等待。

7030
来自专栏Node开发

Mysql读写分离

在高并发的时候,如果所有的数据库操作都只通过一台数据库来操作,那数据库很大程度可能出现宕机,而宕机就有可能导致数据丢失,造成不良后果。所以在并发量高的情况下一般...

10510
来自专栏Java架构学习路线

4个点让你彻底明白Redis的各项功能

综上所述,Redis提供了丰富的功能,初次见到可能会感觉眼花缭乱,这些功能都是干嘛用的?都解决了什么问题?什么情况下才会用到相应的功能?那么下面从零开始,一步一...

9240
来自专栏算法之名

分布式秒杀 顶

一般在具体的业务中,平台方会发布秒杀席位个数,秒杀的时间段,让各个商家报名,将自己的产品参与秒杀活动。这里将同事画的一张图放上来,大致是这么一个流程。关于秒杀原...

7520
来自专栏ChaMd5安全团队

“送给最好的TA.apk”简单逆向分析

20190927收到一个apk,名字叫“送给最好的TA.apk”。文件哈希值如下:

30040
来自专栏知识分享

1-OpenResty 介绍 (摘抄)

由于我的做小程序的时候改了下后台的安装软件,不是使用的单纯的Nginx,而是使用的OpenResty,然后呢在网上看到了有介绍OpenResty的,故摘抄下来,...

9020
来自专栏陶士涵的菜地

[视频教程] 包管理器方式安装使用openresty新手上路

OpenResty是一个通过Lua扩展Nginx实现的可伸缩的Web平台,内部集成了大量精良的Lua库、第三方模块以及大多数的依赖项。 用于方便地搭建能够处理超...

14330
来自专栏彤哥读源码

死磕 java同步系列之redis分布式锁进化史

Redis(全称:Remote Dictionary Server 远程字典服务)是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、...

10600

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励