众所周知:限流是保护系统不被流量冲垮的核心手段,老朋友返场,咱们再来重温一下
一、 核心机制解析
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());
}
}
三、 实站:混合策略与多层级治理
聪明的你也能猜到:在成熟的架构中,往往不会单一使用某种算法,而是采用混合策略与多层级治理:
- 网关层(令牌桶):作为系统的第一道防线,使用令牌桶应对前端用户的突发流量,在入口处拦截恶意请求,节约后端资源。如使用 Spring Cloud Gateway + Redis 令牌桶
- 应用层(差异化限流):根据业务重要性实施细粒度控制。例如,对于"创建订单"这种核心且耗时的接口,设置较低的 QPS 限制;对于"查询订单"这种轻量级接口,设置较高的 QPS 限制。如使用 Guava RateLimiter
- 核心服务层(漏桶):在调用外部依赖或写入核心数据库时,使用漏桶进行兜底防护,确保下游组件的绝对安全与稳定。
总结 :如果你需要保护下游不被冲垮,用漏桶 ;如果你想在保证系统稳定的前提下,尽可能多地承接用户的瞬时热情,用令牌桶。