一、简介

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 FLUSHscriptFlush()清除lua脚本缓存
SCRIPT KILLscriptKill()杀死当前正在运行的 Lua 脚本,当且仅当这个脚本没有执行过任何写操作时,这个命令才生效,防止lua脚本死循环
SCRIPT LOAD scriptscriptLoad()将脚本 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用法

Logo

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

更多推荐