Lua调用Redis

Lua脚本调用Redis

Posted by Bug1024 on March 14, 2017

Redis 从2.6版本起开始支持 Lua 脚本

非脚本方式实现的访问限速

    private boolean accessLimit(String ip, int limit, int time, Jedis jedis) {
        boolean result = true;

        String key = "rate.limit:" + ip;
        if (jedis.exists(key)) {
            long afterValue = jedis.incr(key);
            if (afterValue > limit) {
                result = false;
            }
        } else {
            Transaction transaction = jedis.multi();
            transaction.incr(key);
            transaction.expire(key, time);
            transaction.exec();
        }
        return result;
    }

使用Lua脚本实现的访问限速

Lua

    local key = "rate.limit:" .. KEYS[1]
    local limit = tonumber(ARGV[1])
    local expire_time = ARGV[2]

    local is_exists = redis.call("EXISTS", key)
    if is_exists == 1 then
        if redis.call("INCR", key) > limit then
            return 0
        else
            return 1
        end
    else
        redis.call("SET", key, 1)
        redis.call("EXPIRE", key, expire_time)
        return 1
    end

Java

    private boolean accessLimit(String ip, int limit, int timeout, Jedis connection) throws IOException {
        List<String> keys = Collections.singletonList(ip);
        List<String> argv = Arrays.asList(String.valueOf(limit), String.valueOf(timeout));
        return 1 == (long) connection.eval(loadScriptString("script.lua"), keys, argv);
    }

    // 加载Lua代码
    private String loadScriptString(String fileName) throws IOException {
        Reader reader = new InputStreamReader(Client.class.getClassLoader().getResourceAsStream(fileName));
        return CharStreams.toString(reader);
    }

Lua脚本方式优势

  • 减少网络开销: 不使用 Lua 的代码需要向 Redis 发送多次请求, 而脚本只需一次即可, 减少网络传输
  • 原子操作: Redis 将整个脚本作为一个原子执行, 无需担心并发, 也就无需事务
  • 复用: 脚本会永久保存 Redis 中, 其他客户端可继续使用