redisTemplate+lua脚本实现分布式锁报错:@user_script:1: ERR value is not an integer or out of range

.

执行的Lua脚本


if redis.call('setNx', KEYS[1], ARGV[1]) == 1 then
 redis.call('expire', KEYS[1],ARGV[2]) 
 return 1 
 else
 return 0 
 end

以上脚本意思为 如果创建key成功则给当前key设置存活时间,存活时间设置成功则返回1,创建key或者设置存活时间失败则返回0。

工具类代码

import java.security.MessageDigest;
import java.util.Collections;

import cn.hutool.core.util.StrUtil;
import com.jiahe.ylq.common.exception.BizException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;

 public class RedisLockUtil {

	private static final Long SUCCESS = 1L;

	/*
	 * 加锁脚本
	 */
	private static final String SCRIPT_LOCK = "if redis.call('setNx', KEYS[1], ARGV[1]) == 1 then redis.call('expire', KEYS[1],ARGV[2]) return 1 else return 0 end";
	/*
	 * 解锁脚本
	 */
	private static final String SCRIPT_UNLOCK = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

	/*
	 * 加锁脚本sha1值
	 */
	private static final String SCRIPT_LOCK_SHA1 = encrypt(SCRIPT_LOCK);
	/*
	 * 解锁脚本sha1值
	 */
	private static final String SCRIPT_UNLOCK_SHA1 = encrypt(SCRIPT_UNLOCK);

	/**
	 * 尝试获取分布式锁
	 * <p>
	 * //	 * @param redisTemplate          Redis客户端
	 *
	 * @param lockKey                锁
	 * @param requestId              请求标识
	 * @param expireTimeMilliseconds 超期时间,多少毫秒后这把锁自动释放
	 * @return 返回true表示拿到锁
	 */
	@SuppressWarnings("unchecked")
	public static boolean tryGetDistributedLock(
			@SuppressWarnings("rawtypes") final RedisTemplate redisTemplate,
			final String lockKey,
			final String requestId,
			final long expireTimeMilliseconds) {

		Object result = redisTemplate.execute(
				new RedisScript<Long>() {
					@Override
					public String getSha1() {
						return SCRIPT_LOCK_SHA1;
					}

					@Override
					public Class<Long> getResultType() {
						return Long.class;
					}

					@Override
					public String getScriptAsString() {
						return SCRIPT_LOCK;
					}

				}, Collections.singletonList(lockKey),// KEYS[1]
				requestId, // ARGV[1]
				expireTimeMilliseconds // ARGV[2]
		);
	
		return SUCCESS.equals(result);
	}

	/**
	 * 释放分布式锁
	 * <p>
	 * //	 * @param redisTemplate Redis客户端
	 *
	 * @param lockKey   锁
	 * @param requestId 请求标识
	 * @return 返回true表示释放锁成功
	 */
	@SuppressWarnings("unchecked")
	public static boolean releaseDistributedLock(
			@SuppressWarnings("rawtypes") RedisTemplate redisTemplate,
			String lockKey, String requestId) {

		Object result = redisTemplate.execute(new RedisScript<Long>() {
			@Override
			public String getSha1() {
				return SCRIPT_UNLOCK_SHA1;
			}

			@Override
			public Class<Long> getResultType() {
				return Long.class;
			}

			@Override
			public String getScriptAsString() {
				return SCRIPT_UNLOCK;
			}

		}, Collections.singletonList(lockKey), requestId);
		
		return SUCCESS.equals(result);
	}


	private static final String ZERO = "0";
	private static final String ALGORITHM = "SHA1";

	/**
	 * sha1加密
	 *
	 * @param str
	 * @return 返回十六进制的字符串形式,全部小写
	 */
	private static String encrypt(String str) {
		if (StrUtil.isBlank(str)) {
			return null;
		}
		try {
			MessageDigest messageDigest = MessageDigest.getInstance(ALGORITHM);
			messageDigest.update(str.getBytes());
			return byte2hex(messageDigest.digest());
		} catch (Exception e) {
			throw new BizException("sha1加密异常:" + e.getMessage(), e);
		}
	}

	/**
	 * 二进制转十六进制字符串
	 *
	 * @param bytes
	 * @return
	 */
	private static String byte2hex(byte[] bytes) {
		StringBuilder sign = new StringBuilder();
		for (int i = 0; i < bytes.length; i++) {
			String hex = Integer.toHexString(bytes[i] & 0xFF);
			if (hex.length() == 1) {
				sign.append(ZERO);
			}
			sign.append(hex);
		}
		return sign.toString();
	}

}

测试类

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.redis.core.RedisTemplate;

@ComponentScan
@SpringBootTest(classes = Tests.class)
class Tests{
	@Autowired
	private RedisTemplate redisTemplate;
	
	private int number = 0;

	@Test
	public void test3() {
		String lockKey = "111";
		String requestId = "123";
		long expireTime = 1000;
		try {
		if (RedisLockUtil.tryGetDistributedLock(redisTemplate, lockKey, requestId, expireTime)) {
			System.out.println("当前数字:" + number++);
		}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			RedisLockUtil.releaseDistributedLock(redisTemplate,lockKey, requestId);
		}
	}

执行结果

org.springframework.data.redis.RedisSystemException: 
Error in execution; nested exception is io.lettuce.core.RedisCommandExecutionException:
 ERR Error running script (call to f_a30e2ad38036bd1262c4c14a46e4fe2eeb5019f7):
  @user_script:1: ERR value is not an integer or out of range 
	at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:54)
	at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:52)
	at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:41)
	at org.springframework.data.redis.PassThroughExceptionTranslationStrategy.translate(PassThroughExceptionTranslationStrategy.java:44)
	at org.springframework.data.redis.FallbackExceptionTranslationStrategy.translate(FallbackExceptionTranslationStrategy.java:42)
	at org.springframework.data.redis.connection.lettuce.LettuceConnection.convertLettuceAccessException(LettuceConnection.java:273)
	at org.springframework.data.redis.connection.lettuce.LettuceScriptingCommands.convertLettuceAccessException(LettuceScriptingCommands.java:236)
	at org.springframework.data.redis.connection.lettuce.LettuceScriptingCommands.evalSha(LettuceScriptingCommands.java:195)
	at org.springframework.data.redis.connection.DefaultedRedisConnection.evalSha(DefaultedRedisConnection.java:1502)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.data.redis.core.CloseSuppressingInvocationHandler.invoke(CloseSuppressingInvocationHandler.java:61)
	at com.sun.proxy.$Proxy206.evalSha(Unknown Source)
	at org.springframework.data.redis.core.script.DefaultScriptExecutor.eval(DefaultScriptExecutor.java:77)
	at org.springframework.data.redis.core.script.DefaultScriptExecutor.lambda$execute$0(DefaultScriptExecutor.java:68)
	at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:228)
	at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:188)
	at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:175)
	at org.springframework.data.redis.core.script.DefaultScriptExecutor.execute(DefaultScriptExecutor.java:58)
	at org.springframework.data.redis.core.script.DefaultScriptExecutor.execute(DefaultScriptExecutor.java:52)
	at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:350)
	at com.jiahe.common.redis.util.RedisLockUtil.tryGetDistributedLock(RedisLockUtil.java:87)
	at com.jiahe.payment.JiaheYlqPaymentApplicationTests.test3(JiaheYlqPaymentApplicationTests.java:42)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:686)
	at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
	at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
	at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:212)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:208)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:137)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:71)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:135)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
	at java.util.ArrayList.forEach(ArrayList.java:1249)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
	at java.util.ArrayList.forEach(ArrayList.java:1249)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:248)
	at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$5(DefaultLauncher.java:211)
	at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:226)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:199)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:132)
	at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:69)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: io.lettuce.core.RedisCommandExecutionException: ERR Error running script (call to f_a30e2ad38036bd1262c4c14a46e4fe2eeb5019f7): @user_script:1: ERR value is not an integer or out of range 
	at io.lettuce.core.ExceptionFactory.createExecutionException(ExceptionFactory.java:135)
	at io.lettuce.core.ExceptionFactory.createExecutionException(ExceptionFactory.java:108)
	at io.lettuce.core.protocol.AsyncCommand.completeResult(AsyncCommand.java:118)
	at io.lettuce.core.protocol.AsyncCommand.complete(AsyncCommand.java:109)
	at io.lettuce.core.protocol.CommandWrapper.complete(CommandWrapper.java:59)
	at io.lettuce.core.protocol.CommandHandler.complete(CommandHandler.java:680)
	at io.lettuce.core.protocol.CommandHandler.decode(CommandHandler.java:640)
	at io.lettuce.core.protocol.CommandHandler.channelRead(CommandHandler.java:591)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
	at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
	at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
	at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
	at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:719)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:655)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:581)
	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:745)

错误信息提示ERR值不是整数或超出范围。

问题解决

在这里插入图片描述
改为int 类型即可
在这里插入图片描述
没搞过lua脚本, 所以刚开始没搞懂什么意思, 搞了很久都没搞好😂

实现分布式限流可以使用 RedisLua 脚本来完成。以下是可能的实现方案: 1. 使用 Redis 的 SETNX 命令来实现基于令牌桶算法的限流 令牌桶算法是一种常见的限流算法,它可以通过令牌的放置和消耗来控制流量。在 Redis 中,我们可以使用 SETNX 命令来实现令牌桶算法。 具体实现步骤如下: - 在 Redis 中创建一个有序集合,用于存储令牌桶的令牌数量和时间戳。 - 每当一个请求到达时,我们首先获取当前令牌桶中的令牌数量和时间戳。 - 如果当前时间戳与最后一次请求的时间戳之差大于等于令牌桶中每个令牌的发放时间间隔,则将当前时间戳更新为最后一次请求的时间戳,并且将令牌桶中的令牌数量增加相应的数量,同时不超过最大容量。 - 如果当前令牌桶中的令牌数量大于等于请求需要的令牌数量,则返回 true 表示通过限流,将令牌桶中的令牌数量减去请求需要的令牌数量。 - 如果令牌桶中的令牌数量不足,则返回 false 表示未通过限流。 下面是使用 RedisLua 脚本实现令牌桶算法的示例代码: ```lua -- 限流的 key local key = KEYS[1] -- 令牌桶的容量 local capacity = tonumber(ARGV[1]) -- 令牌的发放速率 local rate = tonumber(ARGV[2]) -- 请求需要的令牌数量 local tokens = tonumber(ARGV[3]) -- 当前时间戳 local now = redis.call('TIME')[1] -- 获取当前令牌桶中的令牌数量和时间戳 local bucket = redis.call('ZREVRANGEBYSCORE', key, now, 0, 'WITHSCORES', 'LIMIT', 0, 1) -- 如果令牌桶为空,则初始化令牌桶 if not bucket[1] then redis.call('ZADD', key, now, capacity - tokens) return 1 end -- 计算当前令牌桶中的令牌数量和时间戳 local last = tonumber(bucket[2]) local tokensInBucket = tonumber(bucket[1]) -- 计算时间间隔和新的令牌数量 local timePassed = now - last local newTokens = math.floor(timePassed * rate) -- 更新令牌桶 if newTokens > 0 then tokensInBucket = math.min(tokensInBucket + newTokens, capacity) redis.call('ZADD', key, now, tokensInBucket) end -- 检查令牌数量是否足够 if tokensInBucket >= tokens then redis.call('ZREM', key, bucket[1]) return 1 else return 0 end ``` 2. 使用 RedisLua 脚本实现基于漏桶算法的限流 漏桶算法是另一种常见的限流算法,它可以通过漏桶的容量和漏水速度来控制流量。在 Redis 中,我们可以使用 Lua 脚本实现漏桶算法。 具体实现步骤如下: - 在 Redis 中创建一个键值对,用于存储漏桶的容量和最后一次请求的时间戳。 - 每当一个请求到达时,我们首先获取当前漏桶的容量和最后一次请求的时间戳。 - 计算漏水速度和漏水的数量,将漏桶中的容量减去漏水的数量。 - 如果漏桶中的容量大于等于请求需要的容量,则返回 true 表示通过限流,将漏桶中的容量减去请求需要的容量。 - 如果漏桶中的容量不足,则返回 false 表示未通过限流。 下面是使用 RedisLua 脚本实现漏桶算法的示例代码: ```lua -- 限流的 key local key = KEYS[1] -- 漏桶的容量 local capacity = tonumber(ARGV[1]) -- 漏水速度 local rate = tonumber(ARGV[2]) -- 请求需要的容量 local size = tonumber(ARGV[3]) -- 当前时间戳 local now = redis.call('TIME')[1] -- 获取漏桶中的容量和最后一次请求的时间戳 local bucket = redis.call('HMGET', key, 'capacity', 'last') -- 如果漏桶为空,则初始化漏桶 if not bucket[1] then redis.call('HMSET', key, 'capacity', capacity, 'last', now) return 1 end -- 计算漏水的数量和漏桶中的容量 local last = tonumber(bucket[2]) local capacityInBucket = tonumber(bucket[1]) local leak = math.floor((now - last) * rate) -- 更新漏桶 capacityInBucket = math.min(capacity, capacityInBucket + leak) redis.call('HSET', key, 'capacity', capacityInBucket) redis.call('HSET', key, 'last', now) -- 检查容量是否足够 if capacityInBucket >= size then return 1 else return 0 end ``` 以上是使用 RedisLua 脚本实现分布式限流的两种方案,可以根据实际需求选择适合的方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值