zoukankan      html  css  js  c++  java
  • RateLimiter

    RateLimiter是Guava的concurrent包下的一个用于限制访问频率的类.

    限流算法

    常用的更平滑的限流算法有两种:漏桶算法和令牌桶算法.

    很多传统的服务提供商如华为中兴都有类似的专利,参考: http://www.google.com/patents/CN1536815A?cl=zh

    漏桶算法

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

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

    double rate; // leak rate in calls/s
    double burst; // bucket size in calls
    long refreshTime; // time for last water refresh
    double water; // water count at refreshTime
    refreshWater() {
        long now = getTimeOfDay();
        //水随着时间流逝,不断流走,最多就流干到0.
        water = max(0, water- (now - refreshTime)*rate);
        refreshTime = now;
    }
    bool permissionGranted() {
        refreshWater();
        if (water < burst) { // 水桶还没满,继续加1
            water ++;
            return true;
        } else {
            return false;
        }
    }

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

    令牌桶算法

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

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

    RateLimiter简介

    Google开源工具包Guava提供了限流工具类RateLimiter,该类基于令牌桶算法(Token Bucket)来完成限流,非常易于使用.RateLimiter经常用于限制对一些物理资源或者逻辑资源的访问速率.它支持两种获取permits接口,一种是如果拿不到立刻返回false,一种会阻塞等待一段时间看能不能拿到.

    RateLimiter和Java中的信号量(java.util.concurrent.Semaphore)类似,Semaphore通常用于限制并发量.

    源码注释中的一个例子,比如我们有很多任务需要执行,但是我们不希望每秒超过两个任务执行,那么我们就可以使用RateLimiter:

    final RateLimiter rateLimiter = RateLimiter.create(2.0);
    void submitTasks(List<Runnable> tasks, Executor executor) {
        for (Runnable task : tasks) {
            rateLimiter.acquire(); // may wait
            executor.execute(task);
        }
    }

    另外一个例子,假如我们会产生一个数据流,然后我们想以每秒5kb的速度发送出去.我们可以每获取一个令牌(permit)就发送一个byte的数据,这样我们就可以通过一个每秒5000个令牌的RateLimiter来实现:

    final RateLimiter rateLimiter = RateLimiter.create(5000.0);
    void submitPacket(byte[] packet) {
        rateLimiter.acquire(packet.length);
        networkService.send(packet);
    }

    另外,我们也可以使用非阻塞的形式达到降级运行的目的,即使用非阻塞的tryAcquire()方法:

    if(limiter.tryAcquire()) { //未请求到limiter则立即返回false
        doSomething();
    }else{
        doSomethingElse();
    }

    RateLimiter主要接口

    RateLimiter其实是一个abstract类,但是它提供了几个static方法用于创建RateLimiter:

    /**
    * 创建一个稳定输出令牌的RateLimiter,保证了平均每秒不超过permitsPerSecond个请求
    * 当请求到来的速度超过了permitsPerSecond,保证每秒只处理permitsPerSecond个请求
    * 当这个RateLimiter使用不足(即请求到来速度小于permitsPerSecond),会囤积最多permitsPerSecond个请求
    */
    public static RateLimiter create(double permitsPerSecond);
    /**
    * 创建一个稳定输出令牌的RateLimiter,保证了平均每秒不超过permitsPerSecond个请求
    * 还包含一个热身期(warmup period),热身期内,RateLimiter会平滑的将其释放令牌的速率加大,直到起达到最大速率
    * 同样,如果RateLimiter在热身期没有足够的请求(unused),则起速率会逐渐降低到冷却状态
    *
    * 设计这个的意图是为了满足那种资源提供方需要热身时间,而不是每次访问都能提供稳定速率的服务的情况(比如带缓存服务,需要定期刷新缓存的)
    * 参数warmupPeriod和unit决定了其从冷却状态到达最大速率的时间
    */
    public static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit);

    提供了两个获取令牌的方法,不带参数表示获取一个令牌.如果没有令牌则一直等待,返回等待的时间(单位为秒),没有被限流则直接返回0.0:

    public double acquire();
    public double acquire(int permits);

    尝试获取令牌,分为待超时时间和不带超时时间两种:

    public boolean tryAcquire();
    //尝试获取一个令牌,立即返回
    public boolean tryAcquire(int permits);
    public boolean tryAcquire(long timeout, TimeUnit unit);
    //尝试获取permits个令牌,带超时时间
    public boolean tryAcquire(int permits, long timeout, TimeUnit unit);
  • 相关阅读:
    可能会搞砸你的面试:你知道一个TCP连接上能发起多少个HTTP请求吗?
    iOS笔记055
    iOS笔记053- Quartz2D-练习
    iOS笔记052- Quartz2D-绘图
    IOS笔记051-手势使用
    IOS笔记050-事件处理
    IOS笔记049-UITabBarController
    IOS笔记048-数据存储
    IOS笔记047-代理传值和block传值
    IOS笔记046-UIApplication/导航控制器
  • 原文地址:https://www.cnblogs.com/winner-0715/p/9611273.html
Copyright © 2011-2022 走看看