接口限流器中的经常使用算法及其应用场景

小编所在的部门做为公司的基础服务部门,支撑着上层业务的正常运行,当有业务举办活动、遭遇攻击,或者是写土了代码,都会或多或少给咱们的服务带来流量上的冲击。咱们一般说缓存、降级,以及限流技术是高并发服务的三大利器,为保证集团其它业务不受影响,限流每每是服务端接口的必要特性之一,用于对抗大规模恶意请求,保护有限的计算和存储资源。html

一. 经常使用接口限流算法

关于接口限流有不少成熟的算法可供使用,包括:计数器、漏桶,以及令牌桶等,这些算法都为实际项目中的限流器设计提供了理论支撑。java

1.1 计数器算法

计数器应该是最简单、最容易想到的限流策略,毕竟限流的本质就是限制一个接口在某个维度上单位时间的响应次数。咱们能够设置一个计数器对某一时间段内的请求进行计数,当请求量超过某个事先设定的阈值时则触发饱和策略,拒绝用户的请求。好比咱们事先设定某个接口单一 IP 维度在 1 分钟内只能正常响应 100 次用户请求,那么若是范围内某个 IP 请求超过该阈值,就会拒绝后续请求。git

上述方法存在的一个缺点是计数不够平滑,考虑一个 10 点开放抢购的场景,若是一个恶意用户若是在 09:59:30 ~ 09:59:59 之间请求了 100 次,而后等到 10 点整时计数器被清空,这个时候该用户在 10:00:00 ~ 10:00:30 之间又能够再次请求 100 次,实际上在 09:59:30 ~ 10:00:30 这一分钟内该恶意用户请求了 200 次,成功绕过了限流策略。github

针对计数器算法存在的上述缺陷,一种典型的解决方法就是采用 __滑动窗口策略__,本质上就是多级计数。上面咱们介绍的 1 分钟 100 次的请求上限能够看作是一个大窗口,咱们还能够将该窗口进一步细分,好比每 10 秒一个小窗口,该窗口的频率上限一样设置为 100,大窗口中包含了 6 个小窗口,而且没过 10 秒大窗口就往前移动一个小窗口长度。这样的设计下,若是一个恶意用户在 09:59:30 ~ 09:59:59 之间请求了 100 次,等到 10:00:00 ~ 10:00:30 时这 100 次计数仍然是有效的,因此这个时间段该用户新的请求仍然会被拒绝。算法

1.2 漏桶算法

漏桶(Leaky Bucket)算法是限流方面比较经典的算法,该算法最先应用于网络拥塞控制方面。理解该算法能够联想一个具体的漏桶模型,无论进水量有多大,漏桶始终以恒定的速率往外排水,若是桶被装满则后来涌入的水会漫出去。对应接口限流来讲,用户的请求能够看作是这里的水,无论用户的请求量有多大多不均衡,可以被处理的请求速率是恒定的,并且可以被接受的请求数也是有上限的,超出上限的请求会被拒绝,典型的咱们能够采用队列做为这里的漏桶实现。缓存

漏桶算法

由上面的解释咱们应该可以感受到漏桶算法很是适用于秒杀系统的限流,漏桶在这种应用场景下能够起到必定的削峰填谷的做用,而且漏桶的设计从根本上可以应对集中访问的问题,同时具有平滑策略,可是始终恒定的处理速率有时候并不必定是好事情,对于突发的请求洪峰,在保证服务安全的前提下,应该尽最大努力去响应,这个时候漏桶算法显得有些呆滞。安全

1.3 令牌桶算法

令牌桶(Token Bucket)算法能够看做是计数器算法的逆过程,不过相对于计数器来讲更加平滑。该算法要求系统以必定的速率发放访问令牌,用户的请求必须在持有合法令牌的前提下才可以被响应,咱们能够按照权重设置一类请求被响应所需持有的令牌数,只有当桶中的令牌数目知足当前请求所需时才授予令牌,对于其余状况则拒绝该请求。微信

令牌桶算法

因为发放令牌的速率是恒定的,因此对于集中请求来讲,令牌桶算法可以很好的作平滑,好比前面列举的在 09:59:30 ~ 09:59:59 之间有 100 次的突发请求,那么等到 10 点整的时候系统并不会当即容忍 100 次新的请求,这个时候服务的响应受限于当前桶中的令牌数量。实际项目中咱们能够基于当前服务能力动态调整令牌的发放速率,此外咱们还须要为桶设置大小上限(或者为令牌设置生命周期),以防止大量令牌累积致使的 “伪限流失效” 现象。网络

Guava 中的 RateLimiter 类对该算法进行了实现,基于 RateLimiter 咱们能够设置每秒生成的令牌数,并容许以阻塞、尝试,以及超时等策略获取令牌。相关实现和使用方式比较简单,能够参考官方文档。并发

二. 项目中的限流器设计

此前在写项目时发现组内的限流模块散落在各个具体项目中,而且各个项目都根据本身的业务特色对限流实现作了必定的定制化,这样在必定程度上增长了代码的实现和维护成本,因而就抽空闲时间对这一块进行了改造,抽取出了一个公共的 throttle 基础模块。考虑到时间成本和兼容性,这一块的实现仍是采用了平滑版本的计数器,而且在本地和全局维度上进行计数,以应对服务的分布式部署。

计数器 + 滑动窗口

如上图所示,限流模块以拦截器的方式进行工做,针对用户的请求会在本地和 IDC 范围内进行计数,当任何一个计数器达到阈值即触发饱和策略。采用计数器的好处是在分布式限流方面实现上比较简单,且除全局缓存之外不依赖于第三方服务,不过远程通讯这一块的性能损耗不能忽略,关于远程通讯这一块仍是有一些优化手段的,好比在本地计数并定时与远端执行同步,没有必要每一次用户请求都进行请求一次远程计数。另外,考虑到各个业务的限流维度不尽相同,在限流器设计上能够引入 SPI 机制来提高可扩展性和可配置性。

总的说来限流器在实现上并无太多复杂的逻辑,不过正如之前听一位长者所说,__限流的难点在于配置__,如何让限流在不误伤的前提下尽可能发挥硬件的最大性能是一个富有经验的问题,而压测是一个基础且行之有效的途径。


微信扫码关注,第一时间获取好玩有内涵的技术类文章


参考文献

  1. Leaky bucket
  2. Token bucket
  3. 流量调整和限流技术
相关文章
相关标签/搜索