限流策略:多维防御+纵深防御
限流能力
限流是针对请求的各种特征,多维防御+纵深防御,从而限制流量,实现对服务端资源的合理使用。这里的特征是指一个请求所包含的各种信息,包括但不限于 IP、Header、URI、Cookie 等。常见的限流策略有以下三种(以 Nginx 为例进行说明):
限制请求数,意思是请求的次数不能太多
Nginx:http://nginx.org/en/docs/http/ngx_http_limit_req_module.html
限制并发连接数,意思是请求的频率不能太快
Nginx:http://nginx.org/en/docs/http/ngx_http_limit_conn_module.html
限制传输速度,意思是请求的体积不能太大
Nginx:http://nginx.org/en/docs/http/ngx_http_core_module.html#limit_rate
Nginx 还提供 work_connections 和 worker_processes 这类针对程序的全局设置,但是这些配置并非是客户端的请求的特征,不能直接作用于特定请求特征的流量,因此不属于本文限流的讨论范畴。
多维防御
所谓的多维防御,不仅仅针对 IP 进行限制,还可以从 URI、Cookie、状态码等多个维度进行限制,同时还可以组合起来进行精细化的限制,举例说明:
状态码+IP,如果恶意扫描整个网站,必然会出现较多的 404 状态码,这时候结合其他维度,就可以进行一定的防御和限制
URI+Cookie,可以避免公司 IP 出口导致的请求被误伤,同时也解决了 IP 模式下阈值设置太大的问题
纵深防御
所谓的纵深防御,则是指针对单一维度,进行多层级的防御,实现精细化的限制。以 IP 维度为例进行说明:
每秒钟,单个 IP 最多可以访问 100 次
每十秒钟,单个 IP 最多可以访问 500 次
每分钟,单个 IP 最多可以访问 1000 次
阈值设置
上述的阈值只是简单举例,并没有太大的参考价值,在实际部署的过程中,一般需要至少 30 天的访问日志以及多个节假日的访问日志进行离线分析,从而得出业务特定的合理阈值,然后在通过灰度逐步全量。初期的阈值,建议设置的比预估阈值至少高一倍,先具备限流功能,然后再慢慢打磨阈值,避免因为阈值设置不合理,导致限流功能上线受阻,那就得不偿失了。
限流的算法
那各种限流策略背后的实现机制是怎样的呢?常见的限流算法有固定窗口计数器,滑动窗口计数器,漏洞和令牌桶四种,在各类软件的具体实现上却不尽相同,详细内容可以参考这篇文章《分布式系统关注点——限流该怎么做?》,本文仅介绍较为常用的两种算法。
固定窗口计数器
将时间划分为固定时间窗口(例如每秒一个单位),在每个固定的时间窗口内对请求进行计数,如果请求的次数超过了限制,则本时间窗口内所有的后续请求都被丢弃,直到下一个固定时间窗口时,重置计数器。
该算法常被吐槽流量突增,举例来说 100r/m(每分钟 100 次请求),可能在第一秒钟就请求完毕,更有甚者,在两个时间窗口交界处可能会产生 2 倍的请求,这样就会导致短时的流量突增。但是,只要把时间粒度控制在秒级,效果还是可以的。因此,滑动窗口就没有太大的必要进行介绍了,大家看相关文章即可。
漏桶
漏桶算法,有一个固定大小的队列,进入的请求全部放入该队列中,如果队列满,则丢弃进入的请求。同时,以固定的速率从队列中消费请求。好处就是,不管流量进来多少,出去的速率始终固定。
该算法的主要不足就是消费速率设置会偏保守一些,因为不同请求的资源消耗和处理耗时不同,为了能够适配各类场景,必然会导致消费速率设置较为保守。缓解的方法,对不同的请求类型进行集群拆分,从而能够更加精细化的设置消费速率。
各层级限流
全局统一接入:GFE/BFE/JFE
全局统一接入的产品,Goolge 叫做 GFE,Baidu 叫做 BFE,JD 叫做 JFE。这类产品会将公司所有对外流量进行统一的接入和调度,同时具备较强的通用防护能力,如抗 D、WAF 等。
对于一些业务定制化的复杂限流规则,一般会下放到各个业务线自己的接入端来实现。
Webserver:Nginx/OpenResty
上面介绍各种 XFE 的时候提到,一些业务定制化的复杂限流规则,会下放到各个业务线的接入端来实现,在这个阶段,因为 Webserver 的选择较多,所以实现方式也较多,没有统一的标准,一般是直接利用 webserver 提供的限流能力,或者是通过插件方式来实现,前者多以 Nginx 为主,后者则是 Nginx+lua/Nginx+lua+redis 的形式。
本文以 Nginx 为例来进行说明,业务侧的整体思路应该是多维防御+纵深防御,从而实现较好的限流效果。
多维防御,以 Nginx 的 limit_req 为例进行说明,不仅仅对来源 IP 进行流控,还可以对请求的 uri,cookie 进行流控,还可以对 ip+uri+cookie 的组合进行流控,从多个维度下手,进而实现更好的效果。
limit_req_zone $ip zone=1:10m rate=100r/s;
limit_req_zone ip zone=2:10m rate=100r/s;
limit_req_zone ip$cookie zone=3:10m rate=100r/s;
limit_req zone=1 burst=30 nodelay;
limit_req zone=2 burst=10 nodelay;
limit_req zone=3 burst=30 nodelay;
纵深防御,以 Nginx 的 limit_req 为例进行说明,主要是通过漏斗思路,设置三级防御体系,第一级是粗滤,因此阈值可以设置的较大,仅拦截明显的恶意行为即可,第二级过滤,可以拦截一些异常请求,第三级过滤,则可以将大部分异常请求进行拦截。
limit_req_zone $ip zone=11:10m rate=100r/s;
limit_req_zone $ip zone=12:10m rate=500r/10s;
limit_req_zone $ip zone=13:10m rate=1200r/1m;
limit_req zone=1 burst=50 nodelay;
limit_req zone=2 burst=100 nodelay;
limit_req zone=3 burst=200 nodelay;
备注:Nginx 本身并不支持 500r/10s 这种模式,同时,60r/m 的效果等同于 1r/s,因此上述的配置只能起到一个演示的作用。实际上是需要大家通过插件的方式来实现上述的效果。Nginx 官方对于 r/m 的解释如下:The rate is specified in requests per second (r/s). If a rate of less than one request per second is desired, it is specified in request per minute (r/m). For example, half-request per second is 30r/m.
应用程序和中间件:Istio
对于大部分自研的程序来讲,限流功能如有必要,就得自行实现了,算法前面已经介绍过了,当然,也可以参考 Google 的 RateLimiter。
对于进入了 Docker 的程序,则可以考虑引入 Istio,改造成本可控,收益上,限流,熔断,鉴权等等功能均可具备,但是目前大规模应用的公司还不多,大家都是在慢慢探索中。
对于短期内还无法实现虚拟化的业务,则可以考虑类似 Istio 的模式,在每个程序前,在关键程序前,在和多个下游交互的程序前,适度放置 Proxy 来实现流控的效果。在 AWS 的 Proxy-ELB,就是挂在了每组服务之前。这种模式的缺点就在于会略为增加延时,但如果是同 Regin 部署,那么只要增加的 Proxy 层级数量可控,整体延时影响也可控。
极少数情况下,也有业务直接通过 Iptable 的方式进行限流,对于物理机的部署模式,笔者只能建议要谨慎使用。
参考文章
评论