基于用户的API限流策略

应用场景
API接口的流量控制策略:缓存、降级、限流。限流可以认为服务降级的一种,限流就是限制系统的输入和输出流量已达到保护系统的目的。限流策略虽然降低了服务接口的访问频率和并发量,却换取服务接口和业务应用系统的高可用。

常用的限流策略:

Nginx 限流

按照一定的规则如帐号、IP、系统间调用逻辑等在 Nginx 层面做限流

业务系统限流

客户端限流

服务端限流

数据库限流

常用限流算法
计数器

计数器是最简单粗暴的算法,通过直接统计每个时间的请求数目来判断是否需要拒绝。

比如某个服务最多只能每秒钟处理 100 个请求。我们可以设置一个 1 秒钟的滑动窗口,窗口中有 10 个格子,每个格子 100 毫秒,每 100 毫秒移动一次,每次移动都需要记录当前服务请求的次数。内存中需要保存 10 次的次数。可以用数据结构 LinkedList 来实现。格子每次移动的时候判断一次,当前访问次数和 LinkedList 中最后一个相差是否超过 100 ,如果超过就需要限流了。

很明显,当滑动窗口的格子划分的越多,那么滑动窗口的滚动就越平滑,限流的统计就会越精确。

计数器的实现简单,但是是平均分配1秒钟的请求,然而实际情况中的请求往往是动态的,流量不平滑的。

漏桶

漏桶( Leaky Bucket )算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水(接口有响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求,可以看出漏桶算法能强行限制数据的传输速率。

可见这里有两个变量,一个是桶的大小,支持流量突发增多时可以存多少的水( burst ),另一个是水桶漏洞的大小( rate )。

因为漏桶的漏出速率是固定的参数,所以,即使网络中不存在资源冲突(没有发生拥塞),漏桶算法也不能使流突发( burst )到端口速率.因此,漏桶算法对于存在突发特性的流量来说缺乏效率.

令牌桶

令牌桶算法( Token Bucket )和 Leaky Bucket 效果一样但方向相反的算法,更加容易理解.随着时间流逝,系统会按恒定 1/QPS 时间间隔(如果 QPS=100 ,则间隔是 10ms )往桶里加入 Token(想象和漏洞漏水相反,有个水龙头在不断的加水),如果桶已经满了就不再加了.新请求来临时,会各自拿走一个 Token ,如果没有 Token 可拿了就阻塞或者拒绝服务.

令牌桶的另外一个好处是可以方便的改变速度. 一旦需要提高速率,则按需提高放入桶中的令牌的速率. 一般会定时(比如100毫秒)往桶中增加一定数量的令牌, 有些变种算法则实时的计算应该增加的令牌的数量.

基于Redis的令牌桶算法限流策略实现
策略

因为出现了某些客户突然加大流量的情况,为了避免抢占其他用户的资源,所以设计了基于用户(限制单个用户的最大请求数)的令牌桶策略。

JAVA 实现

/**
 * 获取令牌
 *
 * @param key            令牌类别标识(每个用户不同)
 * @param permits        请求的令牌数量
 * @param currMillSecond 当前毫秒数
 * @return 是否能请求到令牌
 */
public boolean acquire(String key, Integer permits, long currMillSecond) {
    try (Jedis jedis = JedisPoolUtil.getJedisPool().getResource()) {
        //针对新用户创建令牌桶
        if (!jedis.exists(key)) {
            jedis.hset(key, "last_mill_second", String.valueOf(currMillSecond));
            jedis.hset(key, "curr_permits", "0");
            jedis.hset(key, "max_permits", "500");
            jedis.hset(key, "rate", "400");
            return true;
        }
        //获取令牌桶信息,上一个令牌时间,当前可用令牌数,最大令牌数,令牌消耗速率
        List<String> limitInfo = jedis.hmget(key, "last_mill_second", "curr_permits", "max_permits", "rate");
        long lastMillSecond = Long.parseLong(limitInfo.get(0));
        Integer currPermits = Integer.valueOf(limitInfo.get(1));
        Integer maxPermits = Integer.valueOf(limitInfo.get(2));
        Double rate = Double.valueOf(limitInfo.get(3));
        //向桶里面添加令牌
        Double reversePermitsDouble = ((currMillSecond - lastMillSecond) / 1000) * rate;
        Integer reversePermits = reversePermitsDouble.intValue();
        Integer expectCurrPermits = reversePermits + currPermits;
        Integer localCurrPermits = Math.min(expectCurrPermits, maxPermits);
        //添加令牌之后更新时间
        if (reversePermits > 0) {
            jedis.hset(key, "last_mill_second", String.valueOf(currMillSecond));
        }
        //判断桶里面剩余的令牌数目
        if (localCurrPermits - permits >= 0) {
            jedis.hset(key, "curr_permits", String.valueOf(localCurrPermits - permits));
            return true;
        } else {
            jedis.hset(key, "curr_permits", String.valueOf(localCurrPermits));
            return false;
        }
    } catch (Exception e) {
        return false;
    }
}

使用方式:

if (!rateLimiterService.acquire("limiter:" + uid, 1, System.currentTimeMillis())) {
        throw new BusinessException(ExceptionType.TOO_BUSY);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值