Redis使用Lua脚本的两个小问题
- 最近在项目中使用redisTemplate 执行Lua 脚本发现两个比较坑的地方,发现之后其实也很简单,过程很容易让人抓狂,
一、整型转换
1.1 场景
- 逻辑是通过一段 Lua 脚本去给redis中的一个值加上参数1的值,如果结果大于参数2,那么就把值归零并返回1,如果小于参数二就不归零且返回0,
static String SCRIPT =
"local sum = redis.call(\"GET\", KEYS[1]) + ARGV[1];\n" +
"if sum >= ARGV[2] then\n" +
"\t redis.call(\"set\",KEYS[1], 0)\n" +
" return 1\n" +
" else\n" +
" redis.call(\"INCRBY\",KEYS[1],ARGV[1])\n" +
"\t return 0\n" +
" end";
- 测试代码:
@Test
public void test3() {
DefaultRedisScript<Integer> redisScript = new DefaultRedisScript<>(SCRIPT,Integer.class);
List<String> keys = Lists.newArrayList("dynamic_car.car__statistical") ;
Object execResult = (Object) redisTemplate.execute(redisScript, keys, 3, 10);
if (execResult != null) {
System.out.println(execResult);
} else {
System.out.println("End ... ");
}
}
1.2 问题
- 错误1:执行的时候报错,首先报的是String无法转换为Integer,这里需要将Lua中的 ARGV[2] 改为: tomumber(ARGV[2]),将参数2转换为数字,即使传进去就是整型也要转,日志如下,提示第二行尝试将String转换为number,这里也感觉很奇怪,明显传参是Integer类型。
@user_script:2: user_script:2: attempt to compare string with number
- 错误2:在 Lua 中根据条件返回1和0,因此初始化 DefaultRedisScript的泛型是 Integer 类型,执行的时候报错:抛出异常,但是查看redis Lua的执行却起了效果,redis中数据已经修改了,由此推测这个异常是和返回值相关,因此把DefaultRedisScript的Integer改成 Long 就好了。
Caused by: io.lettuce.core.RedisException: java.lang.IllegalStateException
- 错误3:这个不算错误,算是一个bug,在如下Lua中第一行call中如果get的key不存在,则返回的是false,那么加运算就会抛出异常提示Boolean类型不能运算,这里需要校验
"local present = redis.call(\"GET\", KEYS[1]);"
1.3 修复
- 最后修改后的Lua和代码如下:
public void test3() {
DefaultRedisScript<Long> getRedisScript = new DefaultRedisScript<>(SCRIPT, Long.class);
String redisKet = "dynamic_car.car__statistical1";
List<String> keys = Lists.newArrayList(redisKet);
Object execResult = (Object) redisTemplate.execute(getRedisScript, keys, 3, 10);
System.out.println(execResult + " -- "+ execResult.getClass());
}
- lua
public static String SCRIPT =
"local present = redis.call(\"GET\", KEYS[1]);\n" +
"if present == false then \n" +
"\t redis.call(\"set\",KEYS[1], ARGV[1])\n" +
"\t return 0 \n" +
"\t end" +
"\t local sum = redis.call(\"GET\", KEYS[1]) + ARGV[1];\n" +
"\t if sum >= tonumber(ARGV[2]) then\n" +
"\t redis.call(\"set\",KEYS[1],0)\n" +
" return 1\n" +
" else\n" +
" redis.call(\"INCRBY\",KEYS[1],ARGV[1])\n" +
"\t return 0\n" +
" end";
二、获取Set元素
2.1 场景
- 通过 Lua 脚本尝试去获取一个 Set 集合的全部元素,如果集合存在,就获取元素并删除集合,如果不存在,就什么都不做;
public static String SCRIPT = "\n" +
"\t if redis.call(\"EXISTS\",KEYS[1]) > 0 then\n" +
"\t local datas = redis.call(\"SMEMBERS\",KEYS[1])\n" +
"\t redis.call(\"DEL\",KEYS[1])\n" +
"\treturn datas\n" +
"end\n" +
"\t";
- java代码
@Test
public void test11() {
DefaultRedisScript<Set> getRedisScript = new DefaultRedisScript<>(SCRIPT1,Set.class);
List<String> keys = Lists.newArrayList("setSchemaTableIds-1");
Object execute = redisTemplate.execute(getRedisScript, keys);
System.out.println(execute);
}
2.2 问题
- 执行后抛出异常:而且发现 execute 返回的总是Set里面的一个元素,很奇怪,改了很多遍 Lua 脚本,定义Set等都不奏效
java.lang.ClassCastException: java.lang.String cannot be cast to java.util.List
2.3 修复
-
最后将DefaultRedisScript 的泛型Set 改成 List,就解决问题了,明显是获取Set,接收是List,也算一个小坑了。不过网上看到说原始的 Lua 获取到 Set 里面的元素顺序是不固定的,猜测可能是 redisTemplate 做了封装,使用List 固定了返回的顺序
-
注意我使用的版本是
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.0.2.RELEASE</version>
</dependency>