likes
comments
collection
share

Redis Lua 脚本详解LUA脚本高频面试题讲解

作者站长头像
站长
· 阅读数 104

Redis 在2.6推出了脚本功能,可以使用 Lua 语言编写脚本传到 Redis 执行。

脚本的优点:

  1. 减少网络开销:本来5次的网络请求,可以使用一个请求完成,原来5次的请求逻辑发送给 redis 服务器完成。减少了网络往返延时,这点跟管道类似。
  2. 原子操作:Redis 会将整个脚本作为一个整体执行,中间不会被其他命令插入。
  3. 替代 Redis 的事务功能:Redis 自带的事务功能很鸡肋,而 redis 的 lua 脚本几乎实现了常规的事务功能,官方推荐如果要使用 redis 的事务功能可以考虑用 lua 替代。

脚本使用说明

通过内置的 lua 解释器,可以使用 EVAL 命令对 Lua 脚本进行求职,命令格式如下:

# 脚本格式
EVAL script numkeys key [key ...] arg [arg ...]
# 示例
127.0.0.1:6379> EVAL "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 k1 k2 a1 a2 
1) "k1"
2) "k2"
3) "a1"
4) "a2"

参数说明:

第一个 script 参数是一段 Lua 脚本程序,他会被运行在 redis 服务器上下文中,这段脚本不必(也不应该)定义为一个 lua 函数。

第二个 numkeys 参数用于指定键名参数的个数。

从第三个参数开始(键名参数 key ),表示在脚本中所用到的那些 Redis 键(key),这些键名参数可以在 Lua 中通过全局变量数组 KEYS1 开始为基址的形式访问 KEYS[1],KEYS[2]

在命令的最后,那些不是键名参数的附加参数 arg [arg ...] ,可以在 lua 中通过全局变量 ARGV 数组进行访问,下标也从 1开始。

在 Lua 脚本中可以使用 redis.call() 函数来执行 redis 命令。

Jedis 扣减库存案例调用示例:

try (Jedis jedis = jedisPool.getResource()) {
    // 初始化商品id为100的库存
    jedis.set("product_stock_100", "10000");
    // @formatter:off
    String script = "local count =  redis.call('get',KEYS[1]) " +
            "local a = tonumber(count) " + 
            "local b = tonumber(ARGV[1]) " + 
            "if a >= b then " + 
            "redis.call('set',KEYS[1],a-b) " + 
            "return 1 " + 
            "end " + 
            "return 0";
    // @formatter:on
    Object decrResult = jedis.eval(script, Arrays.asList("product_stock_100"), Arrays.asList("100"));
    System.out.println(decrResult);
}

**注意:**不要在 lua 脚本中出现死循环和耗时的运算,否则 **redis 会阻塞 **,将不接受其他命令,所以使用时要注意不出现上述情况。

Redis 是单进程单线程执行脚本,管道不会阻塞 redis 。

Redis LUA 脚本执行命令会回滚吗?

使用lua脚本可以在执行一串redis命令时,实现一定原子性(lua脚本中多条指令执行过程中不会被插入新的指令), 但是并不能在命令执行出错时回滚之前的结果

我们用下面的代码片段进行验证,执行代码前我们先清空 redis 数据。

127.0.0.1:6379> FLUSHALL
OK
127.0.0.1:6379> KEYS *
(empty array)

然后执行下面的代码,完整代码关注公众号一个程序猿的异常发送关键字 LUA 获取。

try (Jedis jedis = jedisPool.getResource()) {
    // @formatter:off
    String script = " redis.call('SET', 'success','100') " 
                    // 模拟redis 出现异常信息,-1索引在bitmap不存在会报错
                    + " redis.call('SETBIT', 'bit_test','-1','1')  "
                    + " redis.call('SET', 'fail','100') " 
                    + " return 1";
    // @formatter:on
    Object eval = jedis.eval(script);
    System.out.println(eval);
}

执行完成后查看 redis 内的数据:

127.0.0.1:6379> KEYS *
1) "success"
127.0.0.1:6379> get success
"100"

结论:使用lua脚本可以在执行一串redis命令时,实现一定原子性(lua脚本中多条指令执行过程中不会被插入新的指令), 但是并不能在命令执行出错时回滚之前的结果

转载自:https://juejin.cn/post/7088963946534666271
评论
请登录