Lua 脚本:

  Lua/ˈluə/是一种轻量级脚本语言,它是用 C 语言编写的,跟数据的存储过程有点类似。 使用 Lua 脚本来执行 Redis 命令的好处:

  1. 一次发送多个命令,减少网络开销。
  2. Redis 会将整个脚本作为一个整体执行,不会被其他请求打断,保持原子性。
  3. 对于复杂的组合命令,我们可以放在文件中,可以实现程序之间的命令集复用。

在Redis Cli 中 调用 Lua 脚本:

  使用 eval /ɪ'væl/ 方法,语法格式:

127.0.0.1:6379> eval lua-script key-num [key1 key2 key3 ....] [value1 value2 value3 ....]
eval 代表执行 Lua 语言的命令。 lua-script 代表 Lua 语言脚本内容。 key-num 表示参数中有多少个 key,需要注意的是 Redis 中 key 是从 1 开始的,如果没有 key 的参数,那么写 0。 [key1 key2 key3…]是 key 作为参数传递给 Lua 语言,也可以不填,但是需要和 key-num 的个数对应起来。 [value1 value2 value3 ….]这些参数传递给 Lua 语言,它们是可填可不填的。

  例如:

Redis (六) Lua 脚本

在 Lua 脚本 中调用 Redis 命令:

  使用 redis.call(command, key [param1, param2…])进行操作。语法格式:

redis> eval "redis.call('set',KEYS[1],ARGV[1])" 1 lua-key lua-value
command 是命令,包括 set、get、del 等。
key 是被操作的键。
param1,param2…代表给 key 的参数。

  注意跟 Java 不一样,定义只有形参,调用只有实参。Lua 是在调用时用 key 表示形参,argv 表示参数值(实参)。在 Redis 中调用 Lua 脚本执行 Redis 命令

Redis (六) Lua 脚本

  以上命令等价于 set name wuzz。

在 Redis 中调用 Lua 脚本 文件中的命令 ,

  操作 Redis创建 Lua 脚本文件:

//创建文件
cd /mysoft/redis-4.0.8/src
vim wuzz.lua
//Lua 脚本内容,先设置,再取值:
redis.call('set','wuzz',hello)
return redis.call('get',wuzz)
//在 Redis 客户端中调用 Lua 脚本 0 位参数个数
cd /mysoft/redis-4.0.8/src
redis-cli --eval wuzz.lua 0

  案例 :对 IP 进行限流

  需求:在 X 秒内只能访问 Y 次。

  设计思路:用 key 记录 IP,用 value 记录访问次数。拿到 IP 以后,对 IP+1。如果是第一次访问,对 key 设置过期时间(参数 1)。否则判断次数,超过限定的次数(参数 2),返回 0。如果没有超过次数则返回 1。超过时间,key 过期之后,可以再次访问。KEY[1]是 IP, ARGV[1]是过期时间 X,ARGV[2]是限制访问的次数 Y。

-- ip_limit.lua
-- IP 限流,对某个 IP 频率进行限制 ,5 秒钟访问 2 次
local num=redis.call('incr',KEYS[1])
if tonumber(num)==1 then
    redis.call('expire',KEYS[1],ARGV[1])
    return 1
elseif tonumber(num)>tonumber(ARGV[2]) then
    return 0
else
    return 1
end

  5 秒钟内限制访问 2 次,调用测试(连续调用 2 次):

./redis-cli  -h 127.0.0.1 -p 6379 -a wuzhenzhao   --eval "ip_limit.lua" app:ip:limit:192.168.8.111 , 5 2

Jedis 调用 lua 脚本相关操作:

public static void main(String[] args) {
        Jedis jedis = getJedisUtil();
        for(int i=0; i<5; i++){
            limit();
        }
}

/**
* 5秒内限制访问2次
*/
public static void limit(){
        Jedis jedis = getJedisUtil();
        // 只在第一次对key设置过期时间
        String lua = "local num = redis.call('incr', KEYS[1])\n" +
                "if tonumber(num) == 1 then\n" +
                "\tredis.call('expire', KEYS[1], ARGV[1])\n" +
                "\treturn 1\n" +
                "elseif tonumber(num) > tonumber(ARGV[2]) then\n" +
                "\treturn 0\n" +
                "else \n" +
                "\treturn 1\n" +
                "end\n";
        Object result = jedis.evalsha(jedis.scriptLoad(lua), Arrays.asList("localhost"), Arrays.asList("5", "2"));
        System.out.println(result);
}

private static Jedis getJedisUtil() {
        String ip = ResourceUtil.getKey("redis.host");
        int port = Integer.valueOf(ResourceUtil.getKey("redis.port"));
        String password = ResourceUtil.getKey("redis.password");
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        JedisPool pool = new JedisPool(jedisPoolConfig, ip, port, 10000, password);
        return pool.getResource();
}

缓存 Lua 脚本:

  为什么要缓存?在脚本比较长的情况下,如果每次调用脚本都需要把整个脚本传给 Redis 服务端,会产生比较大的网络开销。为了解决这个问题,Redis 提供了 EVALSHA 命令,允许开发者通过脚本内容的 SHA1 摘要来执行脚本。

  Redis 在执行 script load 命令时会计算脚本的 SHA1 摘要并记录在脚本缓存中,执行 EVALSHA 命令时 Redis 会根据提供的摘要从脚本缓存中查找对应的脚本内容,如果找到了则执行脚本,否则会返回错误:"NOSCRIPT No matching script. Please useEVAL."

Redis (六) Lua 脚本

  脚本超时:

  Redis 的指令执行本身是单线程的,这个线程还要执行客户端的 Lua 脚本,如果 Lua脚本执行超时或者陷入了死循环,是不是没有办法为客户端提供服务了呢?

  使用  eval 'while(true) do end' 0 来模拟死循环,为了防止某个脚本执行时间过长导致 Redis 无法提供服务,Redis 提供了 lua-time-limit 参数限制脚本的最长运行时间,默认为 5 秒钟。

  lua-time-limit 5000(redis.conf 配置文件中) 当脚本运行时间超过这一限制后,Redis 将开始接受其他命令但不会执行(以确保脚本的原子性,因为此时脚本并没有被终止),而是会返回“BUSY”错误。Redis 提供了一个 script kill 的命令来中止脚本的执行。新开一个客户端:script kill

  如果当前执行的 Lua 脚本对 Redis 的数据进行了修改(SET、DEL 等),那么通过 script kill 命令是不能终止脚本运行的。因为要保证脚本运行的原子性,如果脚本执行了一部分终止,那就违背了脚本原子性的要求。最终要保证脚本要么都执行,要么都不执行。遇到这种情况,只能通过 shutdown nosave 命令来强行终止 redis。shutdown nosave 和 shutdown 的区别在于 shutdown nosave 不会进行持久化操作,意味着发生在上一次快照后的数据库修改都会丢失。

  如果我们有一些特殊的需求,可以用 Lua 来实现。

相关文章:

  • 2021-04-23
  • 2021-08-30
  • 2021-08-23
  • 2021-08-11
  • 2021-12-10
  • 2021-11-14
  • 2022-12-23
猜你喜欢
  • 2021-11-29
  • 2021-05-28
  • 2021-07-13
  • 2021-12-29
  • 2022-12-23
相关资源
相似解决方案