- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章RateLimiter 源码分析由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
俗话说得好,缓存,限流和降级是系统的三把利剑。刚好项目中每天早上导出数据时因调订单接口频率过高,订单系统担心会对用户侧的使用造成影响,让我们对调用限速一下,所以就正好用上了。 。
常用的限流算法有2种:漏桶算法和令牌桶算法.
漏桶算法 。
漏桶算法:请求先进入“桶”中,然后桶以一定的速率处理请求。如果请求的速率过快会导致桶溢出。根据描述可以知道,漏桶算法会强制限制请求处理的速度。任你请求的再快还是再慢,我都是以这种速率来处理。 。
但是对于很多情况下,除了要求能够限制平均处理速度外,还要求能允许一定程度的的突发情况。这样的话,漏桶算法就不合适了,用令牌桶算法更合适.
令牌桶算法 。
令牌桶算法的原理是:系统以恒定的速率往桶里丢一定数量的令牌,请求只有拿到了令牌才能处理。当桶里没有令牌时便可拒绝服务。 。
Guava中的Ratelimiter便是实现的令牌桶算法,同时能支持一定程度的突发请求.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
private
static
RateLimiter one=RateLimiter.create(
2
);
//每秒2个
private
static
RateLimiter two=RateLimiter.create(
2
);
//每秒2个
private
RateLimitUtil(){};
public
static
void
acquire(RateLimiter r,
int
num){
double
time =r.acquire(num);
System.out.println(
"wait time="
+time);
}
public
static
void
main(String[] args)
throws
InterruptedException {
acquire(one,
1
);
acquire(one,
1
);
acquire(one,
1
);
System.out.println(
"-----"
);
acquire(two,
10
);
acquire(two,
1
);
}
|
。
输出结果:
1
2
3
4
5
6
|
wait time=
0.0
wait time=
0.499163
wait time=
0.489308
-----
wait time=
0.0
wait time=
4.497819
|
可以看到,我们以每秒2个请求的速度生成令牌。对one来说,当第2次和第3次获取请求的时候,等待的时间加起来就差不多刚好是1秒。对two来说,当第一次获取了10个令牌之后,第二次获取1个请求,就差不多等待5S(10/2=5)。可以看到,guava通过限制后面请求的等待时间,来支持一定程度的突发请求.
接下来,就是通过源码来解析它! 。
当我第一次看到令牌桶的算法描述的时候,我还以为真是有一个线程每隔X秒往一个类似计数器的地方加数字呢…. 。
guava的限流算法有2种模式,一种是稳定速度,还有一种是生成令牌的速度慢慢提升直到维持在一个稳定的速度。2种模式原理类似,只是在具体等待多久的时间计算上有区别。以下就专门指稳定速度的模式.
先来看看它的acquire()方法:
1
2
3
4
5
|
public
double
acquire(
int
permits) {
long
microsToWait = reserve(permits);
//先计算获取这些请求需要让线程等待多长时间
stopwatch.sleepMicrosUninterruptibly(microsToWait);
//让线程阻塞microTowait微秒长的时间
return
1.0
* microsToWait / SECONDS.toMicros(1L);
//返回阻塞的时间
}
|
主要分3步: 。
1. 根据limiter创建时传入的参数,计算出生成这些数量的令牌需要多长的时间。 。
2. 让线程阻塞microTowait这么长的时间(单位:微秒) 。
3. 再返回阻塞了多久,单位:秒 。
具体它是怎么计算需要多长时间的呢?让我们来看看reserve(permits)方法.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
final
long
reserve(
int
permits) {
checkPermits(permits);
//检查参数是否合法
synchronized
(mutex()) {
return
reserveAndGetWaitLength(permits, stopwatch.readMicros());
}
}
↓
↓
↓
final
long
reserveAndGetWaitLength(
int
permits,
long
nowMicros) {
long
momentAvailable = reserveEarliestAvailable(permits, nowMicros);
return
max(momentAvailable - nowMicros,
0
);
}
↓
↓
↓
final
long
reserveEarliestAvailable(
int
requiredPermits,
long
nowMicros) {
resync(nowMicros);
//here
long
returnValue = nextFreeTicketMicros;
double
storedPermitsToSpend = min(requiredPermits,
this
.storedPermits);
double
freshPermits = requiredPermits - storedPermitsToSpend;
long
waitMicros = storedPermitsToWaitTime(
this
.storedPermits, storedPermitsToSpend)
+ (
long
) (freshPermits * stableIntervalMicros);
this
.nextFreeTicketMicros = nextFreeTicketMicros + waitMicros;
this
.storedPermits -= storedPermitsToSpend;
return
returnValue;
}
|
最终调用的是reserveEarliestAvailable方法。先看看resync(nowMicros)方法.
1
2
3
4
5
6
7
8
|
private
void
resync(
long
nowMicros) {
// if nextFreeTicket is in the past, resync to now
if
(nowMicros > nextFreeTicketMicros) {
storedPermits = min(maxPermits,
storedPermits + (nowMicros - nextFreeTicketMicros) / stableIntervalMicros);
nextFreeTicketMicros = nowMicros;
}
}
|
nextFreeTicketMicros的意思是:下次获取的时候需要减去的时间。如果是第一次调用accquire()方法,那nowMicros - nextFreeTicketMicros 就是从初始化(初始化的时候会给nextFreeTicketMicros 赋值一次,具体可以看RateLimiter的构造器)到第一次请求,这中间发生的时间。 。
这个方法的意思,如果当前时间比上一轮设置的下次获取的时间大(因为存在提前获取的情况,比如上次直接获取了10个,那上轮设置的nextFreeTicketMicros就是上一轮的时间+5s。后面会提到),那就计算这个中间理论上能生成多少的令牌。比如这中间隔了1秒钟,然后stableIntervalMicros=5000(稳定生成速度的情况下),那么,就这中间就可以生成2个令牌。再加上它原先存储的storedPermits个,如果比maxPermits大,那最大也只能存maxPermits这么多。如果比maxPermits小,那就是storedPermits=原先存的+这中间生成的数量。同时记录下下次获取的时候需要减去的时间,也就是当前时间 (nextFreeTicketMicros )。 。
接下来继续看reserveEarliestAvailable方法:
1
2
3
4
5
6
7
8
9
10
11
|
final
long
reserveEarliestAvailable(
int
requiredPermits,
long
nowMicros) {
//1
resync(nowMicros);
//2
long
returnValue = nextFreeTicketMicros;
//3
double
storedPermitsToSpend = min(requiredPermits,
this
.storedPermits);
//4
double
freshPermits = requiredPermits - storedPermitsToSpend;
//5
long
waitMicros = storedPermitsToWaitTime(
this
.storedPermits, storedPermitsToSpend)
+ (
long
) (freshPermits * stableIntervalMicros);
//6
this
.nextFreeTicketMicros = nextFreeTicketMicros + waitMicros;
//7
this
.storedPermits -= storedPermitsToSpend;
//8
return
returnValue;
//9
}
|
我们一行一行来看: 。
第二行设置好之后。第3行中将下次获取的时候需要减去的时间作为返回值(这点很重要)。 。
这2句是什么意思呢? 。
其实这2句就是使得RateLimiter能一定程度的突发请求的原因。假设requiredPermits=10,而我们能存的storedPermits=2,那么freshPermits=8,也就是多取了8个。而第6行就是计算这多取的8个需要多长时间才能生成?需要3秒。那么,就将这3秒钟加到我们前面赋值的“下次获取的时候需要减去的时间 ”。 。
比如在05秒的时候一次性获取了10个,那么,第7行的意思就是nextFreeTicketMicros=13S对应的系统的毫秒数。然后storedPermits就是-8。当过了1秒钟,下一次请求来调用acquire(1)的时候,resync方法中由于nowMicros 。
1
2
3
4
|
final
long
reserveAndGetWaitLength(
int
permits,
long
nowMicros) {
long
momentAvailable = reserveEarliestAvailable(permits, nowMicros);
return
max(momentAvailable - nowMicros,
0
);
//取较大的值
}
|
也就是说,reserveAndGetWaitLength会返回max(13-6,0),也就是7。而该方法的返回值又是用于sleep线程的,也就是我们在一开始看到的:
1
2
3
4
5
|
public
double
acquire(
int
permits) {
long
microsToWait = reserve(permits);
stopwatch.sleepMicrosUninterruptibly(microsToWait);
return
1.0
* microsToWait / SECONDS.toMicros(1L);
}
|
总结起来,最主要的是nowMicros,nextFreeTicketMicros这2个值。nextFreeTicketMicros在一开始构造器执行的时候会赋值一次为构造器执行的时间。当第一次调用accquire()的时候,resync会被执行,然后在accquire()中将nextFreeTicketMicros设置为当前时间。但是,需要注意的是,在reserveEarliestAvailable中会根据请求的令牌数和当前存储的令牌数进行比较。如果请求的令牌数很大,则会计算出生成这些多余的令牌需要的时间,并加在nextFreeTicketMicros上,从而保证下次调用accquire()的时候,根据nextFreeTicketMicros和当时的nowMicros相减,若>0,则需要等到对应的时间。也就能应对流量的突增情况了。 。
所以最重要的是nextFreeTicketMicros,它记录了你这次获取的时候,能够开始生成令牌的时间。比如当前是05S,那若nextFreeTicketMicros=10,表示它要到10S才能开始生成令牌,谁叫前面的多拿了这么多呢。至于它这次是多拿了还是只是拿一个令牌,等待时间都是这么多。如果这次又多拿了,那下次就等待更久! 。
1
2
3
4
5
6
7
8
9
10
11
12
|
private
static
RateLimiter too=RateLimiter.create(
2
);
//每秒2个
private
RateLimitUtil(){};
public
static
void
acquire(RateLimiter r,
int
num){
double
time =r.acquire(num);
System.out.println(
"wait time="
+time);
}
public
static
void
main(String[] args)
throws
InterruptedException {
acquire(too,
1
);
acquire(too,
10
);
//只等待了0.5秒就获取了10个
acquire(too,
10
);
//等待了5秒就获取了10个
acquire(too,
1
);
//虽然只获取1个,也是等待5秒
}
|
总结 。
以上就是本文关于RateLimiter 常用方法以及源码分析的全部内容,希望对大家有所帮助。感谢大家对我网站的支持! 。
原文链接:http://blog.csdn.net/foolishandstupid/article/details/76285690#comments 。
最后此篇关于RateLimiter 源码分析的文章就讲到这里了,如果你想了解更多关于RateLimiter 源码分析的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
ACO.Visualization项目 本项目演示蚁群算法求解旅行商问题的可视化过程,包括路径上的信息素浓度、蚁群的运动过程等。项目相关的代码:https://github.com/anycad/A
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 我们不允许提问寻求书籍、工具、软件库等的推荐。您可以编辑问题,以便用事实和引用来回答。 关闭 7 年前。
我需要用Sql数据库制作并包含的PHP票务系统源码用户客户端和管理员。我需要个人 CMS 的这个来源。谢谢你帮助我。 最佳答案 我在不同的情况下使用了 osticket。 这里: http://ost
我的场景:我想在日志文件中写入发生异常的部分代码(例如,发生异常的行前 5 行和行后 5 行 - 或者至少是该方法的所有代码)。 我的想法是用 C# 代码反编译 pdb 文件,并从该反编译文件中找到一
RocketMQ设定了延迟级别可以让消息延迟消费,延迟消息会使用 SCHEDULE_TOPIC_XXXX 这个主题,每个延迟等级对应一个消息队列,并且与普通消息一样,会保存每个消息队列的消费进度
先附上Hystrix源码图 在微服务架构中,根据业务来拆分成一个个的服务,服务与服务之间可以相互调用(RPC),在Spring Cloud可以用RestTemplate+Ribbon和
此篇博客学习的api如标题,分别是: current_url 获取当前页面的url; page_source 获取当前页面的源码; title 获取当前页面的titl
? 1 2
1、前言 作为一个数据库爱好者,自己动手写过简单的sql解析器以及存储引擎,但感觉还是不够过瘾。<<事务处理-概念与技术>>诚然讲的非常透彻,但只能提纲挈领,不能让你
gory"> 目录 运行时信号量机制 semaphore 前言 作用是什么 几个主要的方法 如何实现
自己写的一个评论系统源码分享给大家,包括有表情,还有评论机制。用户名是随机的 针对某一篇文章进行评论 function subcomment() {
一、概述 StringBuilder是一个可变的字符串序列,这个类被设计去兼容StringBuffer类的API,但不保证线程安全性,是StringBuffer单线程情况下的一个替代实现。在可能的情
一、概述 System是用的非常多的一个final类。它不能被实例化。System类提供了标准的输入输出和错误输出流;访问外部定义的属性和环境变量;加载文件和库的方法;以及高效的拷贝数组中一部分元素
在JDK中,String的使用频率和被研究的程度都非常高,所以接下来我只说一些比较重要的内容。 一、String类的概述 String类的声明如下: public final class Str
一、概述 Class的实例代表着正在运行的Java应用程序的类和接口。枚举是一种类,而直接是一种接口。每一个数组也属于一个类,这个类b被反射为具有相同元素类型和维数的所有数组共享的类对象。八大基本树
一、概述 Compiler这个类被用于支持Java到本地代码编译器和相关服务。在设计上,这个类啥也不做,他充当JIT编译器实现的占位符。 放JVM虚拟机首次启动时,他确定系统属性java.comp
一、概述 StringBuffer是一个线程安全的、可变的字符序列,跟String类似,但它能被修改。StringBuffer在多线程环境下可以很安全地被使用,因为它的方法都是通过synchroni
一、概述 Enum是所有Jav中枚举类的基类。详细的介绍在Java语言规范中有说明。 值得注意的是,java.util.EnumSet和java.util.EnumMap是Enum的两个高效实现,
一、概述 此线程指的是执行程序中的线程。 Java虚拟机允许应用程序同时执行多个执行线程。 每个线程都有优先权。 具有较高优先级的线程优先于优先级较低的线程执行。 每个线程可能也可能不会被标记为守
一、抽象类Number 类继承关系 这里面的原子类、BigDecimal后面都会详细介绍。 属性和抽象方法 二、概述 所有的属性,最小-128,最大127,SIZE和BYTES代码比
我是一名优秀的程序员,十分优秀!