网上关于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);
各种类型的编解码器都能在源码里找到,这里就不在赘述。以上。