使用 redission 分布式锁的时候 报错 ERR bad lua script for redis cluster,first parameter of redis.call/redis.pcall must be a single literal string
org.redisson.client.RedisException: ERR bad lua script for redis cluster,first parameter of redis.call/redis.pcall must be a single literal string. channel: [id: 0x188df3c0,L:/192.168.136.122:63033 - R:r-8vb6oesmsvgeqoiu81pd.redis.zhangbei.rds.aliyuncs.com/39.101.250.188:6379] command: (EVAL),promise: java.util.concurrent.CompletableFuture@5db51595[Not completed],params: [local val = redis.call('get',KEYS[3]); if val ~= false then return tonumber(val);end; if (redis.call('hexists',KEYS[1],ARGV[3]) == 0) then return nil;end; local counter = redis.call('hincrby',ARGV[3],-1); if (counter > 0) then redis.call('pexpire',ARGV[2]); redis.call('set',KEYS[3],'px',ARGV[5]); return 0; else redis.call('del',KEYS[1]); redis.call(ARGV[4],KEYS[2],ARGV[1]); redis.call('set',1,ARGV[5]); return 1; end;,3,lock_test,redisson_lock__channel:{lock_test},redisson_unlock_latch:{lock_test}:256a5e75598644d2407e3d02cb3891c5,30000,61fc8b52-67b6-44bf-bc98-096749bcc817:1,PUBLISH,15300]
1.我们先了解
在编程中,"single literal string"(单个字面字符串)指的是直接在代码中表示的字符串,而不是通过变量或函数来动态生成的。字面字符串是预定义的,并且其内容在编译时是已知的。
例如,在许多编程语言中:
- 在Python中,字面字符串是用单引号(')或双引号(")括起来的文本,如
'hello'
或"world"
。 - 在JavaScript中,字面字符串是用反引号(`)括起来的文本,如
hello
或world
。 - 在C或C++中,字面字符串是用双引号(")括起来的字符数组,如
"hello"
。
字面字符串通常用于表示文本数据或代码中的常量值。在Lua脚本中,由于脚本是直接嵌入到命令行中的,所以所有的字符串都必须作为字面字符串提供。
我们知道Redission的分布式锁是使用 lua 脚本实现的
我的测试代码很简单
package com.lemon.test;
import com.lemon.member.Application;
import com.lemon.member.config.redis.RedisUtil;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class LockTest {
@Resource
private RedissonClient redissonClient;
@Test
public void lock() throws InterruptedException {
RLock rLock = redissonClient.getLock("lock_test");
boolean b = rLock.tryLock(100,TimeUnit.SECONDS);
System.out.println(b);
rLock.unlock();
}
}
我们可以跟踪代码看看它的脚本是怎么写的
org.redisson.RedissonLock#tryLock(long,java.util.concurrent.TimeUnit)
@Override
public boolean tryLock(long waitTime,TimeUnit unit) throws InterruptedException {
return tryLock(waitTime,-1,unit);
}
org.redisson.RedissonLock#tryLock(long,long,java.util.concurrent.TimeUnit)
@Override
public boolean tryLock(long waitTime,long leaseTime,TimeUnit unit) throws InterruptedException {
long time = unit.toMillis(waitTime);
long current = System.currentTimeMillis();
long threadId = Thread.currentThread().getId();
Long ttl = tryAcquire(leaseTime,unit,threadId);
// lock acquired
if (ttl == null) {
return true;
}
time -= System.currentTimeMillis() - current;
if (time <= 0) {
acquireFailed(threadId);
return false;
}
current = System.currentTimeMillis();
RFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);
if (!await(subscribeFuture,time,TimeUnit.MILLISECONDS)) {
if (!subscribeFuture.cancel(false)) {
subscribeFuture.onComplete((res,e) -> {
if (e == null) {
unsubscribe(subscribeFuture,threadId);
}
});
}
acquireFailed(threadId);
return false;
}
try {
time -= System.currentTimeMillis() - current;
if (time <= 0) {
acquireFailed(threadId);
return false;
}
while (true) {
long currentTime = System.currentTimeMillis();
ttl = tryAcquire(leaseTime,threadId);
// lock acquired
if (ttl == null) {
return true;
}
time -= System.currentTimeMillis() - currentTime;
if (time <= 0) {
acquireFailed(threadId);
return false;
}
// waiting for message
currentTime = System.currentTimeMillis();
if (ttl >= 0 && ttl < time) {
getEntry(threadId).getLatch().tryAcquire(ttl,TimeUnit.MILLISECONDS);
} else {
getEntry(threadId).getLatch().tryAcquire(time,TimeUnit.MILLISECONDS);
}
time -= System.currentTimeMillis() - currentTime;
if (time <= 0) {
acquireFailed(threadId);
return false;
}
}
} finally {
unsubscribe(subscribeFuture,threadId);
}
// return get(tryLockAsync(waitTime,leaseTime,unit));
}
org.redisson.RedissonLock#tryAcquire
private Long tryAcquire(long leaseTime,TimeUnit unit,long threadId) {
return get(tryAcquireAsync(leaseTime,threadId));
}
org.redisson.RedissonLock#tryAcquireAsync
private <T> RFuture<Long> tryAcquireAsync(long leaseTime,long threadId) {
if (leaseTime != -1) {
return tryLockInnerAsync(leaseTime,threadId,RedisCommands.EVAL_LONG);
}
RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(),TimeUnit.MILLISECONDS,RedisCommands.EVAL_LONG);
ttlRemainingFuture.onComplete((ttlRemaining,e) -> {
if (e != null) {
return;
}
// lock acquired
if (ttlRemaining == null) {
scheduleExpirationRenewal(threadId);
}
});
return ttlRemainingFuture;
}
org.redisson.RedissonLock#tryLockInnerAsync (redission的版本 3.10.7)
<T> RFuture<T> tryLockInnerAsync(long leaseTime,long threadId,RedisStrictCommand<T> command) {
internalLockLeaseTime = unit.toMillis(leaseTime);
return commandExecutor.evalWriteAsync(getName(),LongCodec.INSTANCE,command,"if (redis.call('exists',KEYS[1]) == 0) then " +
"redis.call('hset',ARGV[2],1); " +
"redis.call('pexpire',ARGV[1]); " +
"return nil; " +
"end; " +
"if (redis.call('hexists',ARGV[2]) == 1) then " +
"redis.call('hincrby',ARGV[1]); " +
"return nil; " +
"end; " +
"return redis.call('pttl',KEYS[1]);",Collections.<Object>singletonList(getName()),internalLockLeaseTime,getLockName(threadId));
}
org.redisson.RedissonLock#tryLockInnerAsync(redission的版本 3.24.3)
<T> RFuture<T> tryLockInnerAsync(long waitTime,RedisStrictCommand<T> command) {
return evalWriteAsync(getRawName(),"if ((redis.call('exists',KEYS[1]) == 0) " +
"or (redis.call('hexists',ARGV[2]) == 1)) then " +
"redis.call('hincrby',1); " +
"redis.call('pexpire',ARGV[1]); " +
"return nil; " +
"end; " +
"return redis.call('pttl',Collections.singletonList(getRawName()),unit.toMillis(leaseTime),getLockName(threadId));
}
可以看到这两个版本的加锁的 lua redis.call() 方法中第一个参数都是 single literal string (例如:'exists' 'hexists' 'hincrby' 'pexpire' 'pttl')
接下来看看释放锁的脚本
org.redisson.RedissonLock#unlock
@Override
public void unlock() {
try {
get(unlockAsync(Thread.currentThread().getId()));
} catch (RedisException e) {
if (e.getCause() instanceof IllegalMonitorStateException) {
throw (IllegalMonitorStateException) e.getCause();
} else {
throw e;
}
}
}
org.redisson.RedissonLock#unlockAsync(long)
@Override
public RFuture<Void> unlockAsync(long threadId) {
RPromise<Void> result = new RedissonPromise<Void>();
RFuture<Boolean> future = unlockInnerAsync(threadId);
future.onComplete((opStatus,e) -> {
if (e != null) {
cancelExpirationRenewal(threadId);
result.tryFailure(e);
return;
}
if (opStatus == null) {
IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock,not locked by current thread by node id: "
+ id + " thread-id: " + threadId);
result.tryFailure(cause);
return;
}
cancelExpirationRenewal(threadId);
result.trySuccess(null);
});
return result;
}
org.redisson.RedissonLock#unlockInnerAsync (redission版本 3.10.7)
protected RFuture<Boolean> unlockInnerAsync(long threadId) {
return commandExecutor.evalWriteAsync(getName(),RedisCommands.EVAL_BOOLEAN,"if (redis.call('hexists',ARGV[3]) == 0) then " +
"return nil;" +
"end; " +
"local counter = redis.call('hincrby',-1); " +
"if (counter > 0) then " +
"redis.call('pexpire',ARGV[2]); " +
"return 0; " +
"else " +
"redis.call('del',KEYS[1]); " +
"redis.call('publish',ARGV[1]); " +
"return 1; "+
"end; " +
"return nil;",Arrays.<Object>asList(getName(),getChannelName()),LockPubSub.UNLOCK_MESSAGE,getLockName(threadId));
}
这个版本中释放锁的脚本 所有 redis.call() 方法的第一个参数也都是 single literal string
org.redisson.RedissonLock#unlockInnerAsync (redission版本 3.24.3)
protected RFuture<Boolean> unlockInnerAsync(long threadId,String requestId,int timeout) {
return evalWriteAsync(getRawName(),"local val = redis.call('get',KEYS[3]); " +
"if val ~= false then " +
"return tonumber(val);" +
"end; " +
"if (redis.call('hexists',ARGV[3]) == 0) then " +
"return nil;" +
"end; " +
"local counter = redis.call('hincrby',-1); " +
"if (counter > 0) then " +
"redis.call('pexpire',ARGV[2]); " +
"redis.call('set',ARGV[5]); " +
"return 0; " +
"else " +
"redis.call('del',KEYS[1]); " +
"redis.call(ARGV[4],ARGV[1]); " +
"redis.call('set',ARGV[5]); " +
"return 1; " +
"end; ",Arrays.asList(getRawName(),getChannelName(),getUnlockLatchName(requestId)),getLockName(threadId),getSubscribeService().getPublishCommand(),timeout);
}
redis.call(ARGV[4],ARGV[1]);
可以看到 这个版本中的 lua 脚本的 redis.call() 方法的第一个参数是 ARGV[4] ,而不是single literal string.
所以这个问题就是使用的 Redission 的版本不对,由于你们公司的 redis 服务器可能不支持
redis.call(ARGV[4],ARGV[1]);
这种第一个参数是 变量 的lua脚本,所以释放锁的时候报错了
所以解决办法就是
1.升级redis服务器。让其支持 redis.call(ARGV[4],ARGV[1]); 这种脚本
2.使用低版本的Redission客户端,这样就不会有 redis.call(ARGV[4],ARGV[1]); 这种脚本
原文地址:https://blog.csdn.net/weixin_42286658/article/details/135240364
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。