限流算法详细剖析

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

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 模块均采用或类似令牌桶)。漏桶算法则常用于强制流量整形的场景。

相关推荐
架构源启6 小时前
文档接入与智能解析:基于 Spring AI 1.1.x 的多格式解析、版面理解与结构化抽取
java·人工智能·spring
可编程芯片开发8 小时前
基于MATLAB的PEF湍流风场生成器模拟与仿真
算法
罗西的思考9 小时前
【Agentic RL / 强化学习 / OPD】OpenClaw-RL 源码阅读笔记 --- (9)--- Reward Judging
人工智能·算法·机器学习
CHANG_THE_WORLD9 小时前
12.总结:深入理解 Linux I/O 多路复用:select、poll、epoll 全解析
linux·运维·服务器
风曦Kisaki9 小时前
# Linux笔记:操作系统优化与资源管理
linux·运维·服务器·笔记
Mr.HeBoYan10 小时前
一次持续三天才出现的丢包故障——深入解析 DPDK Memory Ordering、rte_ring 与 CPU Memory Barrier (下)
linux·网络·算法·架构·dpdk
大模型码小白10 小时前
JAVA 集合框架进阶:List 与 Set 的深度解析与实战
java·开发语言·人工智能·windows·语言模型·list·ai编程
wabs66610 小时前
关于图论【深度优先搜索的理论基础】
算法·深度优先·图论
名字还没想好☜11 小时前
Go 的 time.Ticker 陷阱:定时任务里被忽略的内存泄漏与正确关闭
java·数据库·golang·go·定时器
先吃饱再说11 小时前
LeetCode 101. 对称二叉树
算法