zoukankan      html  css  js  c++  java
  • 第四部分-并发编程案例分析1:限流Guava RateLimiter

    1.并发案例,限流Guava RateLimiter

    Guava RateLimiter 如何解决高并发的限流问题

    guava中的工具类RateLimiter

    2.简单实用RateLimiter

    假设已有线程池,每秒只能处理两个任务,提交任务过快,可能导致系统不稳定,用到限流

    创建一个2个请求/秒的限流器。每秒最多允许2个请求通过限流器,也就是1个请求500ms

    伪代码

    
    //限流器流速:2个请求/秒
    RateLimiter limiter = 
      RateLimiter.create(2.0);
    //执行任务的线程池
    ExecutorService es = Executors
      .newFixedThreadPool(1);
    //记录上一次执行时间
    prev = System.nanoTime();
    //测试执行20次
    for (int i=0; i<20; i++){
      //限流器限流
      limiter.acquire();
      //提交任务异步执行
      es.execute(()->{
        long cur=System.nanoTime();
        //打印时间间隔:毫秒
        System.out.println(
          (cur-prev)/1000_000);
        prev = cur;
      });
    }
    
    输出结果:
    ...
    500
    499
    499
    500
    499
    

    3.限流算法:令牌桶

    Guava的RateLimiter使用的是令牌桶算法,核心就是通过限流器的话,必须拿到令牌。
    我们限制发放令牌的速率,就能控制流速。

    令牌桶算法说明:
    1.令牌以固定的速率添加到令牌桶中,假设速率是2/秒,则令牌每1/2秒会添加一个。
    2.假设令牌桶容量是b,如果令牌桶已满,新的令牌会被丢弃。
    3.请求能通过限流的前提是令牌桶中有令牌

    令牌桶容量b是burst的简写,允许的最大突发流量。比如b=10,令牌桶中令牌已满,则限流器允许10个请求同时通过限流器,当然只是突发流量,这10个请求带走10个令牌,后续流量只能按照速率2/秒通过限流器

    原理知道了?如何实现呢?
    最简单的生产者,消费者模式就可以支持。生产者线程定时向阻塞队列中添加令牌,试图通过限流器的线程则作为消费者线程,只有从阻塞队列中取到令牌,才会运行通过限流器

    有什么问题没?
    并发量不大时,没什么问题。但高并发场景,系统压力到极限,定时器的精度会误差非常大。定时器本身会创建调度线程,对系统性能产生影响

    4.Guava的Limiter是如何基于令牌桶算法实现限流器呢

    核心:记录并动态计算下一令牌发放的时间

    假设桶容量b=1,限流速率r=1/秒,如果桶中没有令牌,下一个令牌发送时间第三妙,第二秒有一个线程T1请求令牌,如何处理?
    image

    显然需要等待1秒,因未等1秒后就能拿到令牌。此时令牌产生时间往后顺延1秒,第三秒发的令牌被线程T1预占了
    image

    在T1预占了第三秒的令牌后,又有线程T2请求令牌
    image
    下一个令牌产生时间是第4秒,T2需要等待2秒才能获取令牌,T2预占了第4秒的令牌,令牌产生时间顺延1秒,需要第5秒再产生令牌
    image

    T1和T2都是在下一令牌产生时间之前请求令牌

    如果线程T3在下一令牌产生时间(第5秒)之后,请求令牌如何呢
    image
    线程T3可以直接拿到令牌,无需等待。因为brust=1,所以第6,7秒产生的令牌就直接丢弃了。
    image

    5.guava的限流逻辑伪代码化

    
    
    class SimpleLimiter {
      //当前令牌桶中的令牌数量
      long storedPermits = 0;
      //令牌桶的容量
      long maxPermits = 3;
      //下一令牌产生时间
      long next = System.nanoTime();
      //发放令牌间隔:纳秒
      long interval = 1000_000_000;
      
      //请求时间在下一令牌产生时间之后,则
      // 1.重新计算令牌桶中的令牌数
      // 2.将下一个令牌发放时间重置为当前时间
      void resync(long now) {
        if (now > next) {
          //新产生的令牌数
          long newPermits=(now-next)/interval;
          //新令牌增加到令牌桶
          storedPermits=min(maxPermits, 
            storedPermits + newPermits);
          //将下一个令牌发放时间重置为当前时间
          next = now;
        }
      }
      //预占令牌,返回能够获取令牌的时间
      synchronized long reserve(long now){
        resync(now);
        //能够获取令牌的时间
        long at = next;
        //令牌桶中能提供的令牌
        long fb=min(1, storedPermits);
        //令牌净需求:首先减掉令牌桶中的令牌
        long nr = 1 - fb;
        //重新计算下一令牌产生时间
        next = next + nr*interval;
        //重新计算令牌桶中的令牌
        this.storedPermits -= fb;
        return at;
      }
      //申请令牌
      void acquire() {
        //申请令牌时的时间
        long now = System.nanoTime();
        //预占令牌
        long at=reserve(now);
        long waitTime=max(at-now, 0);
        //按照条件等待
        if(waitTime > 0) {
          try {
            TimeUnit.NANOSECONDS
              .sleep(waitTime);
          }catch(InterruptedException e){
            e.printStackTrace();
          }
        }
      }
    }
    

    6.总结

    令牌桶:定时向令牌桶发送令牌,请求能够从令牌桶中拿到令牌,才能通过限流
    漏桶算法:请求像水一样注入漏桶,漏桶会按照一定的速率自动将水滤掉,只有漏桶留能注水的时候,请求才能通过限流器

    原创:做时间的朋友
  • 相关阅读:
    hdu 2680(最短路)
    hdu 1548(最短路)
    hdu 1596(最短路变形)
    hdu 1546(dijkstra)
    hdu 3790(SPFA)
    hdu 2544(SPFA)
    CodeForces 597B Restaurant
    CodeForces 597A Divisibility
    CodeForces 598E Chocolate Bar
    CodeForces 598D Igor In the Museum
  • 原文地址:https://www.cnblogs.com/PythonOrg/p/14893964.html
Copyright © 2011-2022 走看看