限流-redis+Lua

场景:增加API接口请求的限流策略,针对每一个用户的每种类型的API请求做限流。

比如:同一用户,每秒钟只允许20次签约请求。当每秒请求超过20次时,会提示"客户请求签约接口次数超限"。

1、作为API调用方,就要对并发进行控制,以防出现无效请求。最常用的并发限流方案是借助redis/jedis。为了保证原子性,这里,我使用Redis+LUA脚本的方式来控制。
2、对于提供方来,当请求量超出设定的限流阈值,则直接返回错误码/错误提示,并终止对请求的处理。
而对于调用方来说呢,我们要做的是:当并发请求超出了限定阈值时,要延迟请求,而不是直接丢弃。

@Slf4j
@Component
public class RedisLimiter {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 达到限流时则等待,直到新的间隔。
     */
    public void limitWait(String key, int limitCount, int limitSecond) {
        boolean ok;//放行标志
        do {
            ok = limit(key, limitCount, limitSecond);//调用limit返回是否达到限流
            if (!ok) {
                Long ttl = redisTemplate.getExpire(key, TimeUnit.MILLISECONDS);
                if (null != ttl && ttl > 0) {
                    Thread.sleep(ttl);//睡眠到缓存过期
                }
            }
        } while (!ok);
    }
}
/**
 限流方法 true-放行;false-限流
 key:缓存的key,limitCount:key的最大值,limitSecond:key的过期时间
 */
public boolean limit(String key, int limitCount, int limitSecond) {
    List<String> keys = Collections.singletonList(key);
    String luaScript = buildLuaScript();
    RedisScript<Integer> redisScript = new DefaultRedisScript<Integer>(luaScript, Integer.class);
    Integer count = redisTemplate.execute(redisScript, keys, limitCount, limitSecond);
    if (count != null && count.intValue() <= limitCount) {//判断是否超过数量
        return true;//放行
    } else {
        return false;//限流
    }
}
/**
 * 编写redis Lua 限流脚本
 * 流程:如果超过指定数量就返回,没有超过就计数+1,返回当前的值。第一次进来的时候还要给缓存设置过期时间。
 *  c = redis.call('get',KEYS[1]) //从缓存中获取KEY
 *  if c and tonumber(c) > tonumber(ARGV[1]) then  return c end //超过限定的最大值就直接返回
 *  c = redis.call('incr',KEYS[1])    //没有超过计数器+1,返回当前c的值
 *  if tonumber(c) == 1 then redis.call('expire',KEYS[1],ARGV[2]) end
 *  return c;
 *  注:Incr命令将key中储存的数字值增一,返回当前key的数值。
 *  注:tonumber方法是Lua语法里面的函数,把参数转成10进制的数字
 *  注:KEYS[1] 表示key,ARGV[1]表示:value,ARGV[2]:超时时间
 */
public String buildLuaScript() {
    StringBuilder lua = new StringBuilder();
    lua.append("local c");
    lua.append("\nc = redis.call('get',KEYS[1])");
    lua.append("\nif c and tonumber(c) > tonumber(ARGV[1]) then");//实际调用次数超过阈值,则直接返回
    lua.append("\nreturn tostring(c);");
    lua.append("\nend");
    lua.append("\nc = redis.call('incr',KEYS[1])");// 执行计算器自加
    lua.append("\nif tonumber(c) == 1 then");
    lua.append("\nredis.call('expire',KEYS[1],ARGV[2])");//从第一次调用开始限流,设置对应键值的过期
    lua.append("\nend");
    lua.append("\nreturn tostring(c);");
    return lua.toString();
}

多个请求,通过频繁的调用下面的方法来控制速率

@Resource
RedisLimiter redisLimiter

redisLimiter.limitWait(key, 3, 1)

key表示redis的key,3表示最大请求次数,1表示过期时间1秒,1秒时间内最大请求次数是3次。
第一次调用时,缓存数据就是1,再低调用缓存计数就是2。。。第四次调用因为超过了3,所以需要等待缓存过期才会继续往下执行。

缺点:
这个限流有个弊端就是。
比如我们设置1秒只处理三个请求,加入0.1秒来了一个请求,0.9来了2个请求。
1.1秒来了2个请求,1.9秒来了1个请求,虽然第一秒处理了3个请求,第二个一秒处理了3个请求,但是0.9秒到1.1秒处理了4个请求,这就会有问题了。

原文链接:https://www.cnblogs.com/buguge/p/13477151.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值