限流算法详细剖析

常见的限流算法主要有四种,它们控制流量进入系统的逻辑各不相同,也各有适用的场景。

1. 固定窗口计数器

工作原理:将时间划分为固定的时间窗口(如1秒),在每个窗口内维护一个计数器。每来一个请求,计数器加1。若计数超过阈值(如100),则拒绝后续请求。窗口结束后,计数器清零。

优点:实现非常简单,内存占用极低。

缺点 :存在临界突变问题。例如,窗口1秒限100请求,若前100个请求集中在第0.9-1.0秒,后100个请求集中在第1.0-1.1秒,系统在0.2秒内承受了200个请求,会瞬间超载。

2. 滑动窗口计数器

工作原理:将固定窗口再细分为多个小格子(比如1秒窗口,分为10个100ms的格子)。滑动窗口随着时间向右移动,计算当前时间点所处的整个窗口内所有格子的请求总和。总和超过阈值则限流。

优点:解决了固定窗口的临界突变问题,限流更平滑,精度较高(格子越细越准)。

缺点:实现复杂度比固定窗口高,需要维护多个计数器,占用更多内存。

3. 漏桶算法

工作原理 :想象一个底部有个小洞的桶。请求(水滴)以任意速率流入桶中,然后以恒定的速率从桶底流出被处理。如果桶满了,新请求就会被丢弃(溢出)。

优点:输出流量绝对平稳,能够强行整形突发流量,保护下游系统不被任何瞬时高峰冲垮。

缺点无法应对突发流量。即使系统当下有多余容量,也无法快速处理积压的请求,可能导致不必要的延迟。对需要允许一定突发的业务不太友好。

4. 令牌桶算法

工作原理 :桶中按固定速率(如每秒10个)生成令牌。请求到来时,必须从桶中获取一个令牌,才能被处理。如果桶中没有令牌,请求就被拒绝。桶中可以积攒一定数量的令牌(即 burstCapacity 突发容量),用于应对短时突发流量。

优点 :既保证了平均速率,又允许一定程度的突发(只要令牌积攒足够)。突发流量不会立刻导致限流,而是消耗积攒的令牌,之后又会以恒定速率补充。这也是生产环境(如Spring Cloud Gateway)中最常用的算法。

缺点:实现相对复杂(通常依赖Redis或本地内存存储令牌数),且需要正确处理并发。


快速对比与选择建议

算法 平稳流量 允许突发 实现复杂度 典型场景
固定窗口计数器 是(但有边界突变) 极低 简单粗暴的限流,不关心瞬间尖刺
滑动窗口计数器 较好 中等 对精度有要求,允许平滑突发
漏桶 绝对平稳 中等 必须强制下游以恒定速率处理,如数据库写入
令牌桶 平稳 是(可控) 较高 通用首选,API网关、业务接口限流

5.Spring Gateway限流

在 Spring Cloud Gateway 中,最直接、最推荐的限流方案是使用框架内置的 RequestRateLimiter 过滤器工厂,并配合基于 Redis 的 RedisRateLimiter 来实现分布式限流。

Spring Cloud Gateway 默认使用的令牌桶算法(Token Bucket Algorithm),也是一种允许一定突发流量的算法。除此之外,常见的限流算法还有简单但有"突刺现象"的计数器算法,以及能平滑流量但无法应对突发情况的漏桶算法。

下面是基于Redis的限流实现:

5.1. 添加依赖

在你的 pom.xml 文件中添加 spring-boot-starter-data-redis-reactive 依赖。

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
5.2. 配置 Redis 连接信息

application.yml 中配置 Redis 连接信息。

yaml 复制代码
spring:
  redis:
    host: localhost
    port: 6379
    # password: yourpassword # 如有密码需配置
5.3. 配置限流规则

在网关路由配置中,为需要限流的 routes 添加 RequestRateLimiter 过滤器。

yaml 复制代码
spring:
  cloud:
    gateway:
      routes:
        - id: your-service-id
          uri: lb://your-service-name
          predicates:
            - Path=/api/**
          filters:
            - name: RequestRateLimiter
              args:
                key-resolver: "#{@userKeyResolver}"
                redis-rate-limiter.replenishRate: 10
                redis-rate-limiter.burstCapacity: 20
                redis-rate-limiter.requestedTokens: 1
  • key-resolver: 定义限流规则的粒度(如按用户ID、IP、URI等),下文详解。
  • redis-rate-limiter.replenishRate : 令牌桶填充速率,即每秒允许通过的请求数。
  • redis-rate-limiter.burstCapacity : 令牌桶总容量,即系统能够处理的瞬时最大请求数,也是令牌桶可以累积的令牌数。
  • redis-rate-limiter.requestedTokens : 每次请求消耗的令牌数 ,默认值 1
5.4. 自定义限流维度

通过实现 KeyResolver 接口,定义限流的Key,从而实现不同的限流策略。

  • 按用户ID限流
java 复制代码
@Bean
public KeyResolver userKeyResolver() {
    return exchange -> {
        // 这里假设userId存放在请求头中,你可以根据实际情况调整
        String userId = exchange.getRequest().getHeaders().getFirst("userId");
        return Mono.just(userId != null ? userId : "anonymous");
    };
}
  • 按IP地址限流
java 复制代码
@Bean
public KeyResolver ipKeyResolver() {
    return exchange -> {
        String ip = Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress();
        return Mono.just(ip);
    };
}
  • 按请求路径限流
java 复制代码
@Bean
public KeyResolver pathKeyResolver() {
    return exchange -> Mono.just(exchange.getRequest().getPath().toString());
}
  • 组合限流
java 复制代码
@Bean
public KeyResolver combinedKeyResolver() {
    return exchange -> {
        String userId = exchange.getRequest().getHeaders().getFirst("userId");
        String path = exchange.getRequest().getPath().toString();
        return Mono.just((userId != null ? userId : "anonymous") + ":" + path);
    };
}

在实际应用中,令牌桶 因其既能平滑流量又能应对突发,成为最主流的限流算法(如Spring Cloud Gateway、Guava RateLimiter、Nginx的 limit_req 模块均采用或类似令牌桶)。漏桶算法则常用于强制流量整形的场景。

相关推荐
我是一颗柠檬1 小时前
【Java项目技术亮点】分布式锁实现与优化:从Redisson到ZooKeeper,彻底搞懂分布式锁的底层原理
java·redis·分布式·中间件·java-zookeeper
ANnianStriver1 小时前
PetLumina 04 — 管理后台 UI 全面升级
java·ui·ai编程
winlife_1 小时前
全程用 AI 做一款商业级手游 · EP9 收尾与复盘:做到了哪,没做到哪,边界在哪
java·开发语言·人工智能·unity·ai编程·游戏开发·mcp
周杰伦fans1 小时前
AutoCAD2016经典模式不见了-设置回14版本前的经典工作空间
服务器·c语言·前端
云恒要逆袭1 小时前
Hello World背后的秘密:Java程序是这样运行的
java·后端·程序员
JAVA9651 小时前
JAVA面试-并发篇 09-LockSupport 和 waitnotify 的区别
java·开发语言·面试
蝎子莱莱爱打怪1 小时前
XZLL-IM干货系列 01|万字拆解分布式 IM 架构:7 个微服务 + 自研 Flutter SDK
java·后端·面试
鼎讯信通1 小时前
高性能射频信号模块 全方位守护能源设备稳定运行与高效检测
服务器·人工智能·能源
你是个什么橙2 小时前
Linux 远程桌面访问和管理——VNC服务器
linux·运维·服务器