基于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();
}
解决方法
代码语言:javascript复制/**
*
* 释放锁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;
}
参考
- RedisTemplate 执行lua脚本出错