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>
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐