限流是一种流量控制策略,用于限制对系统资源(如服务器、网络带宽、数据库连接等)的访问速率。它就像是在系统的入口处设置了一个 “阀门”,控制进入系统的请求数量或速率,以确保系统能够在其处理能力范围内稳定运行。
当大量请求瞬间涌入系统时,系统的资源(如 CPU、内存、I/O 等)可能会被耗尽。例如,一个 Web 应用服务器如果同时接收过多的 HTTP 请求,可能会导致服务器响应变慢甚至崩溃。通过限流,可以避免这种情况的发生,确保系统的响应时间和可用性。
在网络环境中,存在分布式拒绝服务攻击(DDoS)等恶意行为。攻击者会发送大量请求来淹没目标系统,使其无法正常服务合法用户。限流可以作为一种防御手段,限制异常高流量的请求,减轻 DDoS 攻击对系统的影响。
在多个用户或服务共享有限资源的场景下,限流可以确保每个用户或服务都能获得合理的资源份额。例如,在一个多租户的云服务环境中,通过对每个租户的请求进行限流,可以防止某个租户过度占用资源,从而保证其他租户的服务质量。
这是一种简单直观的限流方法。它使用一个计数器来记录在特定时间窗口内的请求数量。例如,设定一个时间窗口为 1 分钟,计数器初始值为 0。每收到一个请求,计数器就加 1。当计数器的值超过设定的阈值(如 100 个请求)时,就开始拒绝新的请求,直到下一个时间窗口开始,计数器清零。
但是这种方法存在一个问题,称为 “临界问题”。假设我们的阈值是 100,时间窗口是 1 分钟,在 59 秒的时候已经接收了 99 个请求,然后在第 60 秒的时候又接收了 100 个请求,这样在这 1 分钟内就处理了 199 个请求,超过了我们设定的限制。
为了解决计数器法的临界问题,滑动窗口法应运而生。它将时间窗口划分为多个小的子窗口,每个子窗口都有自己的计数器。例如,将 1 分钟的时间窗口划分为 6 个 10 秒的子窗口。每次有请求到来时,更新当前子窗口和总的请求计数器。当总的请求计数器超过阈值时,就进行限流。
这样可以更平滑地控制请求流量,避免了计数器法在时间窗口边界处可能出现的突发流量问题。
可以把系统想象成一个底部有小孔的水桶。请求就像水一样流入桶中,桶底部的小孔以固定的速率将水(请求)流出。当水(请求)流入的速度超过流出的速度时,桶就会装满,多余的水(请求)就会溢出(被拒绝)。
这种算法的优点是能够平滑请求流量,缺点是对于突发流量的处理不够灵活,因为请求只能按照固定的速率流出。
令牌桶算法中有一个令牌桶,系统会以固定的速率向桶中放入令牌。当有请求到来时,需要从桶中获取一个令牌才能被处理。如果桶中没有令牌了,那么请求就会被限流。
与漏桶算法不同的是,令牌桶算法允许一定程度的突发流量。例如,如果令牌桶中积累了一些令牌,当突发大量请求到来时,只要有足够的令牌,这些请求就可以立即被处理。
spring-cloud-zuul-ratelimit 是一个用于在 Spring Cloud Zuul 网关中实现限流的库。
该库主要基于 Spring Boot 和 Zuul 构建,它允许你在 Zuul 网关层面对进入系统的请求流量进行速率限制,例如可以限制某个 API 端点在一定时间内允许通过的请求数量。
注意了,spring-cloud-zuul-ratelimit 组件库托管在 github 上面,如下图:
组件库地址:https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit
下面将引入 spring-cloud-zuul-ratelimit 和 redis 的依赖信息:
<!-- 用于zuul限流 --> <dependency> <groupId>com.marcosbarbero.cloud</groupId> <artifactId>spring-cloud-zuul-ratelimit</artifactId> <version>2.4.0.RELEASE</version> </dependency> <!-- 基于redis来计数 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
为什么需要引入 redis 依赖?该示例将基于 Redis 来实现限流,Redis 需要保存服务访问信息,限流等信息。
下面是项目中配置 redis 的配置,使用本地 redis:
spring: # ... redis: host: localhost port: 6379 database: 0
下面是 zuul 限流的配置:
# Zuul 服务限流 zuul: # 服务限流 ratelimit: # 开启限流 enabled: true # 使用 redis 存储限流数据 repository: redis # policy-list 自定义配置,局部生效 policy-list: service-product: - limit: 2 # 限制一个时间窗口内 2 次 refresh-interval: 60 # 60s 内请求超过2次,服务端抛出异常,60s 后可以恢复正常请求 type: - url - origin # 默认限制,所有服务都有效 default-policy-list: - limit: 10 # 限制次数,每刷新一次次数+1 quota: 1000 # 窗口对应的请求时间限制 refresh-interval: 60 # 多少秒后刷新计数 type: # optional 限制类型 - origin # url 包括ip+接口 - url - user # 路由配置 routes: service-order: path: /api/order/** service-id: service-order service-product: path: /api/product/** service-id: service-product
完成上述配置后,重启项目,如下图:
此时,使用浏览器访问 http://localhost:9000/api/product/product/1 地址,如果在一分钟内访问超过两次,则会出现如下图错误信息:
上图错误信息说明,触发了限流。
最后,使用 RedisDesktopManager(点击下载) 工具连接到 Redis,查看限流相关的信息,如下图:
点击下载/查看本教程相关资料或者源代码。