SpringBoot2.7.0集成Redisson操作Redis脚本

网上关于Redis五大数据结构的增删改查代码示例比较多,但关于Redis脚本的代码示例很少,所以有了这篇文章。

依赖

核心依赖如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.17.4</version>
</dependency>

配置

application.yml

spring:
  redis:
    database: 2
    host: 127.0.0.1
    port: 6379
    password: 123456

配置完就可以使用redisson相关的Bean了

代码示例

注入RedissonClient

@Autowired
private RedissonClient rClient;

获取Redis脚本

方式一:

redis.script:
  test:
    set: "return redis.call('SET', KEYS[1], ARGV[1])"
    get: "return redis.call('GET', KEYS[1])"

这种方式只适合脚本比较简短的情况。对于特别长的脚本,需要写在单独的文件里,然后从文件读取。

方式二:

将脚本写在单独的文件里,比如test.lua。然后告诉程序文件路径,程序从文件里读取。从文件读取lua脚本可以使用springboot自带的类,无需额外引入依赖,方便又快捷:

DefaultRedisScript<Void> redisScript = new DefaultRedisScript<>();
redisScript.setScriptSource(new ResourceScriptSource(new FileSystemResource(scriptPath)));
scriptContent = redisScript.getScriptAsString();
log.info("Script content->{}{}", System.getProperty("line.separator"), scriptContent);

将脚本加载到Redis缓存

对应的Redis命令是SCRIPT LOAD. 返回值是脚本的SHA1摘要。

RScript rScript = rClient.getScript();
shaDigest = rScript.scriptLoad(scriptContent);

脚本太长的情况下,load一下很有必要,可以减少每次执行脚本所传输的数据量,提高效率。

执行脚本

重点来了!

一、直接执行脚本,调用RScript的eval方法,对应的Redis命令是EVAL

<R> R eval(Mode mode, String luaScript, ReturnType returnType, List<Object> keys, Object... values);

下面将逐个说明参数:

  • mode。有READ_ONLY和READ_WRITE两种模式
  • luaScript。Redis脚本原文
  • 返回值类型。有以下几种:
BOOLEAN(RedisCommands.EVAL_BOOLEAN_SAFE),
INTEGER(RedisCommands.EVAL_LONG),
MULTI(RedisCommands.EVAL_LIST),
STATUS(RedisCommands.EVAL_STRING),
VALUE(RedisCommands.EVAL_OBJECT),
MAPVALUE(RedisCommands.EVAL_MAP_VALUE),
MAPVALUELIST(RedisCommands.EVAL_MAP_VALUE_LIST);

Redis返回值类型与LUA数据类型的对应如下:

在这里插入图片描述
是不是一脸懵B发现对不上?没错,Redisson给你进行了相应的转换,类型不对多试几次就行了。

  • keys。脚本中涉及到的键,对应KEYS
  • values。除key以外的参数,对应ARGV

在Redis中执行脚本传的第一个参数是key的个数,例如在Redis命令行执行如下命令:

EVAL "return redis.call('SET', KEYS[1], ARGV[1])" 1 foo bar

上面那个1就是key的个数,不需要传key就填0。foo是key,bar是value。

RScript的eval方法不需要传key的个数,List类型参数keys的size就是key的个数,所以这个keys不能为null,当不需要传key时,keys给empty list。

示例如下:

List<Object> evalResult = rScript.eval(RScript.Mode.READ_ONLY, scriptContent, RScript.ReturnType.MULTI,
                new ArrayList<>(), userId, mediaType);

二、通过SHA1执行脚本,调用RScript的evalSha方法,对应的Redis命令EVALSHA

<R> R evalSha(Mode mode, String shaDigest, ReturnType returnType, List<Object> keys, Object... values);

shaDigest是脚本的SHA1值,其他参数和eval方法没啥区别。示例如下:

List<Object> evalResult = rScript.evalSha(RScript.Mode.READ_ONLY, shaDigest, RScript.ReturnType.MULTI,
                    new ArrayList<>(), userId, mediaType);

需要强调一点,你load进Redis缓存的脚本有可能会被别人删掉,所以evalSha方法不一定能成功。意识到这一点之后,我看了下源码,发现eval方法实际上也是先获取脚本的SHA1值,然后执行EVALSHA命令,当EVALSHA命令返回NOSCRIPT异常时,会将脚本load一下,然后重新EVALSHA。而evalSha方法并没有做重试。所以用evalSha方法还不如用eval方法。

不过由于eval方法每次都是调的EVALSHA命令,如果有人一直在SCRIPT FLUSH, 那这个方法永远也无法成功。作者是不是有点自作聪明?

删除脚本缓存

void scriptFlush();

对应的Redis命令SCRIPT FLUSH。这个命令不支持传参,所以应该是删除所有的脚本缓存,慎用!

编解码器

RScript对象用于执行脚本,通过RedissonClient的如下两个方法获取它:


RScript getScript();

RScript getScript(Codec codec);
    

Codec既是编码器,又是解码器。getScript()用的是org.redisson.codec.MarshallingCodec。之所以要特地强调下这个,是因为我被坑过。默认值有时候不好使。运气好报个错;运气不好不报错,但是脚本的执行结果跟你预期的不一样。比如参数a本来是字符串"123", 你传个整型123,运算结果肯定不对。

假如我要获取一个RScript,使用字符编码,也就是所有参数和返回值都当做字符串处理,代码如下:

RScript rScript = rClient.getScript(StringCodec.INSTANCE);

各种类型的编解码器都能在源码里找到,这里就不在赘述。以上。


版权声明:本文为qq_35939417原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。