Java限流中的「令牌桶」与「漏桶」原理与实现详解(含完整代码)
·
🚀 为什么需要限流?
在微服务、高并发或网关等场景下,如果不加控制地接收请求:
- 数据库、缓存或中间件可能被压垮;
- 导致服务不可用或响应变慢;
- 引发雪崩效应,影响整个系统。
因此,限流成为「高可用架构」中不可或缺的一环。
常见限流场景
- 秒杀系统:控制并发下单量,防止库存超卖。
- API 接口:保护后端服务不被恶意刷流。
- 爬虫防护:限制访问频率,防止恶意抓取。
- 服务降级:当系统达到阈值时,主动限流保护自己。
🔄 限流核心算法概览
| 限流算法 | 原理 | 控制维度 | 应用场景 |
|---|---|---|---|
| 计数器法 | 固定窗口内统计请求数 | 时间 + 次数 | 简单接口 |
| 滑动窗口 | 更精确地统计请求数 | 时间滑动窗口 | 秒级限流 |
| 漏桶算法 | 请求以固定速率流出 | 平滑流量 | 确保系统稳定性 |
| 令牌桶算法 | 请求必须拿到令牌才能处理 | 控制速率 + 支持突发 | 推荐使用 |
💧 漏桶算法(Leaky Bucket)
1. 算法原理
- 漏桶类似一个固定容量的桶,水(请求)可随时进入,但出水速率恒定。
- 如果请求量大于处理速率,多余的请求会被丢弃或排队。
- 达到限流效果的本质是:以固定速率处理请求,消除突发流量对系统的冲击。
2. 应用特点
- 控制出水速率,达到平滑请求流量的目的。
- 如果流入速率持续大于出水速率,会造成请求被抛弃。
- 适合对实时性要求不高、需要稳定输出的系统。
3. Java 实现代码
public class LeakyBucketLimiter {
private final int capacity; // 漏桶容量
private final int rate; // 每秒处理速率
private int water; // 当前水量
private long lastTime;
public LeakyBucketLimiter(int capacity, int rate) {
this.capacity = capacity;
this.rate = rate;
this.water = 0;
this.lastTime = System.currentTimeMillis();
}
public synchronized boolean tryAcquire() {
long now = System.currentTimeMillis();
long timePassed = now - lastTime;
int leaked = (int) ((timePassed / 1000.0) * rate);
water = Math.max(0, water - leaked);
lastTime = now;
if (water < capacity) {
water++;
return true;
} else {
return false; // 拒绝请求
}
}
}
🪙 令牌桶算法(Token Bucket)
1. 算法原理
- 系统以固定速率往桶中放入令牌(如每秒10个),每个请求需获取1个令牌才能被处理。
- 如果桶满了,多余令牌会被丢弃;如果请求来时桶为空,则被拒绝或等待。
- 支持突发流量——如果系统一段时间未被调用,桶中令牌将积累,可以应对短时间突发流量。
2. 应用特点
- 能平衡系统整体负载,且容忍短时高峰。
- 可通过调整速率和容量灵活应对不同业务场景。
- 适用于接口限流、用户行为控制、广告请求频控等。
3. Java 实现代码
public class TokenBucketLimiter {
private final int capacity;
private final int tokensPerSecond;
private double currentTokens;
private long lastRefillTime;
public TokenBucketLimiter(int capacity, int tokensPerSecond) {
this.capacity = capacity;
this.tokensPerSecond = tokensPerSecond;
this.currentTokens = capacity;
this.lastRefillTime = System.nanoTime();
}
public synchronized boolean tryAcquire() {
refill();
if (currentTokens >= 1) {
currentTokens -= 1;
return true;
}
return false;
}
private void refill() {
long now = System.nanoTime();
double seconds = (now - lastRefillTime) / 1_000_000_000.0;
double newTokens = seconds * tokensPerSecond;
currentTokens = Math.min(capacity, currentTokens + newTokens);
lastRefillTime = now;
}
}
🧮 计数器法(Fixed Window Counter)
1. 算法原理
- 将时间划分为固定时间窗口(如1秒),统计该时间段内请求次数。
- 若请求次数超过阈值,则拒绝本窗口后续请求。
2. 存在问题
- 窗口边界突变时,可能造成瞬时流量激增(突刺现象)。
如:999ms内100个请求,1001ms又来100个请求 —— 实际短时间接收200请求。
3. Java 实现
import java.util.concurrent.atomic.AtomicInteger;
public class FixedWindowLimiter {
private final int limit; // 每个窗口允许的请求数
private final long windowSize; // 窗口大小(毫秒)
private long windowStart; // 当前窗口起始时间
private AtomicInteger counter; // 请求计数器
public FixedWindowLimiter(int limit, long windowSize) {
this.limit = limit;
this.windowSize = windowSize;
this.windowStart = System.currentTimeMillis();
this.counter = new AtomicInteger(0);
}
public synchronized boolean tryAcquire() {
long now = System.currentTimeMillis();
if (now - windowStart >= windowSize) {
windowStart = now;
counter.set(0);
}
if (counter.incrementAndGet() <= limit) {
return true;
}
return false;
}
}
⏳ 滑动窗口算法(Sliding Window Log)
1. 算法原理
- 使用时间戳记录每次请求,按时间滚动删除过期记录。
- 在任意时刻统计最近一段时间内的请求次数,判断是否限流。
2. 优点
- 精度高,无突刺问题。
- 但记录每次请求时间,内存消耗较大,适合请求频率较低场景。
3. Java 实现
import java.util.LinkedList;
import java.util.Queue;
public class SlidingWindowLogLimiter {
private final int maxRequests;
private final long intervalMillis;
private final Queue<Long> timestamps = new LinkedList<>();
public SlidingWindowLogLimiter(int maxRequests, long intervalMillis) {
this.maxRequests = maxRequests;
this.intervalMillis = intervalMillis;
}
public synchronized boolean tryAcquire() {
long now = System.currentTimeMillis();
while (!timestamps.isEmpty() && now - timestamps.peek() > intervalMillis) {
timestamps.poll(); // 移除过期请求
}
if (timestamps.size() < maxRequests) {
timestamps.offer(now);
return true;
}
return false;
}
}
🧩 限流算法选型建议
| 算法 | 精度 | 内存 | 突发支持 | 实现复杂度 | 典型应用 |
|---|---|---|---|---|---|
| 计数器法 | ★★☆☆☆ | ★☆☆☆☆ | 否 | 简单 | 基础接口限流 |
| 滑动窗口 | ★★★★☆ | ★★★☆☆ | 否 | 中等 | 高精度限流 |
| 漏桶算法 | ★★★★☆ | ★☆☆☆☆ | 否 | 简单 | 消息队列控制 |
| 令牌桶算法 | ★★★★★ | ★☆☆☆☆ | 是 | 较复杂 | 接口、微服务 |
✅ 实战建议:令牌桶支持突发请求,更加灵活,适合大多数 API 限流场景。
🔐 进阶增强建议
异常策略建议
- 返回
429 Too Many RequestsHTTP 状态码 - 返回 JSON 提示信息
{ code: 429, message: "请求频繁,请稍后重试" } - 加入重试机制、回退策略
分布式限流方案
- Redis + Lua 脚本实现全局令牌桶
- 使用开源组件如:Sentinel、Bucket4j、Resilience4j
应用层封装建议
- 使用注解方式配置限流参数
- 利用 AOP 拦截进行限流处理
🎯 总结
- 漏桶适合稳定处理流量,确保后端不会被压垮。
- 令牌桶适合突发流量,控制速率的同时提升用户体验。
- 高并发系统中合理选型限流策略,是保障服务稳定性的重要手段。
如果你觉得这篇文章有帮助,欢迎点赞、收藏、评论支持!
更多推荐



所有评论(0)