redis+lua脚本实现接口限流

写在前面

在多线程的情况下对一个接口进行访问,如果访问次数过大,且没有缓存存在的情况下大量的请求打到数据库可能会存在数据库宕机,从而造成服务的不可用性。往往我们需要对其进行限流操作用来保证服务的高可用性,以下介绍下redis限流如何使用。

lua脚本

Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。Lua 本身并没有提供对于原子性的直接支持,它只是一种脚本语言,通常是嵌入到其他宿主程序中运行,比如Redis。 在Redis中,执行Lua脚本的原子性是指:整个Lua脚本在执行期间,不会被其他客户端的命令打断。

特性

  • 轻量级:它用标准C语言编写并以源代码形式开放,编译后仅仅一百余K,可以很方便的嵌入别的程序里。
  • 可扩展:Lua提供了非常易于使用的扩展接口和机制:由宿主语言(通常是C或C++)提供这些功能,Lua可以使用它们,就像是本来就内置的功能一样。

lua语法教程

redis使用Lua脚本

Redis 脚本使用 Lua 解释器来执行脚本。 Redis 2.6 版本通过内嵌支持 Lua 环境。执行脚本的常用命令为 EVAL。redis Eval 命令基本语法如下:

java 复制代码
redis 127.0.0.1:6379> EVAL script numkeys key [key ...] arg [arg ...] 

参数说明:

  • script: 参数是一段 Lua 5.1 脚本程序。脚本不必(也不应该)定义为一个 Lua 函数。
  • numkeys: 用于指定键名参数的个数。
  • key [key ...]: 从 EVAL 的第三个参数开始算起,表示在脚本中所用到的那些 Redis 键(key),这些键名参数可以在 Lua 中通过全局变量 KEYS 数组,用 1 为基址的形式访问( KEYS[1] , KEYS[2] ,以此类推)。
  • arg [arg ...]: 附加参数,在 Lua 中通过全局变量 ARGV 数组访问,访问的形式和 KEYS 变量类似( ARGV[1] 、 ARGV[2] ,诸如此类)。

Jedis实现接口限流

首先,我们定义一个lua脚本:

lua 复制代码
local key = KEYS[1];

local times = ARGV[1];

local expire = ARGV[2];

local afterval = redis.call('incr',key);
if afterval ==1 then
    redis.call('expire',key,tonumber(expire) )
    return 1;
end;

if afterval > tonumber(times) then
    return 0;
end

return 1;

这个脚本首先定义了三个成员变量用来获取方法中传入的值,redis.call()方法是redis的命令脚本执行,即redis执行incr操作,对key中存储的key值加1操作,如果afterval值等于1时,执行redis的expire,设置key的过期时间,tonumber是将参数值转换为数值,返回,如果加一后的值大于我们传入的规定值时,返回0,进行限流。

java代码实现限流

java 复制代码
    public boolean acquire(String limitKey, int limit, int expire) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setResultType(Long.class);
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("rateLimiter.lua")));
        Long result = (Long) jedis.eval(redisScript.getScriptAsString(), 1, limitKey, String.valueOf(limit), String.valueOf(expire));
        if (result == 0){
            return false;
        }else {
        return true;
        }
    }

controller层调用

java 复制代码
     if (isAcquire.acquire("myKey",10,60)){
          // 接口放行
           return "success";
       }else {
         // 接口拒绝
           return "err";
       }

这个方法是传入一个你的key,这个key下每一分钟只能请求10次,如果超出10次,进行限流操作,等到上次请求接口的时间超出1分钟后才可以进行放行操作。

END