前言

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

预防单机过大流量使用。

原理

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

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

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

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

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

源码阅读

public static void main(String[] args) {
RateLimiter rateLimiter = RateLimiter.create(10);
while (true) {
long start = System.currentTimeMillis();
rateLimiter.acquire();
System.out.println(System.currentTimeMillis() - start);
}
}  

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

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

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

final void doSetRate(double permitsPerSecond, long nowMicros) {
//这个方法非常重要里面是nextFreeTicketMicros和storedPermits的设置,在生成对象的时候没有用,获取令牌时再讲
resync(nowMicros);
//这个就是令牌生成间隔,微秒表示
double stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;
this.stableIntervalMicros = stableIntervalMicros;
这里又有一个doSetRate
doSetRate(permitsPerSecond, stableIntervalMicros);
} void doSetRate(double permitsPerSecond, double stableIntervalMicros) {
double oldMaxPermits = this.maxPermits;
//设置最大令牌
maxPermits = maxBurstSeconds * permitsPerSecond; //设置存储令牌
if (oldMaxPermits == Double.POSITIVE_INFINITY) {
storedPermits = maxPermits;
} else {
storedPermits =
(oldMaxPermits == 0.0)
? 0.0 // initial state
: storedPermits * maxPermits / oldMaxPermits;
}

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

//获取一个令牌
public double acquire() {
return acquire(1);
} public double acquire(int permits) {
//reserve返回等待时间,内部进行了令牌的获取
long microsToWait = reserve(permits);
stopwatch.sleepMicrosUninterruptibly(microsToWait);
return 1.0 * microsToWait / SECONDS.toMicros(1L);
} final long reserve(int permits) {
checkPermits(permits);
//创建对象时生成的锁
synchronized (mutex()) {
//stopwatch.readMicros()拿到当前的时间,预订
return reserveAndGetWaitLength(permits, stopwatch.readMicros());
}
} final long reserveAndGetWaitLength(int permits, long nowMicros) {
//将数据透传,拿到最早可预订的时间,如果预订时间在未来时间,返回一个大于0的值为等待时间
long momentAvailable = reserveEarliestAvailable(permits, nowMicros);
return max(momentAvailable - nowMicros, 0);
}

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

final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
//这个方法很重要先看下面这个方法的讲解,在从这里往下看
resync(nowMicros);
//返回下次发放令牌时间,如果这个时间大于当前时间,在调用的上层会sleep
long returnValue = nextFreeTicketMicros;
//拿到此次花费的令牌
double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
// 如果令牌不够,这里就会大于0,下面就会得出一个等待时间
double freshPermits = requiredPermits - storedPermitsToSpend;
long waitMicros =
storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
+ (long) (freshPermits * stableIntervalMicros);
//将下次发放令牌的时间加上等待时间
this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
this.storedPermits -= storedPermitsToSpend;
return returnValue;
} void resync(long nowMicros) { if (nowMicros > nextFreeTicketMicros) {
//当前时间大于下次令牌发放时间,新的令牌为当前时间减去下次发放令牌时间除以生成令牌的时间间隔
double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();
//不能超过最大令牌数
storedPermits = min(maxPermits, storedPermits + newPermits);
//更新下次发放令牌时间为当前时间
nextFreeTicketMicros = nowMicros;
}
}

总结

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. python3下scrapy爬虫(第九卷:scrapy数据存储进JSON文件)

    将爬取数据存储在JSON文件里并不难,只需修改pipelines文件 直接看代码: 来看下结果: 中文字符恶心的很 之后我会在后卷中做出修改

  2. 【Linux_Shell 脚本编程学习笔记四、监控系统内存并报警企业案例脚本】

    前置知识:awk 参考学习博客:https://www.cnblogs.com/bugingcode/p/8287914.html awk 'BEGIN{ commands } pattern{ co ...

  3. redis下载安装及php配置redis

    下载及安装redis 1.首先去github网站上下载https://github.com/dmajkic/redis/downloads: 2.根据实际情况,将64bit的内容cp到自定义盘符目录, ...

  4. Docker学习笔记_10 docker应用 - 部署TOMCAT服务

    选择基镜像 基镜像使用dokcer hub官方提供的tomcat8 alpine当前最新版本,https://hub.docker.com/_/tomcat/ docker pull tomcat:8 ...

  5. Shell字符串比较相等、不相等方法小结

    #!/bin/sh #测试各种字符串比较操作. #shell中对变量的值添加单引号,爽引号和不添加的区别:对类型来说是无关的,即不是添加了引号就变成了字符串类型, #单引号不对相关量进行替换,如不对$ ...

  6. OpenCA搭建

    前言: OpenCA是OpenCA开源组织使用Perl对OpenSSL进行二次开发而成的一套完善的PKI免费软件,主要由四部分组成:CA.RA.PUB和NODE.简而言之,PUB是对外提供服务的接口, ...

  7. 查漏补缺:socket编程:TCP粘包问题和常用解决方案(上)

    1.TCP粘包问题的产生(发送端) 由于TCP协议是基于字节流并且无边界的传输协议,因此很容易产生粘包问题.TCP的粘包可能发生在发送端,也可能发生在接收端.发送端的粘包是TCP协议本身引起的,TCP ...

  8. Autotestplat体验中心

    web端 移动端 可戳[阅读原文]进行体验

  9. AndroidManifest.xml文件详解

    本文为安全专家李洋的最新一篇专栏文章<AndroidManifest.xml文件详解>.AndroidManifest.xml是一个跟安全相关的配置文件,该配置文件是Android安全保障 ...

  10. 什么是SNAT

    SNAT是源地址转换,其作用是将ip数据包的源地址转换成另外一个地址,可能有人觉得奇怪,好好的为什么要进行ip地址转换啊,为了弄懂这个问题,我们要看一下局域网用户上公网的原理,假设内网主机A(192. ...