又拍云张聪:OpenResty 动态流控的几种姿式

2019 年 1 月 12 日,由又拍云、OpenResty 中国社区主办的 OpenResty × Open Talk 全国巡回沙龙·深圳站圆满结束,又拍云首席架构师张聪在活动上作了《 OpenResty 动态流控的几种姿式 》的分享。
OpenResty x Open Talk 全国巡回沙龙是由 OpenResty 社区、又拍云发起的,为促进 OpenResty 在技术圈的发展,增进 OpenResty 使用者的交流与学习的系列活动,活动将会陆续在深圳、北京、上海、广州、杭州、成都、武汉等地举办,欢迎你们关注。
张聪,又拍云首席架构师,多年 CDN 行业产品设计、技术开发和团队管理相关经验,我的技术方向集中在 Nginx、OpenResty 等高性能 Web 服务器方面,国内 OpenResty 技术早期推广者之一;目前担任又拍云内容加速部技术负责人,主导又拍云 CDN 技术平台的建设和发展。html

 

如下是分享全文:node

 

你们下午好,今天我主要和你们分享“在 OpenResty 上如何作动态的流量控制”,将会从如下几个方面来介绍:git

  • Nginx 如何作流控,介绍几种经典的速率和流量控制的指令和方法;
  • OpenResty 如何动态化作流控;
  • OpenResty 动态流控在又拍云的业务应用。

又拍云与 OpenResty 结缘

我目前在又拍云负责 CDN 的架构设计和开发工做,又拍云早在 2012 年就开始接触 OpenResty ,当时咱们作调研选型,部分项目考虑用 Lua 来实现,在此以前是基于 Nginx C 模块来作业务开发,一个防盗链模块就好几千行代码,改为 Lua 以后大量减小了代码,而且整个开发的效率、维护的复杂度都下降了。此外咱们经过测试和性能对比,几乎没有多的损耗,由于在这一层主要是字符串的处理,甚至在 LuaJIT 加速的状况下有不少的调用,比咱们原先用 C 写的函数还高效得多。github

 

目前又拍云整个 CDN 代理层系统、对外开放的 API 系统、数据中心的网关系统、分布式云存储代理层、逻辑层所有用 ngx_lua 进行了深度的改造,又拍云内部几个不一样业务的团队都在 OpenResty 技术栈上有多年的实践和经验积累。redis

又拍云开放了一个 upyun-resty 的仓库(https://github.com/upyun/upyun-resty),咱们内部孵化的开源项目以及对社区的补丁修复等都会发布在这个仓库。你们若是对又拍云这块的工做感兴趣能够关注这个仓库,咱们今年还会陆续把内部使用很是成熟的一些库放出来,包括今天讲的两个与限速有关的 Lua 库也已经开源出来了。算法

什么是流控以及为何要作流控

一、什么是流控

今天的主题,首先是针对应用层,尤为是 7 层的 HTTP 层,在业务流量进来的时候如何作流量的疏导和控制。我我的对“流控”的理解(针对应用层):后端

(1) 流控一般意义下是经过一些合理的技术手段对入口请求或流量进行有效地疏导和控制,从而使得有限资源的上游服务和整个系统能始终在健康的设计负荷下工做,同时在不影响绝大多数用户体验的状况下,整个系统的“利益”最大化。安全

由于后端资源有限,不管考虑成本、机器或者系统自己的瓶颈,不可能要求上游系统可以承受突发的流量,而须要在前面作好流量的控制和管理。服务器

有时候咱们不得不牺牲少数的用户体验,拒绝部分请求来保证绝大多数的请求正常地服务,其实没有完美可以解决全部问题的方案,因此这个在流量控制中要结合咱们对业务的理解须要学会作取舍。网络

(2) 流控有时候也是在考虑安全和成本时的一个手段。

除了上面的通用场景,流控也在安全和成本上作控制。好比敏感帐号的登陆页面,密码失败次数多了就禁掉它,不容许反复暴力尝试;好比咱们的上游带宽有限,须要确保传输的带宽在较低的水平中进行,不要把线路跑满,由于跑满有可能涉及到一些成本超支的问题等。

二、为何要流控

针对上面的描述,下面介绍一些流控跟速率限制的方法。

(1)为了业务数据安全,针对关键密码认证请求进行有限次数限制,避免他人经过字典攻击暴力破解。

为了数据安全,咱们会对一些敏感的请求尝试访问作累计次数的限制,好比必定时间内你输错了三次密码,接下来的几个小时内就不让你来尝试了,这是一种很常见的手段。若是没有这样的保护,攻击者会不断试你的密码,调用这个敏感的接口,最终可能会让他试出来,因此这是须要保护的。

(2)在保障正经常使用户请求频率的同时,限制非正常速率的恶意 DDoS 攻击请求,拒绝非人类访问。

咱们须要保障一个 API 服务正常的请求流量,可是拒绝彻底恶意的 DDoS 的攻击、大量非人类访问。这也是在最前面这层须要作的事情,不然这些请求串进上游,不少后端的服务器确定是抗不住的。

(3)控制上游应用在同一时刻处理的用户请求数量,以避免出现并发资源竞争致使体验降低。

咱们须要控制上游只能同时并发处理几个任务或几个请求,此时关心的是“同时”,由于它可能有内部的资源竞争,或者有一些冲突,必须保证这个服务“同时”只能知足几个用户的处理。

(4)上游业务处理能力有限,若是某一时刻累计未完成任务超过设计最大容量,会致使总体系统出现不稳定甚至持续恶化,须要时刻保持在安全负荷下工做。

当咱们整个上游系统的弹性伸缩能力还不错,它会有一个设计好的最大容量空间,即最多累计可以承受多大量的请求流入。若是超过它最大可处理范围性能就会降低。例如一个任务系统每小时可以完成 10 万个任务,若是一个小时内任务没有堆积超过 10 万,它都可以正常处理;但某一个小时出现了 20 万请求,那它处理能力就会降低,它本来一小时能处理 10 万,此时可能只能处理 5 万或 2 万甚至更少,性能变得不好,持续恶化,甚至最终致使崩溃。

所以,咱们须要对这样的流量进行疏导,确保后端系统可以健康地运行,若是它每小时最多只能跑 10 万的任务,那么不管多大的任务量,每小时最多都应只让它跑 10 万的量,而不是由于量超过了,反而最后连 10 万都跑不到。

(5)集群模式下,负载均衡也是流控最基础的一个环节,固然也有些业务没法精确进行前置负载均衡,例如图片处理等场景就容易出现单点资源瓶颈,此时须要根据上游节点实时负载状况进行主动调度。

在作流量管理时,负载均衡是很基础的。若是一个集群基本负载均衡都没作好,流量仍是偏的,上游某个节点很容易在集群中出现单点,这时去作流量控制就有点不合适。流量控制,首先在集群模式下要先作好负载均衡,在流量均衡的状况下再去作流量控制,识别恶意的流量。而不要前面的负载均衡都没作好,流量都集中在某一台机器上,那你在这一台上去作控制,吃力不讨好。

(6)在实际的业务运营中,每每出于成本考虑,还须要进行流量整形和带宽控制,包括下载限速和上传限速,以及在特定领域例如终端设备音视频播放场景下,根据实际码率进行针对性速率限制等。

出于成本的考虑,咱们会对一些流量进行控制。好比下载限速是一个很常见的场景,终端用户尤为是移动端,在进行视频的播放,按正常的码率播放已经足够流畅。若是是家庭带宽,下载速度很快,打开没一下子就把电影下载完成了,但实际上没有必要,由于电影播放已经足够流畅,一会儿把它下载完浪费了不少流量。特别地对于音视频的内容提供商,他以为浪费了流量并且用户体验差很少,因此此时通常会对这些文件进行下载限速。

经典的 Nginx 方式实现流量控制

 

Nginx 你们都很是熟悉,特别是数据中心或后端的服务,无论是什么语言写的,可能你也不明白为何要这么作,但前面套一个 Nginx 老是让人放心一点,由于在这么多年的发展中,Nginx 已经默默变成一个很是基础可靠的反顶流量最外层入口的在向代理服务器,基本上不少开发者甚至感知不到它的存在,只知道运维帮忙前面架了一个转发的服务。

因此,若是咱们要作流量管理,应该尽可能往前作,不要等流量转发到后面,让应用服务去作可能已经来不及了,应用服务只须要关心业务,这些通用的事情就让上层的代理服务器完成。

一、Nginx 请求速率限制

(1)limit_req 模块

△ ngx_http_limit_req_module

limit_req 是 Nginx 最经常使用的限速的模块,上图是一个简单的配置,它基于来源 IP 做为惟一的 Key,针对某个惟一的来源 IP 作速率控制,这里的速率控制配置是 5r/s( 1 秒内容许 5 个请求进来),基于这个模块的实现,再解释一下 5r/s,即每隔 200ms 可以容许进来一个请求,每一个请求的间隔必须大于 200ms,若是小于 200ms 它就会帮你拒绝。

使用起来很简单,配置一个共享内存,为了多个 worker 能共享状态。具体能够在 location 作这样的配置,配完以后就会产生限速的效果。

 
 

△ limit_req 请求示意图

上图能够更加直观地了解这个机制,图中整个灰色的时间条跨度是 1s,我按 200ms 切割成了五等份,时间条上面的箭头表明一个请求。0s 的时候第一次请求过来直接转发到后面去了;第二次间隔 200ms 过来的请求也是直接转发到上游;第三个请求也同样;第四个请求(第一个红色箭头)在 500ms 左右过来,它跟前一个请求的时间间隔只有 100ms,此时模块就发挥做用,帮你拒绝掉,后面也是相似的。

总结一下 limit_req 模块的特色:

  • 针对来源IP,限制其请求速率为 5r/s。
  • 意味着,相邻请求间隔至少 200ms,不然拒绝。

但实际业务中,偶尔有些突增也是正常的。

这样简单地使用,不少时候在实际的业务中是用得不舒服的,由于实际业务中不少场景是须要有一些偶尔的突增的,这样操做会过于敏感,一超过 200ms 就弹,绝大多数系统都须要容许偶尔的突发,而不能那么严格地去作速率限制。

(2)brust 功能参数

这样就引出了 limit_req 模块中的一个功能参数 brust(突发),为了方便演示,这里设置 brust=4,表示在超过限制速率 5r/s 的时候,同时最多容许额外有 4 个请求排队等候,待平均速率回归正常后,队列最前面的请求会优先被处理。

 

△ brust=4,limit_req 请求示意图

在 brust 参数的配合下,请求频率限制容许必定程度的突发请求。设置为 4 次后,表示在超过 5r/s 的瞬间,原本要直接弹掉的请求,如今系统容许额外有 4 个位置的排队等候,等到总体的平均速率回归到正常后,排队中的 4 个请求会挨个放进去。对于上游的业务服务,感知到的始终是 200ms 一个间隔进来一个请求,部分提早到达的请求在 Nginx 这侧进行排队,等到请求能够进来了就放进来,这样就容许了必定程度的突发。

如上图,时间条上面第四个请求跟第三个,间隔明显是小于 200ms ,按原来的设置应该就直接拒绝了,但如今咱们容许必定程度的突发,因此第四个请求被排队了,等时间慢慢流转到 600ms 的时候,就会让它转发给后端,实际它等待了 100ms。下面也是挨个进来排队,第五个请求进来它排队了 200ms,由于前面的时间片已经被第四个请求占用了,它必须等到下一个时间片才能转发。能够看到上游服务接收到的请求间隔永远是恒定的 200ms。

在已经存在 4 个请求同时等候的状况下,此时“马上”过来的请求就会被拒绝。上图中能够看到从第五个请求到第九个请求,一共排队了 5 个请求,第十个请求才被拒绝。由于时间一直是在流动的,它总体是一个动态排队的过程,解决了必定程度的突发,固然太多突发了仍是会处理的。

虽然容许了必定程度的突发,但有些业务场景中,排队致使的请求延迟增长是不可接受的,例如上图中突发队列队尾的那个请求被滞后了 800ms 才进行处理。对于一些敏感的业务,咱们不容许排队过久,由于这些延时根本就不是在进行有效处理,它只是等候在 Nginx 这侧,这时不少业务场景可能就接受不了,这样的机制咱们也须要结合新的要求再优化。可是若是你对延时没有要求,容许必定的突发,用起来已经比较舒服了。

(3)nodealy 功能参数

limit_req 模块引入了 nodelay 的功能参数,配合 brust 参数使用。nodelay 参数配合 brust=4 就可使得突发时须要等待的请求当即获得处理,与此同时,模拟一个插槽个数为 4 的“令牌”队列(桶)。

△ brust=4 配合 nodelay 的 limit_req 请求示意图

原本突发的请求是须要等待的,有了 nodelay 参数后,本来须要等待的 4 个请求一旦过来就直接转发给后端,落到后端的请求不会像刚刚那样存在严格的 200ms 间隔,在比较短的时间内就会落下去,它实际上没有在排队,请求进来直接往上游就转发,不事后续超出队列突发的请求仍然是会被限制的。

为了可以比较好理解这个场景,引入一个虚拟“桶”。从抽象的角度描述下这个过程,该“令牌”桶会每隔 200ms 释放一个“令牌”,空出的槽位等待新的“令牌”进来,若桶槽位被填满,随后突发的请求就会被拒绝。

原本第六到第九这 4 个请求是排队等候在 Nginx 一侧,如今它们没有等待直接下去了,能够理解为咱们拿出了 4 个虚拟的令牌放入一个“桶”,4 个令牌模拟这 4 个请求在排队。“桶”每隔 200ms 就会释放出一个令牌,而一旦它释放出一个,新的虚拟令牌就能够过来,若是它还没释放出,“桶”是满的,这时请求过来仍是会被拒绝。总而言之就是真实的请求没有在排队,而是引入了 4 个虚拟的令牌在排队,在它满的状况下是不容许其它请求进来。

如此,能够保证这些排队的请求不须要消耗在无谓的等待上,能够直接进去先处理,而对于后面超过突发值的请求仍是拒绝的。这样就达到了折中,对于上游,它须要更短的时间间隔来处理请求,固然这须要结合业务来考虑,这里只是提供了一种方式和特定的案例。

总结,在这个模式下,在控制请求速率的同时,容许了必定程度的突发,而且这些突发的请求因为不须要排队,它可以当即获得处理,改善了延迟体验。

(4)delay 功能参数

Nginx 最新的版本 1.15.7 增长了 delay 参数,**支持 delay=number 和 brust=number 参数配合使用。 **delay 也是一个独立的参数,它支持 number(数量)的配置,和突发的数量配置是同样的,这两个参数解决的问题更加细致,通用场景中遇到的可能会少一点。

这个功能参数是这样描述的:在有些特定场景下,咱们既须要保障正常的少许关联资源可以快速地加载,同时也须要对于突发请求及时地进行限制,而 delay 参数能更精细地来控制这类限制效果。

好比网站的页面,它下面有 4-6 个 JS 、CSS 文件,加载页面时须要同时快速地加载完这几个文件,才能确保整个页面的渲染没有问题。但若是同时超过十个并发请求在这个页面上出现,那可能就会是非预期的突发,由于一个页面总共才 4-6 个资源,若是刷一下同时过来的是 12 个请求,说明用户很快地刷了屡次。在这种状况下,业务上是要控制的,就能够引入了 delay 参数,它可以更精细地来控制限制效果。

在上面的例子中,一个页面并发加载资源加载这个页面的时候会跑过来 4-6 个请求,某个用户点一下页面,服务端收到的是关于这个页面的 4-6 个并发请求,返回给它;若是他很快地点了两下,咱们以为须要禁止他很快地刷这个页面刷两次,就须要把超过并发数的这部分请求限制掉,但 burst 设置过小又担忧有误伤,设置太大可能就起不到任何效果。

此时,咱们能够配置一个策略,总体突发配置成 12,超过 12 个确定是须要拒绝的。而在 12 范围内,咱们但愿前面过来的 4-6 个并发请求可以更快地加载,不要进行无效地等待,这里设置 delay=8 ,队列中前 8 个等候的请求会直接传给上游,而不会排队,而第 8 个以后的请求仍然会排队,但不会被直接拒绝,只是会慢一些,避免在这个尺度内出现一些误伤,同时也起到了必定限制效果(增大时延)。

上面 4 点都是讲 Nginx 怎么进行请求速率限制,简单总结一下,速率就是针对连续两个请求间的请求频率的控制,包括容许必定程度的突发,以及突发排队是否须要延后处理的优化,还有后面提到的 delay 和 brust 的配合使用。

二、Nginx 并发链接数限制

 

△ ngx_http_limit_conn_module

Nginx 有一个模块叫 limit_conn,在下载的场景中,会出现几个用户同时在下载同一个资源,对于处理中的请求,该模块是在读完请求头所有内容后才开始计数,好比同时容许在线 5 人下载,那就限制 5 个,超过的 503 拒绝。特别地,在 HTTP/2 和 SPDY 协议下,每个并发请求都会看成一个独立的计数项。

三、Nginx 下载带宽限制

 

△ ngx_http_core_module

在 ngx_http_core_module 模块里面有 limit_rate_after 和 limit_rate 参数,这个是下载带宽限制。如上图,意思是在下载完前面 500KB 数据后,对接下来的数据以每秒 20KB 速度进行限制,这个在文件下载、视频播放等业务场景中应用比较多,能够避免没必要要的浪费。例如视频播放,第一个画面可以尽快看到,对用户体验来讲很重要,若是用户第一个页面看不到,那他的等待忍耐程度是不好的,因此这个场景下前面的几个字节不该该去限速,在看到第一个画面以后,后面画面是按照必定视频码率播放,因此不必下载很快,并且快了也没用,它照样是流畅的,但却多浪费了流量资源,若是用户看到一半就关掉,整个视频下载完成,对于用户和内容提供商都是资源浪费。

OpenResty 动态流控

相比 Nginx ,OpenResty 具备不少的优点。

  • 咱们须要更加丰富的流控策略!Nginx 只有经典的几种。
  • 咱们须要更加灵活的配置管理!限速的策略配置规则是多样化的,咱们须要更加灵活。
  • 咱们须要在 Nginx 请求生命周期的更多阶段进行控制!前面提到的的 limit_req 模块,它只能在 PREACCESS 阶段进行控制,咱们可能须要在 SSL 的卸载过程当中对握手的链接频率进行控制,咱们也可能须要在其它任意阶段进行请求频率控制,那 Nginx 这个模块就作不到了。
  • 咱们须要跨机器进行状态同步!

请求速率限制 / 并发链接数限制

OpenResty 官方有一个叫作 lua-resty-limit-traffic 的模块,里面有三种限速的策略。

(1) resty.limit.req 模块

 

△ lua-resty-limit-traffic (resty.limit.req)

resty.limit.req 模块的设计与 NGINX limit_req 实现的效果和功能同样,固然它用 Lua 来表达限速逻辑,能够在任何的代码里面去引入,几乎能够在任意上下⽂中使⽤。

(2)resty.limit.conn 模块

功能和 NGINX limit_conn 一致,但 Lua 版本容许突发链接进行短暂延迟等候。

(3)resty.limit.count 模块

 

△ lua-resty-limit-traffic (resty.limit.count)

第三个是 resty.limit.count 模块,请求数量限制,这个目前 Nginx 没有,用一句话归纳这个模块,就是在单位时间内确保累计的请求数量不超过一个最大的值。好比在 1 分钟以内容许累计有 100 个请求,累计超过 100 就拒绝。这个模块和 Github API Rate Limiting(https://developer.github.com/v3/#rate-limiting)的接口设计相似,也是一个比较经典的限制请求的方式。

跨机器速率限制

 

△ lua-resty-redis-ratelimit (resty.redis.ratelimit)

有了 OpenResty,能够作一些更加有意思的事情。好比咱们有多台机器,想把限制的状态共享,又拍云以前开放了一个简单的模块叫 lua-resty-redis-ratelimit(resty.redis.ratelimit),顾名思义就是把这个状态扔到 Redis 保存。它和 Nginx limit req 以及 resty.limit.req 同样,都是基于漏桶算法对平均请求速率进行限制。不一样的是,该模块将信息保存在 Redis 从而实现多 Nginx 实例状态共享。

借助于 Redis Lua Script 机制 ,Redis 有一个支持写 Lua 脚本的功能,这个脚本可以让一些操做在 Redis 执行的时候保证原子性,依赖这个机制,咱们把一次状态的变动用 Lua Script 就可以彻底原子性地在 Redis 里面作完。

同时,该模块支持在整个集群层⾯禁⽌某个非法⽤用户一段时间,可实现全局自动拉⿊功能。由于是全局共享,一旦全网有一个客户触发了设置的请求频率限制,咱们能够在整个集群内瞬间把他拉黑几个小时。

固然这个模块是有代价的,并且代价也比较大,由于 Nginx 和 Redis 交互须要网络 IO,会带来必定延迟开销,仅适合请求量不大,但须要很是精确限制全局请求速率或单位统计时间跨度很是大的场景。

固然,这个模块也能够作一些本身的优化,不必定全部的状态都须要跟 Redis 同步,能够根据本身的业务状况作一些局部计算,而后定时作全局同步,牺牲一些精确性和及时性,这些均可以去抉择,这边只是多提供了一个手段。

知识点-漏桶算法

 

△ 漏桶算法示意图

前面提到的多个模块都是基于漏桶算法的思想达到频率限速的效果,如上图,一个水桶,水滴一滴一滴往下滴,咱们但愿水往下滴的速度尽量是恒定的,这样下游可以承载的处理能力是比较健康的,不要一会儿桶就漏了一个大洞冲下去,但愿它均衡地按序地往下滴,同时前面会有源源不断的水进来。

 

 

这个漏桶算法思想的核心就是上图中这个简单的公式,咱们怎么把请求的 5r/s,即每 200ms 一个请求的频次限制代到这个公式呢?

首先,在具体实现中,通常定义最小速率为 0.001r/s,即最小的请求刻度是 0.001 个请求,为了直观计算,咱们用 1 个水滴(假设单位t)来表达 0.001 个请求,那么 rate=5r/s 至关于 5000t/s。

前面提到该算法是计算两个相邻请求的频率,因此要计算当前请求和上一个请求的时间间隔,假设是 100 ms,单位是毫秒,下面公式中除以 1000 转换成秒等于 0.1s,即 0.1s 可以往下滴 500 个水滴,由于速率是 5000t/s,时间过去了 0.1 秒,固然只滴下去 500 滴水。

500 水滴下去的同时,速率一直是恒定的,可是同时又有请求进来,由于新的请求进来才会去计算这个公式,因此后面加了 1000,1000 个水滴表明当前这一个请求。就能够计算出当前桶的剩余水滴数。

excess 表示上一次超出的水滴数(延迟经过),一开始是 0 。特别地,若是 excess<0,说明这个桶空了,就会把 excess 重置为 0 ;若是 excess>0,说明这个桶有水滴堆积,这时水滴的流入速度比它的流出速度快了,返回 BUSY,表示繁忙。经过这样动态的标记就能够把这个速率给控制起来。

前面提到的突发,只要把这里的 0 换成 4 ,就是容许必定程度的突发了。

令牌桶限速

令牌桶和漏桶从一些特殊的角度(特别是从效果)上是有一些类似的,可是它们在设计思想上有比较明显的差别。

 

△ 令牌桶

令牌桶是指令牌以必定的速率往桶里进令牌,进来的请求是恒定的速率来补充这个桶,只要桶没有满就能够一直往里面放,若是是补充满了就不会再补充了。每处理一个请求就从令牌桶拿出一块,若是没有令牌能够拿那么请求就没法往下走。

 

△ lua-resty-limit-rate (resty.limit.rate)

lua-resty-limit-rate(resty.limit.rate)是又拍云最近开源的一个库,基于令牌桶实现。

上图是个简化的演示,首先申请两个令牌桶,一个是全局的令牌桶,一个是针对某个用户的令牌桶,由于系统内确定有不少用户调用,全局是一个桶,每一个用户是一个桶,能够作一个组合的设置。若是全局的桶没有满,单个用户超过了用户单独的频次限制,咱们通常会容许其突发,后端对于处理 A 用户、B 用户的消耗通常是相同的,只是业务逻辑上分了 A 用户和 B 用户。

所以,总体容量没有超过限制,单个用户即使超过了他的限制配置,也容许他突发。只有全局桶拿不出令牌,此时再来判断每一个用户的桶,看是否能够拿出令牌,若是它拿不出来了就拒绝掉。此时总体系统达到瓶颈,为了用户体验,咱们不可能无差异地去弹掉任意用户的请求,而是挑出当前突发较大的用户将其请求拒绝而保障其余正常的用户请求不受任何影响,这是基于用户体验的角度来考虑限速的方案配置。

相比 limit.req 基于漏桶的设计,令牌桶的思想更关注容量的变化,而非相邻请求间的速率的限制,它适合有必定弹性容量设计的系统,只有在全局资源不够的时候才去作限制,而非两个请求之间频率超了就限制掉,速率容许有较⼤大的波动。

相比 limit.count 对单位窗口时间内累计请求数量进行限制,该模块在特定配置下,也能达到相似效果,而且能避免在单位时间窗口切换瞬间致使可能双倍的限制请求状况出现。 limit.count 模块在单位时间内,好比在 1 分钟内限制 100 次,在下一个 1 分钟统计时,上一个 1 分钟统计的计数是清零的,固定的时间窗口在切换的时候,在这个切换的瞬间,可能前 1 分钟的最后 1 秒上来了 99 个请求,下一个 1 分钟的第 1 秒上来 99 个请求,在这 2 秒内,它超过了设计的单位时间最多 100 个请求的限制,它的切换瞬间会有一些边界的重叠。而基于令牌桶后,由于它的流入流出有一个桶的容量在保护,因此它切换是比较平滑的,流入速度和流出速度中间有一个缓冲。

除了请求速率限制(一个令牌一个请求),还可以对字节传输进行流量整形,此时,一个令牌至关于一个字节。由于流量都是由一个个字节组成的。若是把字节变成令牌,那流量的流出流入也能够经过令牌桶来给流量作一些整形。整形就是流量按你指望设计的形状带宽(单位时间内的流量)进行传输。

OpenResty 动态流控在又拍云的业务应用

  • 海外代理进行上传流量整形,避免跑满传输线路带宽(流量整形);
  • 某 API 请求基于令牌桶针对不一样帐户进行请求速率控制(令牌桶应用);
  • CDN 特性:IP 访问限制,支持阶梯策略升级(IP访问限制);
  • CDN 特性:码率适配限速

又拍云和 KONG

 

 

KONG 是一个很是著名的 OpenResty 的应用,又拍云在 2018 年在网关层引入了 KONG ,内部也维护了一个 KONG 的 Fork 版本,作了一些插件的改造和适配。

流量整形

咱们在 KONG 上怎么去作流量呢?由于香港到国内数据中心的传输线路价格很是昂贵,咱们购买线路带宽是有必定限制的。可是咱们在这条线路传输有不少 API ,若是有一个 API 突发流量,就会影响到其余,因此咱们在 KONG 上作了改造。

 

KONG 的设计不容许管控请求的 socket 字节流,也是用 Nginx 的核心模块来转发字节流,咱们须要去管控全部从 req socket 进来的字节流,由于要作字节流限制,因此咱们这里用纯 Lua 接管了。

Lua 接管以后,能够看到每 8192 个字节,都会拿 8192 个令牌,若是能拿出来,就让这 8192 个字节日后端传;若是拿不出来,说明当时已经日后传太多字节了,就让它等一等,起到一些限制效果。

令牌桶应用

 

咱们在某一个 API 系统中用令牌桶怎么作策略的限制呢?上图是一个简单的配置示例,咱们针对全局有一个桶,一个令牌的添加速度是 40r/s,令牌的容量是 12000,每次是 4 个令牌一块儿添加,这是全局桶的策略;每一个用户空间的策略是:桶的容量是 6000,每次 2 个令牌一块儿添加,它的限制大概是 10r/s ;对于一些特殊的操做,好比 delete,咱们会限制得更加严格一点,引入了第三个,专门针对 delete 操做的桶。

因此这里能够有好多桶来配合,全局的,局部的以及特殊的操做,你们的限制等级都不太同样,策略均可以灵活去配置。

 

△ 限制效果图

上图是咱们实际的限制效果,蓝色部分是经过令牌桶屏蔽掉的,绿色的是健康的,这部分被弹的,看业务数据的话,不是任意空间被弹掉,它被弹的时候都是那么几个空间被弹掉,会比较集中那几个空间,特别出头的被弹掉。而不是说一大堆的空间,甚至请求流量很小的,你随机去弹几个。确定要挑出那些捣乱的把它弹掉,从而保护整个后端的请求能维持在一个健康的水位下。

IP 访问限制

 

△ IP 访问限制

又拍云的产品中有一个 IP 访问的限制的功能,针对单位时间内的 IP 进行频率的保护。当你的网站或者静态资源被一些恶意的 IP 疯狂下载,浪费你不少流量的时候是有帮助的。并且咱们支持阶梯的配置,达到第一个阶梯禁止多少时间,若是继续达到第二个阶梯,阶梯升级禁用的力度就会更大。

码率适配限速

 

△ 码率适配限速

针对视频播放,咱们须要对码率进行适配。这个功能能够动态读取 MP4 的元数据,读到它的码率状况,从而作出相应的下载带宽控制的策略,使得这个文件在播放的时候看到的是很流畅的,用户体验没有受到任何影响,可是不会由于客户端网速较快而多浪费流量资源。这是下载带宽限速,结合实际应用的一个例子。

 

分享视频及PPT可前往:

OpenResty 动态流控的几种姿式 - 又拍云

相关文章
相关标签/搜索