滑动窗口限流 java_Spring Boot 的接口限流算法优缺点深度分析

点击上方蓝色字体,选择“标星公众号”

优质文章,第一时间送达

上一篇:这300G的Java资料是我师傅当年给我的,免费分享给大家(已修复)

下一篇:昨天分享资料不小心把百度网盘深处的秘密泄露了(已修复)

转自: loubobooo

原文:my.oschina.net/loubobooo/blog/1796752

前言

在一个高并发系统中对流量的把控是非常重要的,当巨大的流量直接请求到我们的服务器上没多久就可能造成接口不可用,不处理的话甚至会造成整个应用不可用。

那么何为限流呢?顾名思义,限流就是限制流量,就像你宽带包了1个G的流量,用完了就没了。通过限流,我们可以很好地控制系统的qps,从而达到保护系统的目的。本篇文章将会介绍一下常用的限流算法以及他们各自的特点。

计数器法

计数器法是限流算法里最简单也是最容易实现的一种算法。比如我们规定,对于A接口来说,我们1分钟的访问次数不能超过100个。那么我们可以这么做:在一开始的时候,我们可以设置一个计数器counter,每当一个请求过来的时候,counter就加1,如果counter的值大于100并且该请求与第一个请求的间隔时间还在1分钟之内,那么说明请求数过多;如果该请求与第一个请求的间隔时间大于1分钟,且counter的值还在限流范围内,那么就重置counter,具体算法的示意图如下:

efde1f90176005c45a774f337006d13e.png

具体的伪代码如下:

public class CounterDemo {
public long timeStamp = getNowTime();
public int reqCount = 0;
public final int limit = 100; // 时间窗口内最大请求数
public final long interval = 60000; // 时间窗口ms
public boolean grant() {
long now = getNowTime();
if (now < timeStamp + interval) {
// 在时间窗口内
reqCount++;
// 判断当前时间窗口内是否超过最大请求控制数
return reqCount <= limit;
}
else {
timeStamp = now;
// 超时后重置
reqCount = 1;
return true;
}
}
private static Long getNowTime(){
return System.currentTimeMillis();
}
}

这个算法虽然简单,但是有一个十分致命的问题,那就是临界问题,我们看下图:

e49c68349f9734cee48f92407db1a25a.png

从上图中我们可以看到,假设有一个恶意用户,他在0:59时,瞬间发送了100个请求,并且1:00又瞬间发送了100个请求,那么其实这个用户在1秒里面,瞬间发送了200个请求。我们刚才规定的是1分钟最多100个请求,也就是每秒钟最多1.7个请求,用户通过在时间窗口的重置节点处突发请求,可以瞬间超过我们的速率限制。用户有可能通过算法的这个漏洞,瞬间压垮我们的应用。

聪明的朋友可能已经看出来了,刚才的问题其实是因为我们统计的精度太低。那么如何很好地处理这个问题呢?或者说,如何将临界问题的影响降低呢?我们可以看下面的滑动窗口算法。

滑动窗口

滑动窗口,又称rolling window。为了解决这个问题,我们引入了滑动窗口算法。如果学过TCP网络协议的话,那么一定对滑动窗口这个名词不会陌生。下面这张图,很好地解释了滑动窗口算法:

2ea045803d2edb094231fca8634585f3.png

在上图中,整个红色的矩形框表示一个时间窗口,在我们的例子中,一个时间窗口就是一分钟。然后我们将时间窗口进行划分,比如图中,我们就将滑动窗口划成了6格,所以每格代表的是10秒钟。每过10秒钟,我们的时间窗口就会往右滑动一格。每一个格子都有自己独立的计数器counter,比如当一个请求在0:35秒的时候到达,那么0:30~0:39对应的counter就会加1。

那么滑动窗口怎么解决刚才的临界问题的呢?我们可以看上图,0:59到达的100个请求会落在灰色的格子中,而1:00到达的请求会落在橘黄色的格子中。当时间到达1:00时,我们的窗口会往右移动一格,那么此时时间窗口内的总请求数量一共是200个,超过了限定的100个,所以此时能够检测出来触发了限流。

我再来回顾一下刚才的计数器算法,我们可以发现,计数器算法其实就是滑动窗口算法。只是它没有对时间窗口做进一步地划分,为60s。

由此可见,当滑动窗口的格子划分的越多,那么滑动窗口的滚动就越平滑,限流的统计就会越精确。

public class CounterDemo {
public long timeStamp = getNowTime();
public int reqCount = 0;
public final int limit = 100; // 时间窗口内最大请求数
public final long interval = 6000; // 时间窗口6ms,6格
public boolean grant() {
long now = getNowTime();
if (now < timeStamp + interval) {
// 在时间窗口内
reqCount++;
// 判断当前时间窗口内是否超过最大请求控制数
return reqCount <= limit;
}
else {
timeStamp = now;
// 超时后重置
reqCount = 1;
return true;
}
}
private static Long getNowTime(){
return System.currentTimeMillis();
}
}

漏桶算法

漏桶算法,又称leaky bucket。为了理解漏桶算法,我们看一下对于该算法的示意图:

ce0dc988b6d66d08f493cf55b5a764f3.png

从图中我们可以看到,整个算法其实十分简单。首先,我们有一个固定容量的桶,有水流进来,也有水流出去。对于流进来的水来说,我们无法预计一共有多少水会流进来,也无法预计水流的速度。但是对于流出去的水来说,这个桶可以固定水流出的速率。而且,当桶满了之后,多余的水将会溢出。

我们将算法中的水换成实际应用中的请求,我们可以看到漏桶算法天生就限制了请求的速度。当使用了漏桶算法,我们可以保证接口会以一个常速速率来处理请求。所以漏桶算法天生不会出现临界问题。具体的伪代码实现如下:

public class LeakyDemo {
public long timeStamp = getNowTime();
public int capacity; // 桶的容量
public int rate; // 水漏出的速度
public Long water; // 当前水量(当前累积请求数)
public boolean grant() {
long now = getNowTime();
water = Math.max(0L, water - (now - timeStamp) * rate); // 先执行漏水,计算剩余水量
timeStamp = now;
if ((water + 1) < capacity) {
// 尝试加水,并且水还未满
water += 1;
return true;
}
else {
// 水满,拒绝加水
return false;
}
}
private static Long getNowTime(){
return System.currentTimeMillis();
}
}

令牌桶算法

令牌桶算法,又称token bucket。为了理解该算法,我们再来看一下算法的示意图:

65ec76be8b2cfe963ba2d9e79feaffac.png

从图中我们可以看到,令牌桶算法比漏桶算法稍显复杂。首先,我们有一个固定容量的桶,桶里存放着令牌(token)。桶一开始是空的,token以一个固定的速率r往桶里填充,直到达到桶的容量,多余的令牌将会被丢弃。每当一个请求过来时,就会尝试从桶里移除一个令牌,如果没有令牌的话,请求无法通过。

具体的伪代码实现如下:

public class TokenBucketDemo {
public long timeStamp = getNowTime();
public int capacity; // 桶的容量
public int rate; // 令牌放入速度
public Long tokens; // 当前令牌数量
public boolean grant() {
long now = getNowTime();
// 先添加令牌
tokens = Math.min(capacity, tokens + (now - timeStamp) * rate);
timeStamp = now;
if (tokens < 1) {
// 若不到1个令牌,则拒绝
return false;
}
else {
// 还有令牌,领取令牌
tokens -= 1;
return true;
}
}
private static Long getNowTime(){
return System.currentTimeMillis();
}
}

RateLimiter实现

对于令牌桶的代码实现,可以直接使用Guava包中的RateLimiter

@Slf4j
public class RateLimiterExample1 {
// 代表每秒最多2个
// guava限流采用的是令牌桶的方式
private static RateLimiter rateLimiter = RateLimiter.create(2);
public static void main(String[] args) {
for (int index = 0; index < 100; index++) {
            // 单位时间内获取令牌
if (rateLimiter.tryAcquire(190, TimeUnit.MILLISECONDS)) {
handle(index);
}
}
}
private static void handle(int i) {
log.info("{}", i);
}

相关变种

若仔细研究算法,我们会发现我们默认从桶里移除令牌是不需要耗费时间的。如果给移除令牌设置一个延时时间,那么实际上又采用了漏桶算法的思路。Google的guava库下的SmoothWarmingUp类就采用了这个思路。

临界问题

我们再来考虑一下临界问题的场景。在0:59秒的时候,由于桶内积满了100个token,所以这100个请求可以瞬间通过。但是由于token是以较低的速率填充的,所以在1:00的时候,桶内的token数量不可能达到100个,那么此时不可能再有100个请求通过。所以令牌桶算法可以很好地解决临界问题。下图比较了计数器(左)和令牌桶算法(右)在临界点的速率变化。我们可以看到虽然令牌桶算法允许突发速率,但是下一个突发速率必须要等桶内有足够的token后才能发生:

3f85e83b773f9ce4d89d3c2297dfe60c.png

计数器 VS 滑动窗口

计数器算法是最简单的算法,可以看成是滑动窗口的低精度实现。滑动窗口由于需要存储多份的计数器(每一个格子存一份),所以滑动窗口在实现上需要更多的存储空间。也就是说,如果滑动窗口的精度越高,需要的存储空间就越大。

漏桶算法 VS 令牌桶算法

漏桶算法和令牌桶算法最明显的区别是令牌桶算法允许流量一定程度的突发。因为默认的令牌桶算法,取走token是不需要耗费时间的,也就是说,假设桶内有100个token时,那么可以瞬间允许100个请求通过。

令牌桶算法由于实现简单,且允许某些流量的突发,对用户友好,所以被业界采用地较多。当然我们需要具体情况具体分析,只有最合适的算法,没有最优的算法。

说句题外话,springboot全家桶技术交流群可以加我微信,但是坑位有限哦,由于忙于工作,有时不能及时回复大家,请多包涵。

精彩推荐

1、SpringMVC+Spring+Mybatis支付宝支付功能实战(图文详解)

2、今天我才知道Redis有9种基本数据类型,据说只有5%的人知道,涨知识了

3、几步教你轻松搭建一个Java Web项目

4、爱奇艺的 "数据库" 选型到底有多牛逼?

5、JVM 性能调优监控工具 jps、jstack、jmap、jhat、jstat、hprof 使用详解

6、未来有望干掉 Python 和 JavaScript 的编程语言

7、冒着被开除风险也要给大家看看看这份Spring Cloud 总结

8、全面了解 Nginx 主要应用场景

点个在看少个 bug

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/355596.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mips j指令_MIPS的基本实现

MIPS核心指令集&#xff1a;指令集概括为3个指令类&#xff1a;存储器访问指令类lw&#xff0c;sw等2. 算术逻辑指令类add&#xff0c;sub等3. 转移指令类beg&#xff0c;J等指令的共同性取指令&#xff0c;送PC值给Memory&#xff1b;根据指令内容读取寄存器内容&#xff1b;除…

腾讯管家去除桌面快捷小图标

找了大半天&#xff0c;原来腾讯管家也可以&#xff1a; 废话不多说&#xff0c;动手实践吧。。。。。 打开电脑管家——电脑诊所——桌面图标——【去掉快捷方式小箭头】 就这样轻松的实现了&#xff0c;赶快试试吧&#xff01; 转载于:https://www.cnblogs.com/yjq520/p/6858…

argparse模块_Argparse:一个具体案例教会你python命令行参数解析

问题描述&#xff1a;现有一个用于数据格式转换的py脚本(多转一)&#xff0c;执行时通过命令行传入一系列的参数控制其具体运行方式&#xff0c;使满足以下要求&#xff1a;1. 必须传入需要处理的原始数据文件名2. 可以指定输入文件的格式&#xff0c;若没有指定则使用默认值&a…

Eclipse中src/main/resources配置文件启动问题

项目pom文件有做修改如下的时候&#xff0c;还没有进行mvn clean install 启动test项目中的appcontext会 可以手动清空 然后就可以了。 出现如下问题的原因是 配置文件默认输出到target/classes 下 项目启动默认也会找该目录下的配置文件 其实这里pom文件拷贝配置文件只是用于读…

仍然不安全:变成了Java 9功能的Java 6中的主要错误

sun.misc.Unsafe的未来将如何发展&#xff1f; 随着2015年即将结束&#xff0c;我们认为这将是对Java社区过去一年中最热门辩论之一进行尸检的好机会。 通过查看标题&#xff0c;您中的大多数人可能已经开始在口腔中产生酸味并在肠道中产生愤怒的感觉&#xff0c;但是如果您错…

camvid数据集使用方法_使用PyTorch处理CIFAR10数据集并显示

在训练图像分类的时候&#xff0c;我们通常会使用CIFAR10数据集&#xff0c;今天就先写一下如何展示数据集的图片及预处理。第一部分代码&#xff0c;展示原始图像&#xff1a;import numpy as npimport torch#导入内置cifarfrom torchvision.datasets import cifar#预处理模块…

rhel 8.2不识别unicode_基于tensorflow 实现端到端的OCR:二代身份证号识别

最近在研究OCR识别相关的东西&#xff0c;最终目标是能识别身份证上的所有中文汉字数字&#xff0c;不过本文先设定一个小目标&#xff0c;先识别定长为18的身份证号&#xff0c;当然本文的思路也是可以复用来识别定长的验证码识别的。本文实现思路主要来源于Xlvector的博客&am…

Lua和C++交互详细总结

转载自&#xff1a;http://www.cnblogs.com/sevenyuan/p/4511808.html 一、Lua堆栈 要理解Lua和C交互&#xff0c;首先要理解Lua堆栈。 简单来说&#xff0c;Lua和C/C语言通信的主要方法是一个无处不在的虚拟栈。栈的特点是先进后出。 在Lua中&#xff0c;Lua堆栈就是一个struc…

adf开发_ADF BC:创建绑定到业务组件的UI表

adf开发在此示例中&#xff0c;我们将展示如何创建绑定到业务组件的简单UI表&#xff08;af&#xff1a;table&#xff09;。 我再次尝试使用简单的标准在网上进行搜索&#xff1a; “如何创建绑定到业务组件ADF 11g的af&#xff1a;table” 我必须承认我没有得到我想要的答案…

java游戏热血江湖,热血江湖源码_附安装教程

释放双眼&#xff0c;带上耳机&#xff0c;听听看~&#xff01;源码里面可能有联系方式之类的不要相信&#xff0c;反正我在这里已经说过了&#xff0c;你们上当就不管我的事了还有&#xff0c;源码里面的联系方式跟我没关系!教程来了认真看&#xff01;&#xff01;&#xff0…

在没有适当上下文的情况下引发异常是一种不良习惯

Allison Anders等人的《四个房间》&#xff08;1995&#xff09;。 我不断重复同样的错误。 因此&#xff0c;该停止并制定规则以防止这种情况了。 错误不是致命的&#xff0c;但很烦人。 当查看生产日志时&#xff0c;经常会看到类似"File doesnt exist" &#xff…

内存压力测试软件_日常游戏,毫无压力,荣耀Magicbook 14锐龙版性能测试

上期蚂蚁给大家带来了荣耀Magicbook 14锐龙版的初见评测&#xff0c;本期将会带来性能的测试&#xff0c;究竟这台高性价比的电脑&#xff0c;能不能应付得了日常的游戏使用呢&#xff1f;蚂蚁这次使用的测试软件分别为&#xff1a;鲁大师、CPU-Z、Cinebench R15、CrystalDiskM…

c 解析java byte,深入解析Java编程中面向字节流的一些应用

文件输入输出流文件输入输出流 FileInputStream 和 FileOutputStream 负责完成对本地磁盘文件的顺序输入输出操作。【例】通过程序创建一个文件&#xff0c;从键盘输入字符&#xff0c;当遇到字符“#”时结束&#xff0c;在屏幕上显示该文件的所有内容import java.io.*;class e…

cdatabase读取excel第一行数据_pandas读取excel数据并对重复数据进行标记或者删除

pandas读取excel数据并对重复数据进行标记或者删除​mp.weixin.qq.compandas通常在读取excel数据之后&#xff0c;如果需要进行去重&#xff0c;有两种方式&#xff0c;一种是进行标记&#xff0c;另一种是在pandas中直接去重如下图所示&#xff0c;excel数据&#xff1a;&…

java 7.函数-递归_带有谓词的Java中的函数样式-第2部分

java 7.函数-递归在本文的第一部分中&#xff0c;我们介绍了谓词&#xff0c;这些谓词通过具有返回true或false的单一方法的简单接口&#xff0c;为Java等面向对象的语言带来了函数式编程的某些好处。 在第二部分和最后一部分中&#xff0c;我们将介绍一些更高级的概念&#xf…

apk改之理_一份礼物.apk-O泡果奶的逆向分析

事情起因是震惊全国大学生的1013事件&#xff01;&#xff01;&#xff01;刚好看到社团群里在讨论这个&#xff0c;于是就发挥专业特长分析一下拿到apk ,第一步肯定先放到虚拟机里跑一下看下效果emmm这似曾相识的页面,这熟悉的音量,唯一变化的就是音乐变成了O泡果奶的魔性洗脑…

python彩色螺旋线_解决python彩色螺旋线绘制引发的问题

彩色螺旋线的绘制代码如下&#xff1a; import turtle import time turtle.pensize(2) turtle.bgcolor(black) colors [red, yellow, purple, blue] turtle.tracer(False) for x in range(400): turtle.forward(2*x) turtle.color(colors[x % 4]) turtle.left(91) turtle.trac…

ID3和C4.5分类决策树算法 - 数据挖掘算法(7)

&#xff08;2017-05-18 银河统计&#xff09;决策树(Decision Tree&#xff09;是在已知各种情况发生概率的基础上&#xff0c;通过构成决策树来判断其可行性的决策分析方法&#xff0c;是直观运用概率分析的一种图解法。由于这种决策分支画成图形很像一棵树的枝干&#xff0c…

mysql 聚簇索引和非聚簇索引_图文并茂,说说MySQL索引

点击上方 小伟后端笔记 &#xff0c;选择 星标 公众号重磅资讯、干货&#xff0c;第一时间送达作者&#xff1a;小小木的博客来源&#xff1a;cnblogs.com/wyc1994666/p/10831039.html序开门见山&#xff0c;直接上图&#xff0c;下面的思维导图即是现在要讲的内容&#xff0c;…

mysql数据库日志截断,MySQL基础(十一):查询截取分析

下面是小凰凰的简介&#xff0c;看下吧&#xff01;&#x1f497;人生态度&#xff1a;珍惜时间&#xff0c;渴望学习&#xff0c;热爱音乐&#xff0c;把握命运&#xff0c;享受生活&#x1f497;学习技能&#xff1a;网络 -> 云计算运维 -> python全栈( 当前正在学习中…