Redis从2.6版支持Lua。Lua脚本可以编译、运行在任何平台上;一个脚本即是一个原子事务。
官网,一门小巧精悍的脚本语言。设计用于嵌入到应用程序中,为应用程序提供灵活的扩展、定制功能,与C/C++代码可相互调用。还可用作配置文件。Lua-JIT项目,旨在提供在特定平台上的即时编译功能。
特性:
Redis支持大部分Lua标准库:
库名 | 说明 |
---|---|
Base | 提供一些基础函数 |
String | 提供用于字符串操作的函数 |
Table | 提供用于表操作的函数 |
Math | 提供数学计算函数 |
Debug | 提供用于调试的函数 |
另外,在脚本中可使用redis.call
函数调用redis命令:
redis.call('set', 'foo', 'bar')
local value=redis.call('get', 'foo') --value的值为bar
Redis命令的返回值有5种类型,redis.call
函数会将这5种类型的返回值转换成对应的Lua的数据类型:
redis返回值类型 | Lua数据类型 |
---|---|
整数 | 数字类型 |
字符串 | 字符串类型 |
多行字符串 | table类型,数组形式 |
状态 | table类型(只有一个ok字段存储状态信息) |
错误 | table类型(只有一个err字段存储错误信息) |
空结果 | false |
redis还提供redis.pcall
函数,功能与redis.call
相同,唯一的区别是当命令执行出错时,redis.pcall
会记录错误并继续执行,而redis.call
会直接返回错误,不会继续执行。在脚本中可以使用return语句将值返回给客户端,如果没有执行return语句则默认返回nil。
在redis.conf
配置文件中:lua-time-limit 5000
。为了防止某个脚本执行时间过长,导致Redis无法提供服务,Redis提供lua-time-limit参数限制脚本的最长运行时间,默认为5秒钟。当脚本运行时间超过这一限制后,Redis将开始接受其他命令但不会执行(以确保脚本的原子性,因为此时脚本并没有被终止),而是会返回“BUSY”错误。
Redis支持Lua脚本功能,随之新增的几个命令(script debug除外,是Redis 3.2版本引入的命令)。
参考:eval文档
命令参数:EVAL script numkeys key [key ...] arg [arg ...]
命令解读:
key [key ...]
,表示在脚本中所用到的那些Redis键(key),可在Lua中通过全局变量KEYS数组,用1为基址的形式访问( KEYS[1] , KEYS[2] ,以此类推)arg [arg ...]
,可在Lua中通过全局变量ARGV数组访问,访问的形式和KEYS变量类似( ARGV[1] 、 ARGV[2] ,诸如此类)演示:
1608(10.114.31.113:6408)> eval "return {KEYS[1],ARGV[1]}" 1 testKey testValue
1) "testKey"
2) "testValue"
1608(10.114.31.113:6408)> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 username age jack 20
"CROSSSLOT Keys in request don't hash to the same slot"
参考:redis-cross-slot-error,大意是在一个分布式Redis集群里,key会被划分到不同的槽中,不同节点会拥有散列槽的一个子集。
In a cluster topology, the keyspace is divided into hash slots. Different nodes will hold a subset of hash slots.Multiple keys operations, transactions, or Lua scripts involving multiple keys are allowed only if all the keys involved are in hash slots belonging to the same node.
Redis集群实现了所有非分布式版本的单key命令。多个key的操作、事务或者lua脚本调用多个key是允许的,前提是:所有被调用的key都在一个节点的hash槽中就可以。
解决方法
使用Hash Tags强制所有的key属于一个节点。
lua脚本较长时,可放置在文件中,
$ redis-cli --eval path/to/redis.lua KEYS[1] KEYS[2] ... , ARGV[1] ARGV[2] ...
–eval,告诉redis-cli读取并运行后面的lua脚本
KEYS和ARGV中间的 ‘,’ 两边的空格,不能省略。
参考:evalsha文档
在脚本比较长的情况下,若每次调用脚本都需要将整个脚本传给Redis会占用较多的带宽。为解决这个问题,Redis提供EVALSHA命令,允许开发者通过脚本内容的SHA1摘要来执行脚本,该命令的用法和EVAL一样,只不过是将脚本内容替换成脚本内容的SHA1摘要。
Redis在执行EVAL命令时会计算脚本的SHA1摘要并记录在脚本缓存中,执行EVALSHA命令时Redis会根据提供的摘要从脚本缓存中查找对应的脚本内容,找到则执行脚本,否则返回错误:“NO SCRIPT No matching script. Please use EVAL.”
命令参数:EVALSHA sha1 numkeys key [key ...] arg [arg ...]
sha1是通过SCRIPT LOAD
生成的SHA1校验码。
对比
eval命令会将脚本添加到脚本缓存中,并立即对输入的脚本进行求值。
evalsha命令会将脚本添加到脚本缓存中,但并不立即执行这个脚本。
参考:script-load文档
将脚本加入缓存,但不执行, 返回脚本的SHA1摘要。
参考:script-exists文档
判断脚本是否已被缓存。
参考:script-flush文档
清空脚本缓存,redis将脚本的SHA1摘要加入到脚本缓存后会永久保留,手动使用SCRIPT FLUSH命令清空脚本缓存。
参考:script-kill文档
强制终止当前脚本的执行。但是如果当前执行的脚步对redis的数据进行写操作,则SCRIPT KILL命令不会终止脚本的运行,以防止脚本只执行一部分。脚本中的所有命令,要么都执行,要么都不执行。
参考:script-debug文档
将上面演示的eval命令翻译成基于Jedis的Java代码:
@Test
public void testLuaWithJedis() {
Jedis jedis = new Jedis("10.114.31.113", 6408);
String luaStr = "return {KEYS[1],ARGV[1]}";
Object result = jedis.eval(luaStr, Lists.newArrayList("testKey"), Lists.newArrayList("testValue"));
}
但是报错。
解决方法:
spring-boot-starter-data-redis依赖,使用redisTemplate
lua脚本中的变量都要是local 的,不可以是全局变量,否则会报错。详见 http://doc.redisfans.com/script/eval.html#id6
使用DefaultRedisScript加载lua脚本
在应用上下文中配置一个DefaultRedisScript单例,避免在每个脚本执行的时候重复创建脚本的SHA1:
@Bean
public DefaultRedisScript<Boolean> redisScript() {
DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>();
redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/checkandset.lua")));
redisScript.setResultType(Boolean.class);
return redisScript;
}
redis-lua-script
redis-cross-slot-error
https://blog.csdn.net/u011943534/article/details/82717253