RateLimiter令牌桶算法
限流,是服务或者应用对自身保护的一种手段,通过限制或者拒绝调用方的流量,来保证自身的负载。
常用的限流算法有两种:漏桶算法和令牌桶算法
漏桶算法
思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大会直接溢出,可以看出漏桶算法能强行限制数据的传输速率。
对于很多应用场景来说,除了要求能够限制数据的平均传输速率外,还要求允许某种程度的突发传输。这时候漏桶算法可能就不合适了,令牌桶算法更为适合。
令牌桶算法
原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。
下面的内容主要讨论令牌桶算法。
仔细讨论之前,先看下一下基于分布式缓存实现的令牌桶的流程图。
以下是对流程图的讲解:
1. key是否存在。因为流程图是基于分布式缓存做的集群限流,需要根据不同key做统计,第一次访问初始化key。
2. 如果key不存在,初始化令牌桶,防止初始令牌数量,并且设置key过期时间为interval*2。这里的初始令牌数量一般可以设置成限流阈值,比如限流10qps,初始值可以设置成10,来应对一开始的流量。interval是间隔时间,比如限流阈值10qps,interval设置为1s。过期时间是缓存中key的时间,interval*2是为了防止key过期无法拦截流量。
3. 如果key存在,将当前请求时间和当前key的最后放置令牌时间做比较。如果间隔超过interval,进入第4步,间隔未超过interval,进入第5步。
4. 间隔已经超过1s,直接放置令牌到最大数量。
5. 间隔没有超过1s,定义delta为时间差,放置令牌数=delta/(1/qps)。放入令牌时保证令牌数不超过桶的容量。同时,重置放入令牌的时间。
6. 从桶中获取令牌,获取令牌成功,执行请求;获取令牌时间,拒绝请求。
以上是对令牌桶算法的一种实现,接下来会具体分析guava RateLimiter的源码,RateLimiter的原理和上述实现类似,但是会有部分区别。
最基础的使用RateLimiter的姿势如下:
RateLimiter rateLimiter = RateLimiter.create(10.0);
rateLimiter.acquire();
create方法用于构建既定速度的实例,acquire方法使用阻塞方式获取令牌。
首先进入源码,看下create方法:
static RateLimiter create(SleepingStopwatch stopwatch, double permitsPerSecond) {
RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */);
rateLimiter.setRate(permitsPerSecond);
return rateLimiter;
}
这里我们看到,会构建一个SmoothBursty实例,并且给这个实例设置速率。
/**
* This implements a "bursty" RateLimiter, where storedPermits are translated to
* zero throttling. The maximum number of permits that can be saved (when the RateLimiter is
* unused) is defined in terms of time, in this sense: if a RateLimiter is 2qps, and this
* time is specified as 10 seconds, we can save up to 2 * 10 = 20 permits.
*/
SmoothBursty的注释翻译如下:
这是一个“突发性”的RateLimiter实现,这里存储令牌数可以被转义成“零节流”。存储的最大令牌数被存储成(如果RateLimiter实例有一段时间没有被获取令牌)一个时间形式。举例:如果一个RateLimiter实例的速率是2qps,并且maxBurstSeconds时间是10,那么最多可以存储20个令牌(令牌桶容量)。
看一下SmoothBursty的构造函数:
SmoothBursty(SleepingStopwatch stopwatch, double maxBurstSeconds) {
super(stopwatch);
this.maxBurstSeconds = maxBurstSeconds;
}
下面简单介绍下SleepingStopwatch是什么。
@VisibleForTesting
abstract static class SleepingStopwatch {
/*
* We always hold the mutex when calling this. TODO(cpovirk): Is that important? Perhaps we need* to guarantee that each call to reserveEarliestAvailable, etc. sees a value >= the previous?
* Also, is it OK that we don't hold the mutex when sleeping?
*/
abstract long readMicros();
abstract void sleepMicrosUninterruptibly(long micros);
static final SleepingStopwatch createFromSystemTimer() {
return new SleepingStopwatch() {
final Stopwatch stopwatch = Stopwatch.createStarted();
@Override
long readMicros() {
return stopwatch.elapsed(MICROSECONDS);
}
@Override
void sleepMicrosUninterruptibly(long micros) {
if (micros > 0) {
Uninterruptibles.sleepUninterruptibly(micros, MICROSECONDS);
}
}
};
}
}
SleepingStopWatch是一个可sleep的秒表,起始时间是构建StopWatch的时间,sleepMicrosUninterruptibly方法支持不受中断的sleep,sleep是当前线程的sleep。
以上构建方法完成,下面再来看一下acquire的源码。
public double acquire(int permits) {
long microsToWait = reserve(permits);
stopwatch.sleepMicrosUninterruptibly(microsToWait);
return 1.0 * microsToWait / SECONDS.toMicros(1L);
}
第一行是获取令牌需要等待时间;第二行是线程sleep时间,如果令牌足够,这里会返回0,无需sleep;第三行是返回等待时间值,单位转换成秒。
接下来看下reserve实现。
final long reserve(int permits) {
checkPermits(permits);
synchronized (mutex()) {
return reserveAndGetWaitLength(permits, stopwatch.readMicros());
}
}
获取锁之后,直接调用reserveAndGetWaitLength方法,传入参数是需要获取的令牌数、秒表的当前时间。
final long reserveAndGetWaitLength(int permits, long nowMicros) {
long momentAvailable = reserveEarliestAvailable(permits, nowMicros);
return max(momentAvailable - nowMicros, 0);
}
首先计算获取令牌需要的时间节点,如果时间节点小于当前时间,无需等待;如果时间节点在当前时间节点之后,需要sleep线程,sleep时间是momentAvailable和nowMicros的差值。
下面看下reserveEarliestAvailable,计算时间节点的实现。
final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
resync(nowMicros);
long returnValue = nextFreeTicketMicros;
double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
double freshPermits = requiredPermits - storedPermitsToSpend;
long waitMicros = storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
+ (long) (freshPermits * stableIntervalMicros);try {
this.nextFreeTicketMicros = LongMath.checkedAdd(nextFreeTicketMicros, waitMicros);
} catch (ArithmeticException e) {
this.nextFreeTicketMicros = Long.MAX_VALUE;
}
this.storedPermits -= storedPermitsToSpend;
return returnValue;
}
这里面有几个变量需要注意下:
nextFreeTicketMicros 下次允许获取令牌的时间,这个时间是因为RateLimiter允许透支而存在的,比如当前令牌桶只有一个令牌,一个请求来获取5个令牌,请求会成功,但是nextFreeTicketMicros往后推4个时间片段,在当前时间推移到nextFreeTicketMicros之前,所有请求都将等待。如果长时间没有请求到来,这个值会是过去的一个时间值。
storedPermits 当前令牌桶剩余的令牌数。
stableIntervalMicros 时间片段值,qps为5的话,时间片段是200ms。
maxPermits 令牌桶的容量。
下面开始分析代码。
resync(nowMicros);//根据当前时间和nextFreeTicketMicros,往令牌桶放置令牌,最多不超过令牌桶的maxPermits。
long returnValue = nextFreeTicketMicros; //赋值语句
double storedPermitsToSpend =min(requiredPermits,this.storedPermits);//请求的令牌数和令牌桶当前令牌数做比较,取较小值,storedPermitsToSpend是指需要消耗当前令牌桶的令牌数量。
double freshPermits = requiredPermits - storedPermitsToSpend;//需要透支的令牌数量
long waitMicros = storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
+(long) (freshPermits *stableIntervalMicros);//如果不透支,waitMicros为0,下次请求可以正常获取令牌;如果透支,需要将nextFreeTicketMicros往后推。
try {
this.nextFreeTicketMicros = LongMath.checkedAdd(nextFreeTicketMicros, waitMicros);
} catch (ArithmeticException e) {
this.nextFreeTicketMicros = Long.MAX_VALUE;
}
//将nextFreeTicketMicros往后推
this.storedPermits -= storedPermitsToSpend;//清算令牌桶的令牌。
至此,最基础的创建RateLimiter和阻塞获取令牌的过程已经分析完毕。
总结:
1. 文章前端的流程图和guava RateLimiter的实现类似。
2. guava RateLimiter支持透支,如果每次获取单个令牌,那么透支将不会生效。
RateLimiter令牌桶算法的更多相关文章
- 封装RateLimiter 令牌桶算法
自定义注解封装RateLimiter.实例: @RequestMapping("/myOrder") @ExtRateLimiter(value = 10.0, timeOut = ...
- coding++:RateLimiter 限流算法之漏桶算法、令牌桶算法--简介
RateLimiter是Guava的concurrent包下的一个用于限制访问频率的类 <dependency> <groupId>com.google.guava</g ...
- 基于令牌桶算法实现的SpringBoot分布式无锁限流插件
本文档不会是最新的,最新的请看Github! 1.简介 基于令牌桶算法和漏桶算法实现的纳秒级分布式无锁限流插件,完美嵌入SpringBoot.SpringCloud应用,支持接口限流.方法限流.系统限 ...
- coding++:Semaphore—RateLimiter-漏桶算法-令牌桶算法
java中对于生产者消费者模型,或者小米手机营销 1分钟卖多少台手机等都存在限流的思想在里面. 关于限流 目前存在两大类,从线程个数(jdk1.5 Semaphore)和RateLimiter速率(g ...
- 15行python代码,帮你理解令牌桶算法
本文转载自: http://www.tuicool.com/articles/aEBNRnU 在网络中传输数据时,为了防止网络拥塞,需限制流出网络的流量,使流量以比较均匀的速度向外发送,令牌桶算法 ...
- flask结合令牌桶算法实现上传和下载速度限制
限流.限速: 1.针对flask的单个路由进行限流,主要场景是上传文件和下载文件的场景 2.针对整个应用进行限流,方法:利用nginx网关做限流 本文针对第一中情况,利用令牌桶算法实现: 这个方法:h ...
- 令牌桶算法实现API限流
令牌桶算法( Token Bucket )和 Leaky Bucket 效果一样但方向相反的算法,更加容易理解.随着时间流逝,系统会按恒定 1/QPS 时间间隔(如果 QPS=100 ,则间隔是 10 ...
- 限流10万QPS、跨域、过滤器、令牌桶算法-网关Gateway内容都在这儿
一.微服务网关Spring Cloud Gateway 1.1 导引 文中内容包含:微服务网关限流10万QPS.跨域.过滤器.令牌桶算法. 在构建微服务系统中,必不可少的技术就是网关了,从早期的Zuu ...
- php 基于redis使用令牌桶算法 计数器 漏桶算法 实现流量控制
通常在高并发和大流量的情况下,一般限流是必须的.为了保证服务器正常的压力.那我们就聊一下几种限流的算法. 计数器计数器是一种最常用的一种方法,在一段时间间隔内,处理请求的数量固定的,超的就不做处理. ...
随机推荐
- python中的for循环加强
#先执行外面for循环,再执行里面for循环,接着执行外面for循环,程序结束 #打印结果为1,10,2 flag=False for i in range(1,10): print(i) if fl ...
- T100-----调试程序,快速定位到错误行
1.r.d 作业编码 2.ctrl+d3.输入 watch g_errparam.code if g_errparam.code='错误编码', 点几次OK, 再直接run程序,会自动跳 ...
- 使用jQuery开发tab选项卡插件(可以右键关闭多个标签)
在前一篇“使用jQuery开发tab选项卡插件”的基础上添加了tab标签右键关闭菜单功能,菜单主要包括:关闭当前标签.关闭左侧标签.关闭右侧标签.关闭其他.关闭全部. 一.插件效果 二.实现思路 为w ...
- bsd pkg install gcc gmake cmake gdb cgdb
bsd pkg install gcc gmake cmake gdb cgdb 安装pkg帮助文档并查看文档# pkg help install# man pkg-install # pkg sea ...
- selenium用法详解以chrome为例
selenium 是一套完整的web应用程序测试系统,包含了测试的录制(selenium IDE),编写及运行(Selenium Remote Control)和测试的并行处理(Selenium Gr ...
- semaphore demo 并行 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
import 'dart:async'; import 'package:semaphore/semaphore.dart'; import 'dart:io'; import 'dart:conve ...
- Elasticsearch 及 Kibana 安装篇
简介 官网-安装介绍 这里记载了各个软件包的安装方法,Linux Mac Windows-- 本文记载的是在 CentOS 系统安装 Elasticsearch 7.0.0 版本的步骤. 安装 Jav ...
- 移动oracle数据文件的两种方法
1.alter database方法该方法,可以移动任何表空间的数据文件. ***关闭数据库***SQL> shutdown immediateDatabase closed.Database ...
- Swift枚举的全用法
鉴于昨天开会部门会议讨论的时候,发现有些朋友对枚举的用法还是存在一些疑问,所以就写下这个文章,介绍下Swift下的枚举的用法. 基本的枚举类型 来,二话不说,我们先贴一个最基本的枚举: enum Mo ...
- 谈一谈做iOS播放器库开发所涉及的知识点
在自己研究生毕业的时候,想着能找上一份做视频编解码的工作,可惜没有如愿,最后到了一家iOS游戏渠道公司去做游戏支付业务的SDK开发,我的iOS正式开发生涯就这么开始了. 在那家iOS游戏渠道没做上一年 ...