You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
API的限流
在业务安全性方面,我们常常会用到接口限流,主要是为了防止系统压力过大、保证每个用户请求的资源保持均匀以及屏蔽恶意请求。
在开发高并发系统时,有三把利器用来保护系统:缓存、降级和限流。那么何为限流呢?顾名思义,限流就是限制流量,就像你宽带包了1个G的流量,用完了就没了。通过限流,我们可以很好地控制系统的qps,从而达到保护系统的目的。
我们经常在调别人的接口的时候会发现有限制,比如微信公众平台接口、百度API Store、聚合API等等这样的,对方会限制每天最多调多少次或者每分钟最多调多少次。
比如,腾讯云的API接口 就是对请求限流为 20 次/秒
几个常见的场景如下:
目前实现API接口限流的方式有几种常见的,简单来说原理很简单,无非是在一个固定的时间段内,限制API的请求速率,一般来说是根据IP,如果是登录用户的话,还可以用用户的ID。
限制了后怎么做
对于恶意请求,假如对方的反反爬虫做的一般的话,完全可以直接将对方的IP加入黑名单,但如果对方用的IP代理,那就不是限流这个方案能解决的了,需要更高级的反爬虫方案。
但是我想提一点,对方既然爬了你的数据,肯定有对方的用处,假如对方没有恶意,请求频率也没有让你的机器有太多压力,那也就算了,毕竟你可能也在爬其它人的数据,大家都是搞技术的,没准对方背着万恶的KPI呢。
但是,如果对方恶意爬取,那么你完全可以在探测到对方的请求之后,返回空数据,甚至以假乱真的数据欺骗对方,让对方无利可图,对方也可能就会主动放弃了。
如何设计API的限流
可用性和可靠性对于所有 Web 应用程序和 API 服务至关重要。如果您提供 API 服务,您可能体会过流量突增对服务质量的影响,甚至可能造成服务中断。
限制流量可以使 API 服务在下面的场景中更可靠:
某个用户直接或间接造成了流量飙升,我们需要确保对其他用户服务可用。
某个用户向 API 服务发送大量请求。 或者更糟的是,某个用户试图恶意冲垮服务器。
用户发送了大量低优先级请求,但我们希望确保不会影响其他高优先级请求。 例如,发送大量分析数据请求的用户可能会影响其他用户的关键事务。
系统内部产生错误,导致无法处理所有请求,不得不丢弃低优先级的请求。
常见的限流算法和策略
计数器算法
计数器算法是限流算法里最简单也是最容易实现的一种算法。
比如我们规定,对于A接口来说,我们1分钟的访问次数不能超过100个。那么我们可以这么做:在一开始的时候,我们可以设置一个计数器counter,每当一个请求过来的时候,counter就加1,如果counter的值大于100并且该请求与第一个请求的间隔时间还在1分钟之内,那么说明请求数过多;如果该请求与第一个请求的间隔时间大于1分钟,且counter的值还在限流范围内,那么就重置 counter。
优点:实现简单,并且内存占用小,我们只需要存储时间窗口中的计数即可;
缺点:流量曲线可能不够平滑,有“突刺现象” ”和 临界问题“ 。
突刺现象:如果在单位时间1s内允许100个请求,在10ms已经通过了100个请求,那后面的990ms,只能眼巴巴的把请求拒绝,我们把这种现象称为“突刺现象”。
临界问题:窗口切换时可能会产生两倍于阈值流量的请求。即:瞬时流量的临界问题,在最坏的情况下,会让通过的请求量是限制数量的两倍;
比如窗口大小为1s,限流大小为100,然后恰好在某个窗口的第999ms来了100个请求,窗口前期没有请求,所以这100个请求都会通过。再恰好,下一个窗口的第1ms有来了100个请求,也全部通过了,那也就是在2ms之内通过了200个请求,而我们设定的阈值是100,通过的请求达到了阈值的两倍。
聪明的朋友可能已经看出来了,刚才的问题其实是因为我们统计的精度太低,窗口粒度太大。那么如何很好地处理这个问题呢?或者说,如何将临界问题的影响降低呢?我们可以看下面的滑动窗口算法。
滑动窗口
计数器滑动窗口算法是计数器固定窗口算法的改进,解决了固定窗口切换时可能会产生两倍于阈值流量请求的缺点。TCP协议中数据包的传输,同样也是采用滑动窗口来进行流量控制。
滑动窗口算法在固定窗口的基础上,将一个计时窗口分成了若干个小窗口,然后每个小窗口维护一个独立的计数器。
当请求的时间大于当前窗口的最大时间时,则将计时窗口向前平移一个小窗口。平移时,将第一个小窗口的数据丢弃,然后将第二个小窗口设置为第一个小窗口,同时在最后面新增一个小窗口,将新的请求放在新增的小窗口中。同时要保证整个窗口中所有小窗口的请求数目之后不能超过设定的阈值。
在上图中,整个红色的矩形框表示一个时间窗口,在我们的例子中,一个时间窗口就是一分钟。然后我们将时间窗口进行划分,比如图中,我们就将滑动窗口划成了 6 格,所以每格代表的是 10 秒钟。每过 10 秒钟,我们的时间窗口就会往右滑动一格。每一个格子都有自己独立的计数器 counter,比如当一个请求在 0:35 秒的时候到达,那么 0:30 ~ 0:39 对应的 counter 就会加 1。
那么滑动窗口怎么解决刚才的临界问题的呢?我们可以看上图,0:59 到达的 100 个请求会落在灰色的格子中,而 1:00 到达的请求会落在橘黄色的格子中。当时间到达 1:00 时,我们的窗口会往右移动一格,那么此时时间窗口内的总请求数量一共是 200 个,超过了限定的 100 个,所以此时能够检测出来触发了限流。
由此可见,当滑动窗口的格子划分的越多,那么滑动窗口的滚动就越平滑,限流的统计就会越精确,但是格子越多,复杂度越高,内存占用会更多
对限流规则的思考
限流规则包含三个部分:时间粒度,接口粒度,最大限流值。限流规则设置是否合理直接影响到限流是否合理有效。
对于限流时间粒度的选择,我们既可以选择 1 秒钟不超过 1000 次,也可以选择 10 毫秒不超过 10 次,还可以选择 1 分钟不超过 6 万次,
虽然看起这几种限流规则都是等价的,但过大的时间粒度会达不到限流的效果,比如限制 1 分钟不超过 6 万次,就有可能 6 万次请求都集中在某一秒内;
相反,过小的时间粒度会削足适履导致误杀很多本不应该限流的请求,因为接口访问在细时间粒度上随机性很大。所以,尽管越细的时间粒度限流整形效果越好,流量曲线越平滑,但也并不是越细越合适。
对于访问量巨大的接口限流,比如秒杀,双十一,这些场景下流量可能都集中在几秒内,QPS 会非常大,几万甚至几十万,需要选择相对小的限流时间粒度。
相反,如果接口 QPS 很小,建议使用大一点的时间粒度,比如限制 1 分钟内接口的调用次数不超过 1000 次
Beta Was this translation helpful? Give feedback.
All reactions