zoukankan      html  css  js  c++  java
  • 使用Guava中RateLimiter进行限流

    使用Guava中RateLimiter进行限流

    Google 出的 Guava 是 Java 核心增强的库,应用非常广泛。

    限流场景

    最常见的秒杀场景多个用户在同时抢购一件或者多件商品,用户量过多可能会导致系统挂掉。还有就是大量的消息推送,服务商接口每秒能处理的短信发送量有限。

    总结一句话就是提供服务的接口,业务负载能力有限,为了防止过多请求涌入造成系统崩溃,我们应该如何进行流量控制?

    流量控制策略有:分流,降级,限流等。

    这里我们讨论限流策略,他的作用是限制请求访问频率,换取系统高可用,是比较保守方便的策略。

    常用的限流算法

    漏桶算法

    漏桶作为计量工具(The Leaky Bucket Algorithm as a Meter)时,可以用于流量整形(Traffic Shaping)和流量控制(TrafficPolicing),漏桶算法的描述如下:

    • 一个固定容量的漏桶,按照常量固定速率流出水滴;
    • 如果桶是空的,则不需流出水滴;
    • 可以以任意速率流入水滴到漏桶;

    如果流入水滴超出了桶的容量,则流入的水滴溢出了(被丢弃),而漏桶容量是不变的。

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

    image

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

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

    令牌桶算法

    令牌桶算法是一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌。令牌桶算法的描述如下:

    • 假设限制2r/s,则按照500毫秒的固定速率往桶中添加令牌;
    • 桶中最多存放b个令牌,当桶满时,新添加的令牌被丢弃或拒绝;
    • 当一个n个字节大小的数据包到达,将从桶中删除n个令牌,接着数据包被发送到网络上;
    • 如果桶中的令牌不足n个,则不会删除令牌,且该数据包将被限流(要么丢弃,要么缓冲区等待)。

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

    image

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

    Guava中RateLimiter类

    RateLimiter 从概念上来讲,速率限制器会在可配置的速率下分配许可证。如果必要的话,每个acquire() 会阻塞当前线程直到许可证可用后获取该许可证。一旦获取到许可证,不需要再释放许可证。

    RateLimiter经常用于限制对一些物理资源或者逻辑资源的访问速率。与Semaphore 相比,Semaphore 限制了并发访问的数量而不是使用速率。(注意尽管并发性和速率是紧密相关的,比如参考Little定律)

    通过设置许可证的速率来定义RateLimiter。在默认配置下,许可证会在固定的速率下被分配,速率单位是每秒多少个许可证。为了确保维护配置的速率,许可会被平稳地分配,许可之间的延迟会做调整。
    可能存在配置一个拥有预热期的RateLimiter 的情况,在这段时间内,每秒分配的许可数会稳定地增长直到达到稳定的速率。

    举例来说明如何使用RateLimiter,想象下我们需要处理一个任务列表,但我们不希望每秒的任务提交超过两个:

    //速率是每秒两个许可
    final RateLimiter rateLimiter = RateLimiter.create(2.0);
    
    void submitTasks(List tasks, Executor executor) {
        for (Runnable task : tasks) {
            rateLimiter.acquire(); // 也许需要等待
            executor.execute(task);
        }
    }
    

    再举另外一个例子,想象下我们制造了一个数据流,并希望以每秒5kb的速率处理它。可以通过要求每个字节代表一个许可,然后指定每秒5000个许可来完成:

    // 每秒5000个许可
    final RateLimiter rateLimiter = RateLimiter.create(5000.0); 
    
    void submitPacket(byte[] packet) {
        rateLimiter.acquire(packet.length);
        networkService.send(packet);
    }
    

    有一点很重要,那就是请求的许可数从来不会影响到请求本身的限制(调用acquire(1) 和调用acquire(1000) 将得到相同的限制效果,如果存在这样的调用的话),但会影响下一次请求的限制,也就是说,如果一个高开销的任务抵达一个空闲的RateLimiter,它会被马上许可,但是下一个请求会经历额外的限制,从而来偿付高开销任务。注意:RateLimiter 并不提供公平性的保证。

    • 方法摘要

    image

    代码示例

    • Test 1
    public static void main(String[] args) {
            RateLimiter rateLimiter = RateLimiter.create(3d);
    
            ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newCachedThreadPool();
            for (int i = 0; i < 10; i++) {
                final int no = i;
                executor.execute(() -> {
                    try {
                        // 获取令牌桶中的一个令牌,最多等待10秒
                        if (rateLimiter.tryAcquire(1, 10, TimeUnit.SECONDS)) {
                            try {
                                Thread.sleep(5000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
    
                            System.out.println("no:" + no + "	" + DateUtil.formatFullTime(LocalDateTime.now()));
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                });
            }
    
            executor.shutdown();
        }
        
        
        // 输出结果
        no:1	20190814110240
        no:5	20190814110240
        no:9	20190814110241
        no:2	20190814110241
        no:6	20190814110241
        no:0	20190814110242
        no:4	20190814110242
        no:8	20190814110242
        no:3	20190814110243
        no:7	20190814110243
    
    • Test 2
    public static void main(String[] args) {
        RateLimiter rateLimiter = RateLimiter.create(3d);
    
        ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final int no = i;
            executor.execute(() -> {
                try {
                    //获取许可
                    rateLimiter.acquire();
                    System.out.println("no:" + no + "	" + DateUtil.formatFullTime(LocalDateTime.now()));
    
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
    
        executor.shutdown();
    }
    
    • Test 3: Semaphore

    Semaphore和RateLimiter的区别

    Semaphore:信号量,直译很难理解。作用是限定只有抢到信号的线程才能执行,其他的都得等待!你可以设置N个信号,这样最多可以有N个线程同时执行。注意,其他的线程也在,只是挂起了。

    RateLimiter:这是guava的,直译是速率限制器。其作用是 限制一秒内只能有N个线程执行,超过了就只能等待下一秒。注意,N是double类型。

    ========================================

    Semaphore:从线程个数限流
    RateLimiter:从速率限流 目前常见的算法是漏桶算法和令牌算法

    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);
    
        ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final int no = i;
            executor.execute(() -> {
                try {
                    //获取许可
                    semaphore.acquire();
                    System.out.println("no:" + no + "	" + DateUtil.formatFullTime(LocalDateTime.now()));
    
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //访问完后,释放许可
                    semaphore.release();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
    
        executor.shutdown();
    }
    

    参考文档:

    http://ifeve.com/guava-ratelimiter/

    https://mp.weixin.qq.com/s/gxzB49wu1D-lVK6fDSBeDg

    https://blog.csdn.net/JIESA/article/details/50412027

    https://blog.csdn.net/boling_cavalry/article/details/75174486

    -------------已经触及底线 感谢您的阅读-------------
  • 相关阅读:
    PHP中无限分类、无限回复评论盖楼的实现方法,thinkphp5.0无限分类实例
    PHP中session详解
    使用thinkPHP做注册程序的实例
    虾米盒子系统开发APP
    angular 使用base64密码加密
    开发中遇到的两种表格文本长度处理,即长文本截断
    树组件使用文件夹图标
    angular实现指定DIV全屏
    JS调用浏览器打印机
    使用blob二进制流的方式下载后台文件
  • 原文地址:https://www.cnblogs.com/cnsyear/p/12777929.html
Copyright © 2011-2022 走看看