上一篇文章微服务中常用的限流算法(一)中我们介绍了滑动窗口算法和滚动窗口算法和具体的实现代码,本篇文章我们介绍漏桶限流算法和令牌桶限流算法。

漏桶限流算法

漏桶限流算法是模拟水流过一个有漏洞的桶进而限流的思路。

微服务 限流 微服务限流算法_令牌桶算法

水龙头的水先流入漏桶,再通过漏桶底部的孔流出。如果流入的水量太大,底部的孔来不及流出,就会导致水桶太满溢出去。限流器利用漏桶的这个原理设计漏桶限流算法,用户请求先流入到一个特定大小的漏桶中,系统以特定的速率从漏桶中获取请求并处理。如果用户请求超过限流,就会导致漏桶被请求数据填满,请求溢出,返回 503 响应。所以漏桶算法不仅可以限流,当流量超过限制的时候回拒绝处理,直接返回503响应,还能控制请求处理的速度,漏桶限流算法示意图如下:

微服务 限流 微服务限流算法_微服务 限流_02

在实现上,根据请求处理的事件间隔,例如:限流每秒10个请求,那么两个请求处理的时间间隔必须>=100ms,每个用户请求到达限流器后,根据当前最近一个请求处理的时间和最色的请求线程数目,计算当前线程sleep的时间,每个请求线程sleep的时间不同,最后就可以可以实现每隔100ms唤醒一个请求线程处理请求,从而达到漏桶限流的效果。漏桶限流限流算法的实现代码如下(父类定义在上一篇文章中):

public class LeakBucket extends AbstractLimitAlgo {

    private int interval;
    private int blockTask = 0;

    public LeakBucket(int limit,int interval) {
        super(limit);
        this.interval = interval;
    }

    @Override
    public boolean tryAcquire() {
        if(getElapseTimeFromLast() >= interval && blockTask <= 0) {
            lastTimeStamp = System.currentTimeMillis();
            return true;
        }

        if(blockTask > limit) {
            return false;
        }

        blockTask++;
        long sleep = interval * (++blockTask) - getElapseTimeFromLast();
        try {
            Thread.sleep(sleep);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        lastTimeStamp = System.currentTimeMillis();
        blockTask--;
        return true;
    }
}

细心的老铁会发现,漏桶算法的限流策略,只能使请求按照一定的时间间隔处理,即使当前资源比较空闲,漏桶也只能慢慢的一个一个的处理,这其实不太符合人们的期望,因为这样就是在浪费计算资源。上述漏桶算法实现代码,存在一定的线程安全问题,如果要保证线程安全,需要增加一些额外的代码,反而使算法实现变得更加复杂,本文重点说明算法思路,对于线程安全问题,读者可以自行在本地实现。

令牌桶限流算法

令牌桶是另一种桶限流算法,模拟一个特定大小的桶,然后向桶中以特定的速度放入令牌(token),请求到达后,必须从桶中取出一个令牌才能继续处理。如果桶中已经没有令牌了,那么当前请求就被限流,返回 503 响应。

微服务 限流 微服务限流算法_限流_03

上面的算法描述似乎需要有一个专门线程生成令牌,还需要一个数据结构模拟桶。实际上,令牌桶的实现,只需要在请求获取令牌的时候,通过时间计算,就可以算出令牌桶中的总令牌数,实现代码如下:

public class TokenBucket extends AbstractLimitAlgo {

    private int limit;
    private int count = 0;
    private long lastTimeStamp ;

    // 流速控制
    private long interval;

    public TokenBucket(int limit, long interval) {
        super(limit);
        this.interval = interval;
    }

    @Override
    public boolean tryAcquire() {
        if(count >= 1) {
            count--;
            return true;
        }
        count= Math.min(limit,count + Long.valueOf(getElapseTimeFromLast() / interval).intValue());
        if(count >= 1) {
            count--;
            return true;
        }
        return false;
    }

}

令牌桶限流算法,可以根据桶内剩余令牌的多少,来影响请求处理的速度,例如一段时间内,没有用户请求,使令牌桶内积攒一部分令牌 ,那么此时 如果来了一波流量的话,那么可以在短时间内,获取到桶内所有令牌,是这波流量同时被处理,也就是说,使用令牌桶限流算法,并不会限制请求处理的速度,后端服务处理请求的峰值为令牌桶的容量。所以在设置令牌桶容量时,可以参考后端服务可以处理流量的峰值。同样令牌桶算法的实现代码也存在线程安全问题,读者可自行解决线程安全问题。

令牌桶限流算法综合效果比较好,能在最大程度利用系统资源处理请求的基础上,实现限流的目标。

总结

小编用了两篇文章 微服务中常用的限流算法(一) 和 微服务中常用的限流算法(二),讨论了在微服务中常用的限流算法和对应的代码实现,除了上文中提到的四种限流算法外,你还只知道哪些限流算法吗?