lua脚本语言小巧轻便,运行性能优异,被很多语言作为嵌入式脚本语言使用。redis对lua脚本语言有很好的支持,对于开发者,如果要处理的数据涉及到只是简单的计算逻辑,就不必将redis中的数据拿到应用程序中执行了,直接写一段lua脚本程序就可以在redis服务端完成逻辑处理。
有关lua语言的语法内容,这里就不做过多的介绍,这里只是介绍在redis中如何运行lua脚本。
在redis命令行运行lua脚本非常简单,只需要定义好lua代码的字符串,在redis客户端使用 eval
命令运行脚本,命令格式如下:
127.0.0.1:6379> eval script numkeys key [key ...] arg [arg ...]
script :是要执行的lua脚本字符串
numkeys :key的数量,如果没有键这个数就设置为0
key :key的具体值,key的数量要与前面定义的数量保持一致
arg :参数列表
下面演示一下如何使用上面的命令:
127.0.0.1:6379> eval "return 'hello,world!';" 0
"hello,world!"
127.0.0.1:6379>
127.0.0.1:6379> eval "return redis.call('set', KEYS[1], ARGV[1]);" 1 lua_key lua_val
OK
127.0.0.1:6379>
127.0.0.1:6379> eval "return redis.call('get', KEYS[1]);" 1 lua_key
"lua_val"
127.0.0.1:6379>
127.0.0.1:6379> eval "if redis.call('exists', KEYS[1]) > 0 then return redis.call('get', KEYS[1]); else return ''; end;" 1 kkkk
""
127.0.0.1:6379>
127.0.0.1:6379> eval "if redis.call('exists', KEYS[1]) > 0 then return redis.call('get', KEYS[1]); else return ''; end;" 1 lua_key
"lua_val"
127.0.0.1:6379>
在lua脚本中调用redis命令使用 redis.call()
方法,方法的第一个参数是命令的名称,后面跟参数,所有的键放入KEYS中,值放入ARGV中,在脚本中参数下标从1开始。
除了上面的调用方式,我们也可以将lua脚本写入到系统文件中,然后通过命令行调用本地文件执行:
我们在系统新建一个lua脚本文件,命名为lua_script.lua,内容如下:
local exist = redis.call('exists', KEYS[1]);
if exist > 0 then
local rs = redis.call('incrby', KEYS[1], ARGV[1]);
return '1_' .. rs; // 这里是lua中的字符串拼接方法:str1 .. str2 会将两个字符串拼接成一个字符串
else
local rs = redis.call('set', KEYS[1], ARGV[2]);
if rs then
local rs = redis.call('incrby', KEYS[1], ARGV[1]);
return '2_' .. rs;
else
return nil;
end;
end;
脚本的执行流程是:先判断键是否存在,如果存在直接执行incrby指令进行加数操作;如果不存在,先初始化键的值,然后再执行incrby指令进行加数操作。
lua脚本文件执行调用命令是:
redis-cli --eval myscript.lua key1 key2 , arg1 arg2 arg3
–eval 表示要执行lua脚本文件,后面跟着的就是脚本文件路径,可以是相对路径也可以是绝对路径;接下来就是所有的键和值,键和值之间使用","分隔,多个键和多个值之间使用空格分隔。
例如,要调用上面写好的脚本文件使用下面的命令:
[root@localhost src]# ./redis-cli --eval /root/lua_scripts/lua_script.lua incr:key:0001 , -2 200
"2_198"
[root@localhost src]#
[root@localhost src]# ./redis-cli --eval /root/lua_scripts/lua_script.lua incr:key:0001 , -2 200
"1_196"
[root@localhost src]#
命令行中运行lua脚本在实际项目中其实并不常用,更多的是在代码中执行lua脚本,下面演示在代码中如何使用lua脚本,先演示在redisTemplate中的使用:
在redisTemplate中使用lua脚本需要调用redisTemplate.execute(RedisScript script, List keys, Object… args)方法,该方法第一个参数是lua脚本,第二个参数是键列表,第三个参数是一个不定个数的值数组。使用方式如下:
String lua = "return redis.call('set', KEYS[1], ARGV[1]);";
DefaultRedisScript<String> script = new DefaultRedisScript<>(lua, String.class);
List<String> keys = Collections.singletonList("java:lua:key");
String rs = redisTemplate.execute(script, keys, "java_lua_value");
除了上面这种定义lua脚本方式,还可以写一个lua脚本文件,在程序中调用这个lua脚本文件执行,代码如下:
DefaultRedisScript<String> script = new DefaultRedisScript<>();
script.setResultType(String.class);
script.setScriptSource(new ResourceScriptSource(new FileSystemResource("E:\\lua_script.lua")));
List<String> keys = Collections.singletonList("ncr:key:0001");
String rs = redisTemplate.execute(script, keys, "-2", "200");
如果项目使用的是jedis,代码就跟redis客户端执行命令一样:
String script = "return redis.call('get', KEYS[1]);";
Object rs = jedis.eval(script, 1, "incr:key:0001");
System.out.println(rs);
redis还提供了使用evalsha()方式执行lua脚本,这种方式是先将lua脚本在服务端缓存,客户端只需要传递过去缓存脚本的编码就可以调用lua脚本执行:
比如在服务端加载一个lua脚本并生成了编码:
127.0.0.1:6379> script load "return redis.call('get', KEYS[1]);"
"796941151549c416aa77522fb347487236c05e46"
127.0.0.1:6379>
这时在客户端要执行lua脚本只需要执行下面的方法:
Object rs = jedis.evalsha("796941151549c416aa77522fb347487236c05e46", 1, "incr:key:0001");
System.out.println(rs);
使用redisTemplate调用evalsha需要redisTemplate的execute()方法,它类似redis原生命令,请求和返回数据都是byte数组:
Object rs = redisTemplate.execute(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
byte[] rs = connection.evalSha("796941151549c416aa77522fb347487236c05e46", ReturnType.VALUE, 1, "incr:key:0001".getBytes(StandardCharsets.UTF_8));
return new String(rs);
}
});
System.out.println(rs);
上面这种方式性能是最好的,因为脚本都是在服务端缓存起来了,省去了数据提交时的性能损耗。但是在项目中应该很少被使用,因为一旦redis宕机,意味着缓存的脚本编码都将失效,那么程序运行就会出问题。
在redis中使用lua脚本非常好用,可以将一些简单的处理逻辑在服务端就执行了,避免数据的来回传输。使用lua脚本也可以让多个命令执行保持原子性,在lua脚本执行中间不能插入其他的redis命令。由于lua脚本都是整体运行的,所以也不建议在lua脚本中执行非常耗时的命令,或者一个lua脚本中执行过多的命令,这样会导致服务端响应其他的请求等待时间超时。