限流、漏斗桶和令牌桶的区别

众所周知:限流是保护系统不被流量冲垮的核心手段,老朋友返场,咱们再来重温一下

一、 核心机制解析

1. 漏桶算法(Leaky Bucket):强制匀速,绝对平滑
  • 工作原理 :把请求想象成"水",漏桶是一个有固定容量的容器。水(请求)以任意速率流入桶中,但桶底有一个固定大小的孔,水只能以恒定的速率流出(被处理)。如果流入的水太多,桶满了之后,多余的水就会溢出(被直接丢弃或拒绝)。
  • 核心特性:它强行抹平了突发流量,保证了下游系统接收到的请求速率是绝对平滑、恒定的。
2. 令牌桶算法(Token Bucket):允许突发,兼顾平滑
  • 工作原理:系统以恒定的速率向一个容量上限固定的"桶"中生成并放入令牌。请求在处理前,必须先消耗对应数量的令牌。如果桶里的令牌足够,请求就能被处理;如果令牌不足,请求就会被限流(丢弃、排队或标记降级)。当桶满时,新生成的令牌会溢出丢弃。
  • 核心特性 :在维持长期平均速率的同时,允许一定程度的突发流量(只要桶里有积攒的令牌,瞬间的大量请求也能被放行)。
考量维度 令牌桶 (Token Bucket) 漏桶 (Leaky Bucket)
突发流量处理 支持良好:允许短时流量超限(消耗库存令牌) 严格限制:输出绝对平滑,无视突发请求
流量平滑度 相对平滑,允许一定波动 完全平滑,无任何波动
延迟与性能 延迟较低,处理效率高 可能会增加排队延迟(水在桶中等待流出)
核心价值 兼顾平均速率与瞬时突发处理能力 提供极强的确定性,保护下游不被打垮

二、 架构选型:根据业务场景匹配

在实际的系统架构设计中,选择哪种算法并非技术优劣的比拼,而是业务场景的匹配度问题:

1. 什么时候选"令牌桶"?
  • 适用场景:API 网关、Web 服务、常规接口限流。
  • 业务诉求:系统本身具备一定的弹性处理能力,希望尽可能多地承接用户的瞬时请求(如秒杀、抢购、突发热点事件),而不是生硬地拒绝用户。
  • 实战落地 :在分布式环境中,常通过 Redis + Lua 脚本实现原子性的分布式令牌桶;在单机应用中,Java 生态常使用 Guava 的 RateLimiter 结合协程来优化高并发性能。
1、 应用层限流:Guava RateLimiter 实战

保护本地核心资源(如数据库写入、第三方API调用),使用令牌桶算法 。Guava 的 RateLimiter经测试绝对是单机限流的绝对王者。

2、 分布式限流:Redis + Lua 脚本实战

场景 :微服务集群下,多个实例共享限流状态。必须使用 Lua 脚本 保证"查询+修改"的原子性,防止并发下的计数错误。

复制代码
-- limit.lua (Redis 脚本)
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])

local current = redis.call('INCR', key)
if current == 1 then
    redis.call('EXPIRE', key, window)
end

if current > limit then
    return 0
end
return 1


// Java 调用封装
public boolean isAllowed(String key, int limit, int windowSec) {
    DefaultRedisScript<Long> script = new DefaultRedisScript<>();
    script.setScriptText(LUA_SCRIPT); // 注入上面的 lua 脚本
    script.setResultType(Long.class);

    Long result = redisTemplate.execute(script, 
        Collections.singletonList(key), 
        String.valueOf(limit), 
        String.valueOf(windowSec));
        
    return result != null && result == 1L;
}
2. 什么时候选"漏桶"?
  • 适用场景:消息队列消费端、支付网关、核心数据库写入接口。
  • 业务诉求:下游系统的处理能力极其有限且固定(例如第三方银行接口限制每秒只能处理 50 笔支付)。此时不需要突发,只需要保证绝对不超频,漏桶的"确定性"是救命稻草。
1、网关层限流:Spring Cloud Gateway 落地
复制代码
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/user/**
          filters:
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 100  # 令牌桶每秒填充速率 (QPS)
                redis-rate-limiter.burstCapacity: 200  # 令牌桶最大容量 (允许突发)
                key-resolver: "#{@ipKeyResolver}"     # 限流维度:按IP限流

@Configuration
public class RateLimiterConfig {
    // 根据请求者 IP 进行限流
    @Bean
    public KeyResolver ipKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
    }
}

三、 实站:混合策略与多层级治理

聪明的你也能猜到:在成熟的架构中,往往不会单一使用某种算法,而是采用混合策略与多层级治理

  1. 网关层(令牌桶):作为系统的第一道防线,使用令牌桶应对前端用户的突发流量,在入口处拦截恶意请求,节约后端资源。如使用 Spring Cloud Gateway + Redis 令牌桶
  2. 应用层(差异化限流):根据业务重要性实施细粒度控制。例如,对于"创建订单"这种核心且耗时的接口,设置较低的 QPS 限制;对于"查询订单"这种轻量级接口,设置较高的 QPS 限制。如使用 Guava RateLimiter
  3. 核心服务层(漏桶):在调用外部依赖或写入核心数据库时,使用漏桶进行兜底防护,确保下游组件的绝对安全与稳定。

总结 :如果你需要保护下游不被冲垮,用漏桶 ;如果你想在保证系统稳定的前提下,尽可能多地承接用户的瞬时热情,用令牌桶

相关推荐
一个被程序员耽误的厨师1 小时前
02-架构篇-前端怎么反客为主把AI编排权拿回到自己手里
前端·人工智能·架构
Shadow(⊙o⊙)1 小时前
信号1.0,信号概念、signal()处理、前后台进程、闹钟设置、初识信号三张表。
linux·运维·服务器·开发语言·c++
暗黑小白1 小时前
第六篇:本地模型选型 —— 4 个模型 × 2 种设备 × 2 项任务的全量对比
架构·ai agent
(Charon)1 小时前
【C++ 面试高频:STL 容器 vector、map、unordered_map 总结】
开发语言·c++·面试
我是一颗柠檬1 小时前
【Java项目技术亮点】滑动窗口限流算法
java·开发语言·算法
syc78901231 小时前
同架构编码工具实测 口述开发场景体验对比
人工智能·架构
暗黑小白1 小时前
第五篇:Reranker 与 BM25 —— 在精排提升与降级可靠性之间划一条线
架构·ai agent
我登哥MVP1 小时前
SpringCloud Alibaba 核心组件解析:分布式事务(Seata)
java·spring boot·分布式·spring·spring cloud·java-ee·intellij-idea
一切皆是因缘际会1 小时前
隐层表征解构:LLM感知式幻觉稀疏成因
算法·数学建模·ai·架构