redis中使用lua脚本处理业务逻辑

lua脚本语言小巧轻便,运行性能优异,被很多语言作为嵌入式脚本语言使用。redis对lua脚本语言有很好的支持,对于开发者,如果要处理的数据涉及到只是简单的计算逻辑,就不必将redis中的数据拿到应用程序中执行了,直接写一段lua脚本程序就可以在redis服务端完成逻辑处理。

有关lua语言的语法内容,这里就不做过多的介绍,这里只是介绍在redis中如何运行lua脚本。

  1. 命令行运行脚本

在redis命令行运行lua脚本非常简单,只需要定义好lua代码的字符串,在redis客户端使用 eval 命令运行脚本,命令格式如下:

shell 复制代码
127.0.0.1:6379> eval script numkeys key [key ...] arg [arg ...]

script :是要执行的lua脚本字符串

numkeys :key的数量,如果没有键这个数就设置为0

key :key的具体值,key的数量要与前面定义的数量保持一致

arg :参数列表

下面演示一下如何使用上面的命令:

shell 复制代码
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,内容如下:

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脚本文件执行调用命令是:

shell 复制代码
redis-cli --eval myscript.lua key1 key2 , arg1 arg2 arg3

--eval 表示要执行lua脚本文件,后面跟着的就是脚本文件路径,可以是相对路径也可以是绝对路径;接下来就是所有的键和值,键和值之间使用","分隔,多个键和多个值之间使用空格分隔。

例如,要调用上面写好的脚本文件使用下面的命令:

shell 复制代码
[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]# 
  1. 代码中使用lua脚本

命令行中运行lua脚本在实际项目中其实并不常用,更多的是在代码中执行lua脚本,下面演示在代码中如何使用lua脚本,先演示在redisTemplate中的使用:

在redisTemplate中使用lua脚本需要调用redisTemplate.execute(RedisScript script, List keys, Object... args)方法,该方法第一个参数是lua脚本,第二个参数是键列表,第三个参数是一个不定个数的值数组。使用方式如下:

java 复制代码
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脚本文件执行,代码如下:

java 复制代码
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客户端执行命令一样:

java 复制代码
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脚本并生成了编码:

shell 复制代码
127.0.0.1:6379> script load "return redis.call('get', KEYS[1]);"
"796941151549c416aa77522fb347487236c05e46"
127.0.0.1:6379> 

这时在客户端要执行lua脚本只需要执行下面的方法:

java 复制代码
Object rs = jedis.evalsha("796941151549c416aa77522fb347487236c05e46", 1, "incr:key:0001");
System.out.println(rs);

使用redisTemplate调用evalsha需要redisTemplate的execute()方法,它类似redis原生命令,请求和返回数据都是byte数组:

java 复制代码
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脚本中执行过多的命令,这样会导致服务端响应其他的请求等待时间超时。

相关推荐
李少兄1 小时前
解决Spring Boot整合Redis时的连接问题
spring boot·redis·后端
日里安1 小时前
8. 基于 Redis 实现限流
数据库·redis·缓存
sam-12310 小时前
k8s上部署redis高可用集群
redis·docker·k8s
看山还是山,看水还是。11 小时前
Redis 配置
运维·数据库·redis·安全·缓存·测试覆盖率
谷新龙00111 小时前
Redis运行时的10大重要指标
数据库·redis·缓存
精进攻城狮@11 小时前
Redis缓存雪崩、缓存击穿、缓存穿透
数据库·redis·缓存
程序员小羊!14 小时前
深入理解接口测试:实用指南与最佳实践5.0(三)
开发语言·lua
avenue轩16 小时前
gdb调试redis。sudo
c++·redis
不惑_16 小时前
Redis:发布(pub)与订阅(sub)实战
前端·redis·bootstrap
cui_win16 小时前
Redis高可用-Sentinel(哨兵)
redis·bootstrap·sentinel