“想象一个神奇的水厂,它用令牌控制水流速度——这就是令牌桶算法的精髓!”
一、生活中的令牌桶 🌍

类比理解:
- 令牌 = 用水许可证/门票/通行卡
- 桶 = 发放许可证的机构
- 水流/人流/车流 = 系统请求流量
流量控制演进史

令牌桶的独特价值:
- ✅ 突发流量处理:允许短时间内超过平均速率
- ✅ 精准控制:毫秒级精度控制流量
- ✅ 资源保护:防止系统被突发流量击垮
- ✅ 公平性保证:避免某些请求完全饥饿
二、算法原理图解 🧩
1. 算法数学模型
令牌生成函数:
tokens(t) = min(C, tokens(t0) + R × (t - t0))
其中:
C = 桶容量(capacity)
R = 令牌生成速率(rate)
t0 = 上次令牌补充时间
t = 当前时间
2. 分布式场景下的挑战
三、核心参数对照表 📊
| 参数 | 生活比喻 | 技术含义 | 示例值 |
|---|---|---|---|
| 容量(capacity) | 水箱最大容量 | 桶能存放的最大令牌数 | 100个 |
| 速率(rate) | 水龙头流速 | 每秒新增令牌数 | 10个/秒 |
| 突发量(burst) | 水箱现有存水 | 当前可用令牌数 | 0-100之间 |
四、算法工作流程 🔄

五、代码实现示例 💻
1. 单机高性能实现(Java)
import java.util.concurrent.atomic.*;
public class HighPerfTokenBucket {
private final long capacity; // 桶容量
private final AtomicLong tokens; // 当前令牌数
private final long intervalNs; // 时间间隔(纳秒)
private final AtomicLong lastRefill; // 上次补充时间
public HighPerfTokenBucket(long capacity, long refillRatePerSecond) {
this.capacity = capacity;
this.tokens = new AtomicLong(capacity);
this.intervalNs = 1_000_000_000 / refillRatePerSecond;
this.lastRefill = new AtomicLong(System.nanoTime());
}
public boolean tryAcquire(long permits) {
while (true) {
long now = System.nanoTime();
long last = lastRefill.get();
long delta = now - last;
long refillCount = delta / intervalNs;
if (refillCount > 0) {
// 尝试补充令牌
long newLast = last + refillCount * intervalNs;
if (lastRefill.compareAndSet(last, newLast)) {
// CAS更新令牌数
tokens.getAndUpdate(
cur -> Math.min(capacity, cur + refillCount)
);
}
}
// 尝试获取令牌
long curTokens = tokens.get();
if (curTokens < permits) return false;
if (tokens.compareAndSet(curTokens, curTokens - permits)) {
return true;
}
}
}
}
2. 分布式实现(Redis + Lua)
-- tokens_bucket.lua
local key = KEYS[1] -- 限流键
local capacity = tonumber(ARGV[1]) -- 桶容量
local rate = tonumber(ARGV[2]) -- 令牌生成速率(个/秒)
local now = tonumber(ARGV[3]) -- 当前时间戳(秒)
local requested = tonumber(ARGV[4]) -- 请求令牌数
local bucket = redis.call("HMGET", key, "tokens", "last_refill")
local tokens = tonumber(bucket[1]) or capacity
local last_refill = tonumber(bucket[2]) or now
-- 计算时间差(秒)
local delta = math.max(0, now - last_refill)
-- 计算新令牌数
local new_tokens = math.min(capacity, tokens + delta * rate)
-- 判断是否有足够令牌
if new_tokens < requested then
redis.call("HMSET", key, "tokens", new_tokens, "last_refill", now)
return 0 -- 限流
else
redis.call("HMSET", key, "tokens", new_tokens - requested, "last_refill", now)
return 1 -- 放行
end
六、应用场景 🌐
1. API网关限流

配置策略:
- 普通API:1000请求/秒
- 关键API:5000请求/秒 + 10000突发容量
- 敏感操作:严格限流 + 人工审核

2. 微服务间流量控制
// Spring Cloud Gateway集成
@Bean
public Customizer<RedisRateLimiter> redisRateLimiterCustomizer() {
return limiter -> limiter.setConfig(
RedisRateLimiter.Config.builder()
.burstCapacity(1000) // 突发容量
.replenishRate(500) // 每秒补充数
.build()
);
}
3. 数据库访问保护
# MyBatis插件配置
token-bucket:
db-read:
capacity: 500
rate: 200
db-write:
capacity: 100
rate: 50
七、高级流量控制策略
1. 分层令牌桶

2. 自适应限流
def adaptive_rate(current_load, error_rate):
# 基于系统负载动态调整速率
if error_rate > 0.1: # 错误率超过10%
return current_load * 0.8 # 降低20%流量
elif current_load < 0.5:
return current_load * 1.2 # 增加20%容量
else:
return current_load
3. 优先级令牌桶
public class PriorityTokenBucket {
private final TokenBucket highPriority;
private final TokenBucket mediumPriority;
private final TokenBucket lowPriority;
public boolean tryAcquire(Priority priority, int permits) {
switch (priority) {
case HIGH:
return highPriority.tryAcquire(permits) ||
(mediumPriority.tryAcquire(permits) &&
lowPriority.tryAcquire(permits * 2));
// ... 其他优先级策略
}
}
}
4. 对比漏桶算法 ⚖️
| 特性 | 令牌桶 | 漏桶 |
|---|---|---|
| 流量形状 | 允许突发流量 | 恒定速率 |
| 实现方式 | 添加令牌消耗令牌 | 固定速率漏水 |
| 适用场景 | 需要应对突发请求 | 需要绝对平滑限流 |
| 典型实现 | Guava RateLimiter | Nginx限流模块 |
八、高级技巧 🚀
1. 预热模式
// Guava的预热限流器
RateLimiter limiter = RateLimiter.create(10, 5, TimeUnit.SECONDS);
// 10个/秒,5秒预热期
2. 分布式限流
// 使用Redis+Lua实现
String luaScript = """
local key = KEYS[1]
local capacity = tonumber(ARGV[1])
local rate = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local requested = tonumber(ARGV[4])
-- 令牌桶算法实现...
""";
3. 单机优化技巧
| 优化点 | 效果 | 实现方式 |
|---|---|---|
| 无锁CAS | 提升50%吞吐量 | AtomicLong+compareAndSet |
| 时间轮算法 | 减少90%系统调用 | HashedWheelTimer |
| 批量令牌获取 | 降低60%锁竞争 | tryAcquireMultiPermits |
4. 分布式优化策略
# Redis集群优化配置
spring:
redis:
cluster:
nodes: redis1:6379,redis2:6379,redis3:6379
lettuce:
pool:
max-active: 32
max-idle: 16
timeout: 500ms
九、常见问题解答 ❓
Q1:令牌桶满了会怎样?
A:新生成的令牌会被丢弃,保持桶容量不变
Q2:如何处理突增流量?
A:可以通过临时扩大桶容量(动态调整参数)
Q3:与熔断降级的区别?
A:限流是预防措施,熔断是故障后的保护机制
1. 限流不均匀问题
原因分析:

解决方案:
- 使用NTP时间同步
- 采用Redis集群保证一致性
- 实现滑动窗口令牌补充
2. 突发流量处理
优化方案:
public class AdaptiveTokenBucket extends TokenBucket {
private double burstFactor = 1.5; // 突发因子
@Override
protected void refill() {
// 基于系统负载动态调整突发因子
double load = getSystemLoad();
if (load > 0.8) burstFactor = 1.0;
else if (load < 0.3) burstFactor = 2.0;
super.refill();
}
public boolean tryAcquire(int permits) {
double effectiveCapacity = capacity * burstFactor;
if (tokens > effectiveCapacity) {
return false; // 超过最大突发容量
}
return super.tryAcquire(permits);
}
}
十、实战案例 📈
案例1:电商大促秒杀系统
挑战:
- 瞬时流量:100万QPS
- 正常容量:5万QPS
解决方案:

效果:
- ✅ 系统稳定运行
- ✅ 错误率<0.1%
- ✅ 核心交易成功率99.99%
案例2:金融支付系统
特殊需求:
- VIP客户优先处理
- 风控请求实时处理
定制方案:
public class FinancialTokenBucket {
// 风控专用桶(50%容量保留)
private TokenBucket riskControlBucket = new TokenBucket(500, 100);
// VIP客户桶(30%容量)
private TokenBucket vipBucket = new TokenBucket(300, 50);
// 普通用户桶(20%容量)
private TokenBucket normalBucket = new TokenBucket(200, 30);
public boolean tryProcessPayment(User user, Payment payment) {
if (payment.isRiskControl()) {
return riskControlBucket.tryAcquire(1);
} else if (user.isVIP()) {
return vipBucket.tryAcquire(1);
} else {
return normalBucket.tryAcquire(1);
}
}
}
💡 建议:结合监控系统动态调整桶参数,实现智能限流
十一、最佳实践总结
-
容量规划公式:
桶容量 = 最大突发请求数 速率 = 平均QPS × 安全系数(1.2-1.5) -
多级防御体系:

-
监控关键指标:
- 令牌获取成功率
- 限流触发次数
- 桶容量利用率
- 令牌补充延迟
思考题:如果某个API需要给VIP用户更高频的访问权限,如何改造令牌桶算法?欢迎分享你的方案! 💭
令牌桶算法:流量控制的好帮手

1833

被折叠的 条评论
为什么被折叠?



