限流,是服务或者应用对自身保护的一种手段,通过限制或者拒绝调用方的流量,来保证自身的负载。

常用的限流算法有两种:漏桶算法和令牌桶算法

漏桶算法

思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大会直接溢出,可以看出漏桶算法能强行限制数据的传输速率。

对于很多应用场景来说,除了要求能够限制数据的平均传输速率外,还要求允许某种程度的突发传输。这时候漏桶算法可能就不合适了,令牌桶算法更为适合。

令牌桶算法

原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。

下面的内容主要讨论令牌桶算法。

仔细讨论之前,先看下一下基于分布式缓存实现的令牌桶的流程图。

以下是对流程图的讲解:

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令牌桶算法的更多相关文章

  1. 封装RateLimiter 令牌桶算法

    自定义注解封装RateLimiter.实例: @RequestMapping("/myOrder") @ExtRateLimiter(value = 10.0, timeOut = ...

  2. coding++:RateLimiter 限流算法之漏桶算法、令牌桶算法--简介

    RateLimiter是Guava的concurrent包下的一个用于限制访问频率的类 <dependency> <groupId>com.google.guava</g ...

  3. 基于令牌桶算法实现的SpringBoot分布式无锁限流插件

    本文档不会是最新的,最新的请看Github! 1.简介 基于令牌桶算法和漏桶算法实现的纳秒级分布式无锁限流插件,完美嵌入SpringBoot.SpringCloud应用,支持接口限流.方法限流.系统限 ...

  4. coding++:Semaphore—RateLimiter-漏桶算法-令牌桶算法

    java中对于生产者消费者模型,或者小米手机营销 1分钟卖多少台手机等都存在限流的思想在里面. 关于限流 目前存在两大类,从线程个数(jdk1.5 Semaphore)和RateLimiter速率(g ...

  5. 15行python代码,帮你理解令牌桶算法

    本文转载自: http://www.tuicool.com/articles/aEBNRnU   在网络中传输数据时,为了防止网络拥塞,需限制流出网络的流量,使流量以比较均匀的速度向外发送,令牌桶算法 ...

  6. flask结合令牌桶算法实现上传和下载速度限制

    限流.限速: 1.针对flask的单个路由进行限流,主要场景是上传文件和下载文件的场景 2.针对整个应用进行限流,方法:利用nginx网关做限流 本文针对第一中情况,利用令牌桶算法实现: 这个方法:h ...

  7. 令牌桶算法实现API限流

    令牌桶算法( Token Bucket )和 Leaky Bucket 效果一样但方向相反的算法,更加容易理解.随着时间流逝,系统会按恒定 1/QPS 时间间隔(如果 QPS=100 ,则间隔是 10 ...

  8. 限流10万QPS、跨域、过滤器、令牌桶算法-网关Gateway内容都在这儿

    一.微服务网关Spring Cloud Gateway 1.1 导引 文中内容包含:微服务网关限流10万QPS.跨域.过滤器.令牌桶算法. 在构建微服务系统中,必不可少的技术就是网关了,从早期的Zuu ...

  9. php 基于redis使用令牌桶算法 计数器 漏桶算法 实现流量控制

    通常在高并发和大流量的情况下,一般限流是必须的.为了保证服务器正常的压力.那我们就聊一下几种限流的算法. 计数器计数器是一种最常用的一种方法,在一段时间间隔内,处理请求的数量固定的,超的就不做处理. ...

随机推荐

  1. python中的for循环加强

    #先执行外面for循环,再执行里面for循环,接着执行外面for循环,程序结束 #打印结果为1,10,2 flag=False for i in range(1,10): print(i) if fl ...

  2. T100-----调试程序,快速定位到错误行

    1.r.d 作业编码 2.ctrl+d3.输入    watch g_errparam.code if g_errparam.code='错误编码',   点几次OK,   再直接run程序,会自动跳 ...

  3. 使用jQuery开发tab选项卡插件(可以右键关闭多个标签)

    在前一篇“使用jQuery开发tab选项卡插件”的基础上添加了tab标签右键关闭菜单功能,菜单主要包括:关闭当前标签.关闭左侧标签.关闭右侧标签.关闭其他.关闭全部. 一.插件效果 二.实现思路 为w ...

  4. 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 ...

  5. selenium用法详解以chrome为例

    selenium 是一套完整的web应用程序测试系统,包含了测试的录制(selenium IDE),编写及运行(Selenium Remote Control)和测试的并行处理(Selenium Gr ...

  6. semaphore demo 并行 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

    import 'dart:async'; import 'package:semaphore/semaphore.dart'; import 'dart:io'; import 'dart:conve ...

  7. Elasticsearch 及 Kibana 安装篇

    简介 官网-安装介绍 这里记载了各个软件包的安装方法,Linux Mac Windows-- 本文记载的是在 CentOS 系统安装 Elasticsearch 7.0.0 版本的步骤. 安装 Jav ...

  8. 移动oracle数据文件的两种方法

    1.alter database方法该方法,可以移动任何表空间的数据文件. ***关闭数据库***SQL> shutdown immediateDatabase closed.Database ...

  9. Swift枚举的全用法

    鉴于昨天开会部门会议讨论的时候,发现有些朋友对枚举的用法还是存在一些疑问,所以就写下这个文章,介绍下Swift下的枚举的用法. 基本的枚举类型 来,二话不说,我们先贴一个最基本的枚举: enum Mo ...

  10. 谈一谈做iOS播放器库开发所涉及的知识点

    在自己研究生毕业的时候,想着能找上一份做视频编解码的工作,可惜没有如愿,最后到了一家iOS游戏渠道公司去做游戏支付业务的SDK开发,我的iOS正式开发生涯就这么开始了. 在那家iOS游戏渠道没做上一年 ...