前言

RateLimiter是基于令牌桶算法实现的一个多线程限流器,它可以将请求均匀的进行处理,当然他并不是一个分布式限流器,只是对单机进行限流。它可以应用在定时拉取接口数据,

预防单机过大流量使用。

原理

首先先讲一下令牌桶的原理,每隔一段时间生产一个令牌放入桶里,请求在执行时需要拿到令牌才可以执行,如果拿不到令牌将等待令牌产生,一个生产者,多个消费者。

但是这样的令牌桶有一个问题,如果CPU负载过高,生产令牌的线程没有获取到时间片生产令牌,那么限制的流量将会比设定值更低。

可能是出于这个原因,guava并没有这样做,而是一个惰性生产令牌,每次请求令牌时,通过当前时间和下次产生令牌时间的差值计算出现在有多少个令牌,如果当前时间比发放时间大,会获得令牌,并且会生成令牌存储。如果令牌不够,则让线程sleep,并且将下次令牌产生时间更新成当前时间+sleep时间

sleep,并且将下次发放令牌的时间,设置成当前时间+线程sleep的时间。这样说,可能不是很清楚,看图。

这样做的好处是什么,如果获取令牌的线程抢不到cpu,只是这个线程的执行时间会晚,其他线程不会受到影响。

源码阅读

  1. public static void main(String[] args) {
  2. RateLimiter rateLimiter = RateLimiter.create(10);
  3. while (true) {
  4. long start = System.currentTimeMillis();
  5. rateLimiter.acquire();
  6. System.out.println(System.currentTimeMillis() - start);
  7. }
  8. }  

运行可以发现,上面的代码除了第一次输出的是0或者1,其他都接近100。下面先看一下RateLimiter.create做了哪些事情

  1. static RateLimiter create(double permitsPerSecond, SleepingStopwatch stopwatch) {
  2. //创建对象,并且赋值,permitsPerSecond这个是我们设置的qps,stopwatch这个相当于一个计时器,记录相对时间,类似于我上面图中的10ms,100ms等,下面传入的1.0就是一秒的意思,设置上速率就是一秒多少次
  3. RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0);
  4. rateLimiter.setRate(permitsPerSecond);
  5. return rateLimiter;
  6. }
  7. 看一下setRate
  8. public final void setRate(double permitsPerSecond) {
  9. checkArgument(permitsPerSecond > 0.0 && !Double.isNaN(permitsPerSecond), "rate must be positive");
  10. //从名字就可以看出这是一个互斥锁,这个互斥锁采用了double check懒汉单例模式生成,
  11. synchronized (mutex()) {
  12. doSetRate(permitsPerSecond, stopwatch.readMicros());
  13. }
  14. }

下面看一下doSetRate,真正开始设置速率了

  1. final void doSetRate(double permitsPerSecond, long nowMicros) {
  2. //这个方法非常重要里面是nextFreeTicketMicros和storedPermits的设置,在生成对象的时候没有用,获取令牌时再讲
  3. resync(nowMicros);
  4. //这个就是令牌生成间隔,微秒表示
  5. double stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;
  6. this.stableIntervalMicros = stableIntervalMicros;
  7. 这里又有一个doSetRate
  8. doSetRate(permitsPerSecond, stableIntervalMicros);
  9. }
  10.  
  11. void doSetRate(double permitsPerSecond, double stableIntervalMicros) {
  12. double oldMaxPermits = this.maxPermits;
  13. //设置最大令牌
  14. maxPermits = maxBurstSeconds * permitsPerSecond;
  15.  
  16. //设置存储令牌
  17. if (oldMaxPermits == Double.POSITIVE_INFINITY) {
  18. storedPermits = maxPermits;
  19. } else {
  20. storedPermits =
  21. (oldMaxPermits == 0.0)
  22. ? 0.0 // initial state
  23. : storedPermits * maxPermits / oldMaxPermits;
  24. }

到了这里整个创建过程就结束了,基本上就是一些设置,创建了锁,设置了生成令牌的间隔时间等等,下面看一下获取令牌的方法。

  1. //获取一个令牌
  2. public double acquire() {
  3. return acquire(1);
  4. }
  5.  
  6. public double acquire(int permits) {
  7. //reserve返回等待时间,内部进行了令牌的获取
  8. long microsToWait = reserve(permits);
  9. stopwatch.sleepMicrosUninterruptibly(microsToWait);
  10. return 1.0 * microsToWait / SECONDS.toMicros(1L);
  11. }
  12.  
  13. final long reserve(int permits) {
  14. checkPermits(permits);
  15. //创建对象时生成的锁
  16. synchronized (mutex()) {
  17. //stopwatch.readMicros()拿到当前的时间,预订
  18. return reserveAndGetWaitLength(permits, stopwatch.readMicros());
  19. }
  20. }
  21.  
  22. final long reserveAndGetWaitLength(int permits, long nowMicros) {
  23. //将数据透传,拿到最早可预订的时间,如果预订时间在未来时间,返回一个大于0的值为等待时间
  24. long momentAvailable = reserveEarliestAvailable(permits, nowMicros);
  25. return max(momentAvailable - nowMicros, 0);
  26. }

下面的代码就是我图中的实现

  1. final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
  2. //这个方法很重要先看下面这个方法的讲解,在从这里往下看
  3. resync(nowMicros);
  4. //返回下次发放令牌时间,如果这个时间大于当前时间,在调用的上层会sleep
  5. long returnValue = nextFreeTicketMicros;
  6. //拿到此次花费的令牌
  7. double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
  8. // 如果令牌不够,这里就会大于0,下面就会得出一个等待时间
  9. double freshPermits = requiredPermits - storedPermitsToSpend;
  10. long waitMicros =
  11. storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
  12. + (long) (freshPermits * stableIntervalMicros);
  13. //将下次发放令牌的时间加上等待时间
  14. this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
  15. this.storedPermits -= storedPermitsToSpend;
  16. return returnValue;
  17. }
  18.  
  19. void resync(long nowMicros) {
  20.  
  21. if (nowMicros > nextFreeTicketMicros) {
  22. //当前时间大于下次令牌发放时间,新的令牌为当前时间减去下次发放令牌时间除以生成令牌的时间间隔
  23. double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();
  24. //不能超过最大令牌数
  25. storedPermits = min(maxPermits, storedPermits + newPermits);
  26. //更新下次发放令牌时间为当前时间
  27. nextFreeTicketMicros = nowMicros;
  28. }
  29. }

总结

RateLimiter的原理用语言描述,很容易把人绕晕,上面的图其实是最好的总结,懂得原理才能更好的使用,在多种限流器中选择合适的限流器。了解源码,能更进一步的掌握原理,并且从源码中可以学到设计思路和

一些设计模式的应用。

guava限流器RateLimiter原理及源码分析的更多相关文章

  1. OpenCV学习笔记(27)KAZE 算法原理与源码分析(一)非线性扩散滤波

    http://blog.csdn.net/chenyusiyuan/article/details/8710462 OpenCV学习笔记(27)KAZE 算法原理与源码分析(一)非线性扩散滤波 201 ...

  2. ConcurrentHashMap实现原理及源码分析

    ConcurrentHashMap实现原理 ConcurrentHashMap源码分析 总结 ConcurrentHashMap是Java并发包中提供的一个线程安全且高效的HashMap实现(若对Ha ...

  3. HashMap和ConcurrentHashMap实现原理及源码分析

    HashMap实现原理及源码分析 哈希表(hash table)也叫散列表,是一种非常重要的数据结构,应用场景及其丰富,许多缓存技术(比如memcached)的核心其实就是在内存中维护一张大的哈希表, ...

  4. (转)ReentrantLock实现原理及源码分析

    背景:ReetrantLock底层是基于AQS实现的(CAS+CHL),有公平和非公平两种区别. 这种底层机制,很有必要通过跟踪源码来进行分析. 参考 ReentrantLock实现原理及源码分析 源 ...

  5. 【转】HashMap实现原理及源码分析

    哈希表(hash table)也叫散列表,是一种非常重要的数据结构,应用场景极其丰富,许多缓存技术(比如memcached)的核心其实就是在内存中维护一张大的哈希表,而HashMap的实现原理也常常出 ...

  6. 【OpenCV】SIFT原理与源码分析:DoG尺度空间构造

    原文地址:http://blog.csdn.net/xiaowei_cqu/article/details/8067881 尺度空间理论   自然界中的物体随着观测尺度不同有不同的表现形态.例如我们形 ...

  7. 《深入探索Netty原理及源码分析》文集小结

    <深入探索Netty原理及源码分析>文集小结 https://www.jianshu.com/p/239a196152de

  8. HashMap实现原理及源码分析之JDK8

    继续上回HashMap的学习 HashMap实现原理及源码分析之JDK7 转载 Java8源码-HashMap  基于JDK8的HashMap源码解析  [jdk1.8]HashMap源码分析 一.H ...

  9. 【OpenCV】SIFT原理与源码分析:关键点描述

    <SIFT原理与源码分析>系列文章索引:http://www.cnblogs.com/tianyalu/p/5467813.html 由前一篇<方向赋值>,为找到的关键点即SI ...

随机推荐

  1. android高仿抖音、点餐界面、天气项目、自定义view指示、爬取美女图片等源码

    Android精选源码 一个爬取美女图片的app Android高仿抖音 android一个可以上拉下滑的Ui效果 android用shape方式实现样式源码 一款Android上的新浪微博第三方轻量 ...

  2. Filter 中注入失败问题

    参考: https://www.cnblogs.com/digdeep/p/4770004.html?tvd https://www.cnblogs.com/EasonJim/p/7666009.ht ...

  3. java中字符串相等判断

    字符串的判断有2种: 1.判断地址是否相等  用:== 2.判断值是否相等  用:equals方法 Object类作为所有类的超类,而Object类的equals方法是直接比较地址的,源码如下: pu ...

  4. 微软Hyperlapse技术:让第一人称摄像稳定而流畅

    编者按:GoPro等第一人称摄像设备已经几乎成为了极限运动者的标配,但拍摄过程中的抖动常会让画面非常糟糕.微软Hyperlapse技术实现了将第一人称录像转化成稳定而流畅的视频.该成果的论文已发表在S ...

  5. AUTODESK 卸载工具,完美彻底卸载清除干净autodesk各种软件残留注册表和文件

    小伙伴是不是遇到 CAD/3dmax/maya/Revit/Inventor 安装失败或者安装不了的问题了呢?AUTODESK系列软件着实令人头疼,CAD/3dmax/maya/Revit/Inven ...

  6. oracle12c 可行的解决办法:ORA-01017: invalid username/password; logon denied

    开启服务OracleServiceORCL和OracleOraDB12Home1TNSListener用Oracle SQL developer 连接测试报错:ORA-01017: invalid u ...

  7. Ionic3学习笔记(十六)上传头像至图床

    本文为原创文章,转载请标明出处 个人做的开源 Demo 登录注册模块采用的是 Wilddog 野狗通讯云的身份认证服务,不得不说各方面和 Google 收购的 Firebase 很像,十分简单易用.其 ...

  8. Protocol Buffers学习(4):更多消息类型

    介绍一下消息的不同类型和引用 使用复杂消息类型 您可以使用其他消息类型作为字段类型.例如,假设你想在每个SearchResponse消息中包含Result消息,您可以在同一个.proto中定义一个Re ...

  9. 吴裕雄--天生自然 R语言开发学习:分类

    #-----------------------------------------------------------------------------# # R in Action (2nd e ...

  10. 中文字体压缩器-解决css引入的字体太大的问题

    字蛛是一个中文字体压缩器 官方网站:http://font-spider.org/index.html 用于解决页面引入的css字体过大的问题 使用方法: npm i -g font-spider 在 ...