基于信号量和令牌桶算法的限流

2018-08-16  本文已影响371人  fantuanjiaozi

限流的三种算法

限流主要有三种算法:信号量、漏桶算法和令牌桶算法。

信号量限制的是并发、资源。令牌桶限制的是QPS。

信号量

Semaphore是一个计数信号量。常用于限制获取某资源的线程数量,可基于java的concurrent并发包实现。
通过acquire()方法获取许可,该方法会阻塞,直到获取许可为止。
通过release()方法释放许可。

基于java的concurrent的实现


@RestController
@RequestMapping("/semaphore")
public class SemaphoreController {
    //初始化信号量=5,true-公平信号量FIFO。
    private Semaphore semaphore = new Semaphore(5, true);

    @RequestMapping("/acquire/{par1}")
    public String acquire(@PathVariable String par1){
        try {
            semaphore.acquire(1);//获取一个许可
            //do sth
            return par1+"获取一个许可";
        }catch (InterruptedException e){
            e.printStackTrace();
            return "获取许可失败";
        }catch (Exception e1){
            return "获取许可失败";
        }finally {
            semaphore.release(1);//处理完后,释放一个许可
        }
    }
}

漏桶算法

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

漏桶.jpg

令牌桶算法

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

令牌桶.jpg

令牌桶算法比漏桶算法更加优雅,适合秒杀等场景。本示例主要描述基于google的guava令牌桶算法的实现。

pom坐标,建议使用16.0及以上版本

        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>25.0-jre</version>
        </dependency>

限流实现

@RestController
@RequestMapping("/ratelimiter")
public class RateLimiterController {

    private RateLimiter rateLimiter = RateLimiter.create(0.5);//每秒发放0.5个令牌。注意:本初始化声明不要放在方法内

    //非阻塞机制
    @RequestMapping("/nonblock")
    public String rateLimiterNonBlock() {
        //如果令牌桶内有令牌,则直接发放;如果能在1000ms内能够获取一个令牌,则等待到时后发放;如果在1000ms内无法获取令牌,则直接返回false,无需等待。
        if (rateLimiter.tryAcquire(1000, TimeUnit.MILLISECONDS)) {
            //do sth
            return "成功获取令牌";
        } else {
            // do other
            return "获取失败";
        }
    }

    //阻塞机制
    @RequestMapping("/block")
    public String reateLimiterBlock(){
        //获取一个令牌,该方法会被阻塞知道获取令牌
        double waitTime = rateLimiter.acquire();
        //do sth
        return "等待"+waitTime+"秒后,成功获取令牌";
    }
}

建议使用非阻塞方式。

方法摘要

修饰符和类型 方法 描述
double acquire() 从RateLimiter获取一个许可,该方法会被阻塞直到获取到请求
double acquire(int permits) 从RateLimiter获取指定许可数,该方法会被阻塞直到获取到请求
static RateLimiter create(double permitsPerSecond) 根据指定的稳定吞吐率创建RateLimiter,这里的吞吐率是指每秒多少许可数(通常是指QPS,每秒多少查询)
static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit) 根据指定的稳定吞吐率和预热期来创建RateLimiter,这里的吞吐率是指每秒多少许可数(通常是指QPS,每秒多少个请求量),在这段预热时间内,RateLimiter每秒分配的许可数会平稳地增长直到预热期结束时达到其最大速率。(只要存在足够请求数来使其饱和)
double getRate() 返回RateLimiter 配置中的稳定速率,该速率单位是每秒多少许可数
void setRate(double permitsPerSecond) 更新RateLimite的稳定速率,参数permitsPerSecond 由构造RateLimiter的工厂方法提供。
boolean tryAcquire() 从RateLimiter获取许可,如果该许可可以在无延迟下的情况下立即获取得到的话
boolean tryAcquire(int permits) 从RateLimiter获取许可数,如果该许可数可以在无延迟下的情况下立即获取得到的话
boolean tryAcquire(int permits, long timeout, TimeUnit unit) 从RateLimiter 获取指定许可数如果该许可数可以在不超过timeout的时间内获取得到的话,或者如果无法在timeout 过期之前获取得到许可数的话,那么立即返回false (无需等待)
boolean tryAcquire(long timeout, TimeUnit unit) 从RateLimiter 获取许可如果该许可可以在不超过timeout的时间内获取得到的话,或者如果无法在timeout 过期之前获取得到许可的话,那么立即返回false(无需等待)

参考资料:http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/util/concurrent/RateLimiter.html

上一篇下一篇

猜你喜欢

热点阅读