`
m635674608
  • 浏览: 4939869 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

接口限流算法总结

 
阅读更多

背景

曾经在一个大神的博客里看到这样一句话:在开发高并发系统时,有三把利器用来保护系统:缓存、降级和限流。那么何为限流呢?顾名思义,限流就是限制流量,就像你宽带包了1个G的流量,用完了就没了。通过限流,我们可以很好地控制系统的qps,从而达到保护系统的目的。本篇文章将会介绍一下常用的限流算法以及他们各自的特点。

算法介绍

计数器法

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

2016-09-01_20:31:28.jpg

具体的伪代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class CounterDemo {
public long timeStamp = getNowTime();
public int reqCount = 0;
public final int limit = 100; // 时间窗口内最大请求数
public final long interval = 1000; // 时间窗口ms
public boolean grant() {
long now = getNowTime();
if (now < timeStamp + interval) {
// 在时间窗口内
reqCount++;
// 判断当前时间窗口内是否超过最大请求控制数
return reqCount <= limit;
}
else {
timeStamp = now;
// 超时后重置
reqCount = 1;
return true;
}
}
}

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

2016-09-01_20:35:21.jpg

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

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

滑动窗口

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

2016-09-01_20:42:46.jpg

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

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

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

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

漏桶算法

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

2016-09-02_09:57:32.jpg

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class LeakyDemo {
public long timeStamp = getNowTime();
public int capacity; // 桶的容量
public int rate; // 水漏出的速度
public int water; // 当前水量(当前累积请求数)
public boolean grant() {
long now = getNowTime();
water = max( 0, water - (now - timeStamp) * rate); // 先执行漏水,计算剩余水量
timeStamp = now;
if ((water + 1) < capacity) {
// 尝试加水,并且水还未满
water += 1;
return true;
}
else {
// 水满,拒绝加水
return false;
}
}
}

令牌桶算法

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

2016-09-02_10:10:24.jpg

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

具体的伪代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class TokenBucketDemo {
public long timeStamp = getNowTime();
public int capacity; // 桶的容量
public int rate; // 令牌放入速度
public int tokens; // 当前令牌数量
public boolean grant() {
long now = getNowTime();
// 先添加令牌
tokens = min(capacity, tokens + (now - timeStamp) * rate);
timeStamp = now;
if (tokens < 1) {
// 若不到1个令牌,则拒绝
return false;
}
else {
// 还有令牌,领取令牌
tokens -= 1;
return true;
}
}
}

相关变种

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

临界问题

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

2016-09-02_14:40:58.jpg

总结

计数器 VS 滑动窗口

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

漏桶算法 VS 令牌桶算法

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

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

 

https://my.oschina.net/sbcagf/blog/783082

分享到:
评论

相关推荐

    开涛高可用高并发-亿级流量核心技术

    4.1 限流算法 67 4.1.1 令牌桶算法 67 4.1.2 漏桶算法 68 4.2 应用级限流 69 4.2.1 限流总并发/连接/请求数 69 4.2.2 限流总资源数 70 4.2.3 限流某个接口的总并发/请求数 70 4.2.4 限流某个接口的时间窗请求数 70 ...

    不加密Google Guava视频教程.txt

    ├─Google Guava 第25讲-Guava之RateLimiter在漏桶限流算法中的使用.wmv ├─Google Guava 第26讲-Guava之RateLimiter令牌桶算法的使用.wmv ├─Google Guava 第27讲-ListenableFuture,FutureCallBack讲解.wmv ...

    java 面试题 总结

    Hashtable和HashMap采用的hash/rehash算法都大概一样,所以性能不会有很大的差异。 12、final, finally, finalize的区别。  final 用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。 ...

    java面试题,180多页,绝对良心制作,欢迎点评,涵盖各种知识点,排版优美,阅读舒心

    【基础】接口是否可继承(extends)接口?抽象类是否可实现(implements)接口?抽象类是否可继承具体类(concrete class) 30 【基础】一个".java"源文件中是否可以包含多个类(不是内部类)?有什么限制? 30 ...

    网络安全实验报告完整.doc

    加密:考虑到DES的安全性问题,加密消息是采用AES方式加密的,本系统采用的是 C#封装好的AES程序,调用接口,实现对明文的加密。解密同理。 认证:认证是将明文哈希散列后采用RSA算法对其进行签名,再用公钥加密...

    计算机二级C语言考试题预测

    (65) 软件设计包括软件的结构、数据接口和过程设计,其中软件的过程设计是指(B) 注:P73 A. 模块间的关系 B. 系统结构部件转换成软件的过程描述 C. 软件层次结构 D. 软件开发过程 (66) 为了避免流程图在描述程序逻辑...

    java安卓开发外卖订餐系统课程设计.doc

    5 5.3 安全性需求 6 5.4 软件质量属性 6 5.5 业务规则 6 6 分析模型 6 6.1 数据流图 6 6.2 用例图 9 6.3系统时序图和协作图 12 6.4系统活动图 16 三、设计报告 19 1 设计概述 19 1.1 限制与约束 19 1.2设计原则和...

    二级C语言公共基础知识

    总结 D. 都不正确 (18) 下述关于数据库系统的叙述中正确的是______。(A) A. 数据库系统减少了数据冗余 B. 数据库系统避免了一切冗余 C. 数据库系统中数据的一致性是指数据类型的一致 D. 数据库系统比文件系统能管理...

    ASP.NET4高级程序设计(第4版) 3/3

    1.3 总结 15 第2章 Visual Studio 16 2.1 Visual Studio 16 2.1.1 网站和Web项目 17 2.1.2 创建无项目文件的网站 18 2.1.3 设计网页 21 2.2 Visual StudioIDE 26 2.2.1 解决方案资源管理器 28 ...

    ASP.NET4高级程序设计第4版 带目录PDF 分卷压缩包 part1

    1.3 总结 第2章 Visual Studio 2.1 Visual Studio 2.1.1 网站和Web项目 2.1.2 创建无项目文件的网站 2.1.3 设计网页 2.2 Visual StudioIDE 2.2.1 解决方案资源管理器 2.2.2 文档窗口 2.2.3 工具...

    软件工程知识点

    用于描述系统对数据的加工过程,其图形符号是一些具有抽象意义的逻辑符号,主要的图形符号包括:数据接口、数据流、数据存储和数据处理。可以依靠数据流图来实现从用户需求到系统需求的过渡。结构化分析就是基于数据...

    自己动手写搜索引擎(罗刚著).doc

    5.1.4 查找词典算法 95 5.1.5 最大概率分词方法 98 5.1.6 新词发现 101 5.1.7 隐马尔可夫模型 102 5.2 语法解析树 104 5.3 文档排重 105 5.4 中文关键词提取 106 5.4.1 关键词提取的基本方法 106 5.4.2 关键词提取的...

    TCP_IP详解卷1

    1.15 应用编程接口 12 1.16 测试网络 13 1.17 小结 13 第2章 链路层 15 2.1 引言 15 2.2 以太网和IEEE 802封装 15 2.3 尾部封装 17 2.4 SLIP:串行线路IP 17 2.5 压缩的SLIP 18 2.6 PPP:点对点协议 18 2.7 环回接口...

    TCPIP详解卷[1].part04

    1.15 应用编程接口 12 1.16 测试网络 13 1.17 小结 13 第2章 链路层 15 2.1 引言 15 2.2 以太网和IEEE 802封装 15 2.3 尾部封装 17 2.4 SLIP:串行线路IP 17 2.5 压缩的SLIP 18 2.6 PPP:点对点协议 18 2.7 环回接口...

    asp.net知识库

    泛型技巧系列:避免基类及接口约束 New Article 不该用Generics实现Abstract Factory的理由 C#2.0-泛型 C#2.0-extern C#2.0-可空类型 C#2.0-分部类 C#2.0-迭代器 C#2.0 的新增功能学习 泛型的序列化问题 .NET 2.0 ...

    TCPIP详解卷[1].part09

    1.15 应用编程接口 12 1.16 测试网络 13 1.17 小结 13 第2章 链路层 15 2.1 引言 15 2.2 以太网和IEEE 802封装 15 2.3 尾部封装 17 2.4 SLIP:串行线路IP 17 2.5 压缩的SLIP 18 2.6 PPP:点对点协议 18 2.7 环回接口...

    TCPIP详解卷[1].part03

    1.15 应用编程接口 12 1.16 测试网络 13 1.17 小结 13 第2章 链路层 15 2.1 引言 15 2.2 以太网和IEEE 802封装 15 2.3 尾部封装 17 2.4 SLIP:串行线路IP 17 2.5 压缩的SLIP 18 2.6 PPP:点对点协议 18 2.7 环回接口...

    TCPIP详解卷[1].part05

    1.15 应用编程接口 12 1.16 测试网络 13 1.17 小结 13 第2章 链路层 15 2.1 引言 15 2.2 以太网和IEEE 802封装 15 2.3 尾部封装 17 2.4 SLIP:串行线路IP 17 2.5 压缩的SLIP 18 2.6 PPP:点对点协议 18 2.7 环回接口...

    TCPIP详解卷[1].part06

    1.15 应用编程接口 12 1.16 测试网络 13 1.17 小结 13 第2章 链路层 15 2.1 引言 15 2.2 以太网和IEEE 802封装 15 2.3 尾部封装 17 2.4 SLIP:串行线路IP 17 2.5 压缩的SLIP 18 2.6 PPP:点对点协议 18 2.7 环回接口...

Global site tag (gtag.js) - Google Analytics