Open nereuschen opened 8 years ago
爆发持续时间:T=b/(m-2r)这个时间是怎么计算的
@nereuschen 如果我想要 10秒 100 次限频,如果第 1 秒用完 100 次,剩下 9 秒都需要等待。这种使用哪种限流算法比较好。
@nereuschen 如果我想要 10秒 100 次限频,如果第 1 秒用完 100 次,剩下 9 秒都需要等待。这种使用哪种限流算法比较好。
感觉你说的就是时间窗口内burst的问题,但是总得有个窗口大小。比如你10秒限制100次,听上去好像跟20秒限制200次差不多,但是我20秒限制200次我可以第一秒用掉200次,但是你10秒100次就不行。所以感觉跟1秒10次差不多?只不过看你允许的burst到底有多大。
所以我理解第一秒就填充所有时间窗口内的令牌是不是就符合你的要求。 比如你的窗口大小是10秒,我初始化的时候就放了100个,然后到10秒结束的时候就放100个,桶最大是100,溢出丢弃。但是这个10秒的意义可能就是允许的burst到底有多大了。
限流
通常限流主要是限制并发数以及QPS,从而避免异常流量对系统的冲击;
并发数和QPS是紧密相关的,可以参考Little's Law(律特法则):L = λW (proven 1961)
最粗暴的实现方式是每执行一次delay一定时间,从而达到限制QPS的效果
比如,我们想以最大的QPS为10去处理1000个业务逻辑,那么代码很可能这么写
这种写法有什么弊端?
delay的预估时间不精确
为什么是100ms呢?其实QPS还依赖于doSomething的执行时间;
如果doSomething的执行时间短,比如是1ms,那么QPS最接近10;
如果doSomething的执行时间长,比如是500,那么QPS还不到2
通常情况下,doSomething的执行时间是非常不确定的,所以我们很难给出一个相对精确的delay时间
多线程下如何控制呢?
显然,由于doSomething的执行时间不确定,会导致没法在较短的时间内处理完1000个业务逻辑
所以必须借助线程池,通过并行去处理,但是QPS也必须控制在10以内
WorkThread的代码很有可能这么写,然后启多少个线程合适呢?
因此,采用delay这种粗暴的实现方式很难将QPS稳定地控制在10
要想解决这个问题,就必须做到
固定速率
在1秒这个时间窗口内,将执行的次数控制在10,这样速率是固定的
不允许突发情况 在任何1秒时间窗口内,不能超过10个
所以不能出现以下突发情况:比如QPS=2,0.0s到1.0s和1.0s到2.0s分别指允许执行2次,
但是在0.08秒、0.09秒、1.01秒、1.02秒执行了4次,所以在0.5秒到1.5秒这期间的1秒,
QPS是4并且超过了2
Leaky bucket(漏桶算法)正好解决了这些问题
Token bucket(令牌桶算法)和它一样,都是最常见的两种限流算法
Leaky bucket漏桶算法
算法实现
不断的往桶里面注水,无论注水的速度是大还是小,水都是按固定的速率往外漏水;
如果桶满了,水会溢出;
特点
Token bucket令牌桶算法
算法实现
令牌发送:每秒往桶里面发送r个令牌(token)
桶的容量:桶中最多可以存放b个token;当放入的token数量超过b时,新放入的token会被丢弃
请求访问:每次请求访问时先check桶中有没有剩余的令牌
由于每秒会不断地往桶中放r个token,所以当无业务请求需处理时,桶中的token数量会不断增加,止到达到桶的容量b为止
特点
令牌可以积累
桶中最大的令牌数是b,也是可以积累的最大令牌数
允许突发流量
桶中token可以积累到n(b<=n<=0),此时如果有n个突发请求同时到达,这n个请求是可以同时允许处理的
Leakly bucket VS Token bucket
具体实现
Guava的RateLimiter实现
在Guava中RateLimiter的实现有两种:
SmoothBursty
和SmoothWarmUp
补充类图
SmoothBursty
以下场景,调用acquire()时何时有返回值?
QPS=1,4个线程在以下时间点依次调用acquire()方法
存储token,具备处理突发请求的能力
当RateLimiter空闲时(无请求需处理),可以积累一定时间内的permits(token)
比如,当QPS=2,maxBurstSeconds=10时,也就意味着如果RateLimiter空闲,那么可以积累
10秒的permits,也就是102=20个(maxPermits = maxBurstSeconds permitsPerSecond),
当下次有请求过来时,可以立即取走这20个permits,从而可以达到突发请求的效果
预热期间QPS会平滑地逐步加速到最大的速率(也就是QPS)
简单用例代码
运行结果
RateLimiter的结论
对于SmoothBurst [RateLimiter.create(permitsPerSecond)] 而言,是基于Token bucket算法,因此
对于SmoothWarmingUp [RateLimiter.create(permitsPerSecond,warmupPeriod,timeUnit)]而言,是基于Leaky bucket算法,因此
获取permit时是否会block线程
如果你在双十一0点0分之后,购物的时候遇到这个页面,那么亲,你被限流了,必须排到等待
用户洪峰
特点:
为了提升用户体验,需要支持爆发量,所以采用令牌桶算法
允许最大的访问速率:b+r 爆发持续时间:T=b/(m-2r) 爆发量:L=T*r
回调洪峰
特点:
采用漏桶算法
限流框架
分成几个主要模块
监控模块
收集数据、实时监控和反馈分析
决策模块
什么场景使用什么限流方式;比如,用户洪峰采用令牌桶算法,
系统回调使用漏桶算法
规则变更模块
动态调整令牌桶容量和产生令牌的速率
限流模块
如何处理被限流的请求,是排队还是丢弃
限流框架的处理流程
小米秒杀
TODO
分层架构
Nginx限流
Nginx限流模块
参数说明
$binary_remote_addr
采用二进制表示访问的IP地址
zone=login:10m
表示定义了一个名为login的内存共享区,其占用内存大小为10M。
10M的内存可以存放16W个二进制表示的IP地址。
rate=1r/s
就是QPS;对同一个IP地址而言,速率被限制为每秒1次请求
burst
如果同一个IP访问的请求超过rate后,没有超过桶上限burst的请求会被放入桶中,等待被处理。
这个例子中桶的容量是5。
nodelay
单个IP的在某时间段内平均QPS未超过设定的rate,那么桶中的请求会立即被处理,
此时的瞬间QPS高于设定的rate;如果在某时间段内平均的QPS超过rate,那么超过
rate的请求会直接被拒绝,直接返回503
从示例配置信息中可以看出:
对访问/account/login/的请求根据访问的IP进行了限速,QPS是10;
对于同一个IP地址只允许每秒10个请求访问该URL;
如果请求量大于QPS(10),会立即返回503(因为配置了参数nodelay);
如果没有配置参数nodelay,那么超过QPS访问量的请求会先积压到桶中(可以积压5个),
如果桶满了,就返回503。
重点说明一下对burst+nodelay的理解
针对这个case的理解:
(1)在[10:00:01到10:00:45]期间,每隔15秒会有15个并发请求,虽然在这些时间点上并发
请求大于rate,但在每个15秒内其QPS是1,依旧未超过rate,所以每次的15个并发请求能够
被成功处理
(2)在[11:00:01到11:00:05]期间,每秒15个并发请求,在11:00:01时,请求量未超过burst,
并且由于配置了nodelay,所以当时15个请求立即被执行了;而在其他的时间点上QPS大于
rate,所以超出rate的请求直接被拒绝
Nginx+lua
REDIS
http://redis.io/commands/INCR#pattern-rate-limiter https://github.com/UsedRarely/spring-rate-limit https://github.com/colley/spring-ratelimiter https://github.com/nlap/dropwizard-ratelimit https://github.com/sudohippie/throttle https://github.com/coveo/spillway https://github.com/marcosbarbero/spring-cloud-starter-zuul-ratelimit
API 调用次数限制实现
Java Rate-Limiting API
ZK
Distributed Atomic Long
Shared Semaphore
HTTP&DUBBO&MQ
可以借助在filter中通过RateLimiter来实现正对单机的限流
DB
淘宝双十一秒杀业务
秒杀特点:瞬时并发高、数据一致性高、热点更新频率高
大量更新DB中的同一条记录时,会产生锁等待,导致DB性能急剧下降
当大量的并发更新同一条记录时,使用排队的方式来保证高并发下热点记录更新依然能保持
较好的性能,为threads_running设置一个硬上线,当并发超过此值是,拒绝执行sql,
保护MySQL,我们将这个称之为高水位限流,这样就给数据库加上了一层限流的功能,使得
数据库不被瞬间的高爆发请求打爆。
高水位限流实现:
监控系统status变量threads_running,当满足拒绝条件,拒绝执行sql,返回用户:
MySQL Server is too busy,判断逻辑在dispatch_command中,sql解析之后。
增加的系统variables:
拒绝必要条件:
以下情况不拒绝:
参考资料
Token bucket算法图片
Better Rate Limiting in .NET
[阿里双11系统管控调度架构与实践.pdf]()
Nginx限流模块