一、简介
redis操作时单线程的,平常如果想要redis原子性操作的话,可以使用incrBy()和decrBy()方法进行原子性的加减,但是对于事务性的逻辑操作,没有办法实现原子性,Redis 使用单个 Lua 解释器去运行所有脚本,当某个脚本正在运行的时候,不会有其他脚本或 Redis 命令被执行,因此,lua脚本需要运行的使用比较快,不会妨碍其它lua脚本执行
二、内容说明
redis命令 | JedisClusterTemplate方法 | 作用 |
EVAL script numkeys key [key ...] arg [arg ...] | eval() | 执行lua脚本 |
EVALSHA sha1 numkeys key [key ...] arg [arg ...] | evalsha() | 执行lua脚本对应的缓存值 |
SCRIPT EXISTS script [script ...] | scriptExists() | 判断脚本是否已经添加到缓存中去了,1代表已经添加,0代表没有添加 |
SCRIPT FLUSH | scriptFlush() | 清除lua脚本缓存 |
SCRIPT KILL | scriptKill() | 杀死当前正在运行的 Lua 脚本,当且仅当这个脚本没有执行过任何写操作时,这个命令才生效,防止lua脚本死循环 |
SCRIPT LOAD script | scriptLoad() | 将脚本 script 添加到脚本缓存中,但并不立即执行这个脚本,eval是执行并添加缓存 |
1.eval命令
eval script numkeys key [key ...] arg [arg ...]
#参数说明
#script:它会被运行在 Redis 服务器上下文中,这段脚本不必(也不应该)定义为一个 Lua 函数。
#numkeys:用于指定键名参数的个数。
#key:键名参数,表示在脚本中所用到的那些 Redis 键(key),这些键名参数可以在 Lua 中通过全局变量 KEYS 数组,用 1 为基址的形式访问( KEYS[1] , KEYS[2] ,以此类推)。
#arg:全局变量,可以在 Lua 中通过全局变量 ARGV 数组访问,访问的形式和 KEYS 变量类似( ARGV[1] 、 ARGV[2] ,诸如此类)
使用样例如下:执行脚本,,KEYS[1]对应foo,ARGV[1]对应第二个1,ARGV[2]对应100,角标[1]对应第1个参数,表示一个key,redis.call里面第一个参数redis命令,之后是该命令对应的key-value
--如果当前foo,对应的值减去1之后,小于0,那么就给它加上100返回,否则直接返回减去1值后的值
eval
"if redis.call('decrBy',KEYS[1],ARGV[1]) < 0 then
return redis.call('incrBy',KEYS[1],ARGV[2])
else
return redis.call('get',KEYS[1])
end"
1 foo 1 100
执行结果如下:
注意:脚本里使用的所有键都应该由 KEYS 数组来传递,变量入参通过ARGV数组传递
通过redis的eval命令来实现,使用 EVAL 命令对 Lua 脚本进行求值。在lua脚本中可以通过两个不同的函数调用redis命令,分别是:redis.call() 和 redis.pcall(),这两者方法对于错误的处理方式不同
redis.call():redis.call关键字执行redis命令,在执行命令的过程中发生错误时,脚本会停止执行,并返回一个脚本错误,错误的输出信息会说明错误造成的原因
redis.pcall(): 出错时并不引发(raise)错误,而是返回一个带 err 域的 Lua 表(table),用于表示错误
eval 命令会在每次执行脚本的时候都发送一次脚本主体(script body),每次都需要重新编译脚本,会有一定的损耗
2.evalsha命令
evalsha命令和eval一样,但它可以使用脚本生成的缓存sha来执行,出现错误是,会使用eval命令执行lua脚本
#格式
evalsha sha1 numkeys key [key ...] arg [arg ...]
(1)Redis 保证所有被运行过的脚本都会被永久保存在脚本缓存当中,当 eval命令在一个 Redis 实例上成功执行某个脚本之后,随后针对这个脚本的所有 evalsha命令都会成功执行,
(2)清空lua脚本方法需要使用 script flush命令
(3)不能使用全局性的脚本变量
3.其他命令
script load加载脚本到缓存中,但不立即执行,script exists判断是否存在该缓存,script flush清除所有lua脚本缓存,script kill杀死正在执行中的命令
三、Java中使用Jedis操作
1.定义lua脚本
这里代表 key的value当前值减去ARGV[1],如果减去ARGV[1]之后小于0的话则返回原来值,否则返回减去之后的值
public static final String LUA = "if redis.call('decrBy',KEYS[1],ARGV[1]) < 0 then\n" +
" return redis.call('incrBy',KEYS[1],ARGV[1])\n" +
" else\n" +
" return redis.call('get',KEYS[1])\n" +
"end ";
2.加载lua脚本(可省略)
String key = "demo";
jedisClusterTemplate.set(key, "2");
log.info("加载脚本lua脚本:lua script={}", LUA);
jedisClusterTemplate.scriptLoad(LUA, key);
3.使用eval执行lua脚本
执行jedisClusterTemplate中的eval方法,传入lua脚本,key的个数,只有接入对应参数,参数对应前面2.1里面redis用法