RedisTemplate执行lua脚本抛出异常IllegalStateException

2019-10-24 11:00:28 浏览数 (1)

基于Redis的分布式锁的释放过程,为了防止释放错误,需要使用lua脚本实现原子释放,但是RedisTemplate在执行lua脚本时会抛出异常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);
    Long result = stringRedisTemplate.execute(RedisScript.of(RELEASE_LOCK_LUA_SCRIPT), Collections.singletonList(key), clientId);
    return Objects.equals(result, RELEASE_LOCK_SUCCESS_RESULT);
}
  • 异常信息
代码语言:javascript复制
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)方法
代码语言:javascript复制
/**
 * 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类型的对应关系
代码语言:javascript复制
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脚本出错

0 人点赞