副标题:令牌桶、漏桶、滑动窗口、计数器,四大天王谁更强?🎯
🎬 开场白:为什么需要限流?
想象一下这个场景:
你开了一家奶茶店🧋,正常情况下一个小时能做30杯奶茶。突然有一天,某个网红在抖音上推荐了你家奶茶,瞬间涌来500个客人...
没有限流的后果:
- 店员累倒了 😵(服务器宕机)
- 奶茶品质下降了 😭(响应变慢)
- 后面的客人白等了 😤(请求超时)
- 店铺被差评淹没了 💔(用户体验极差)
有限流的智慧:
- 排队叫号,有序接待 😊(流量控制)
- 品质稳定,顾客满意 ✨(服务质量保证)
- 超出容量就拒绝或延后 🎫(拒绝策略)
这就是限流的意义:在系统容量有限的情况下,保证服务质量!
📊 四大限流算法全解析
1️⃣ 固定窗口计数器(Fixed Window Counter)
原理图解
时间轴: 0s 1s 2s 3s 4s 5s
│────窗口1────│────窗口2────│────窗口3────│
请求数: [10个] [15个] [8个]
限制: 每秒最多20个请求
窗口1: ✅ 通过(10 < 20)
窗口2: ✅ 通过(15 < 20)
窗口3: ✅ 通过(8 < 20)
代码实现
public class FixedWindowRateLimiter {
private final int maxRequests; // 每个窗口最大请求数
private final long windowSize; // 窗口大小(毫秒)
private AtomicInteger counter = new AtomicInteger(0);
private long windowStart = System.currentTimeMillis();
public FixedWindowRateLimiter(int maxRequests, long windowSizeSeconds) {
this.maxRequests = maxRequests;
this.windowSize = windowSizeSeconds * 1000;
}
public synchronized boolean allowRequest() {
long now = System.currentTimeMillis();
// 判断是否需要重置窗口
if (now - windowStart >= windowSize) {
counter.set(0);
windowStart = now;
}
// 判断是否超过限制
if (counter.get() < maxRequests) {
counter.incrementAndGet();
return true;
}
return false; // 被限流
}
}
使用示例
// 每秒最多100个请求
FixedWindowRateLimiter limiter = new FixedWindowRateLimiter(100, 1);
if (limiter.allowRequest()) {
// 处理请求
processRequest();
} else {
// 返回限流错误
return "系统繁忙,请稍后再试";
}
致命缺陷:临界问题 ⚠️
0:59秒 1:00秒
│ │
────────┼──────────────┼────────
│ 100请求 │100请求
└──窗口1────┘└──窗口2──
↑
在1秒内通过了200个请求!
超出限制但没被拦截!
生活例子 🏥:
- 医院规定每小时接待100人
- 0:50-0:59来了100人(窗口1通过)
- 1:00-1:09又来了100人(窗口2通过)
- 实际上10分钟来了200人,但没被限流!
优点 ✅:
- 实现简单
- 内存占用小
- 性能高
缺点 ❌:
- 临界问题严重
- 无法应对突发流量
- 流量不平滑
2️⃣ 滑动窗口计数器(Sliding Window Counter)
原理图解
固定窗口 vs 滑动窗口
固定窗口:
│──窗口1──│──窗口2──│──窗口3──│
0-1s 1-2s 2-3s
滑动窗口:
│──────│
└──────│
└──────│
└──────│
每次都统计当前时间往前推1秒的请求数
精细化实现:分片统计
将1秒划分为10个小格(每格100ms)
0 100 200 300 400 500 600 700 800 900 1000ms
│────│────│────│────│────│────│────│────│────│────│
[5] [8] [12] [7] [6] [9] [11] [8] [10] [4]
当前时间:850ms
统计范围:850ms - 750ms = 前1秒
计算方法:从当前格往前推10格的总和
代码实现
public class SlidingWindowRateLimiter {
private final int maxRequests; // 最大请求数
private final long windowSize; // 窗口大小(毫秒)
private final int sliceSize; // 分片数量
private final AtomicInteger[] counters; // 每个分片的计数器
private final long sliceDuration; // 每个分片的时长
public SlidingWindowRateLimiter(int maxRequests, long windowSizeSeconds, int sliceSize) {
this.maxRequests = maxRequests;
this.windowSize = windowSizeSeconds * 1000;
this.sliceSize = sliceSize;
this.sliceDuration = windowSize / sliceSize;
this.counters = new AtomicInteger[sliceSize];
for (int i = 0; i < sliceSize; i++) {
counters[i] = new AtomicInteger(0);
}
}
public boolean allowRequest() {
long now = System.currentTimeMillis();
// 计算当前所在的分片
int currentSlice = (int) ((now / sliceDuration) % sliceSize);
// 清理过期的分片
cleanExpiredSlices(now);
// 统计当前窗口内的总请求数
int totalRequests = 0;
for (AtomicInteger counter : counters) {
totalRequests += counter.get();
}
// 判断是否超过限制
if (totalRequests < maxRequests) {
counters[currentSlice].incrementAndGet();
return true;
}
return false;
}
private void cleanExpiredSlices(long now) {
long expireTime = now - windowSize;
for (int i = 0; i < sliceSize; i++) {
long sliceStartTime = (now / sliceDuration - i) * sliceDuration;
if (sliceStartTime < expireTime) {
counters[i].set(0);
}
}
}
}
Sentinel的实现:LeapArray
// Sentinel底层使用的滑动窗口实现
public class LeapArray<T> {
// 窗口时长(毫秒)
protected int windowLengthInMs;
// 采样窗口数量
protected int sampleCount;
// 采样间隔
protected int intervalInMs;
// 核心数组:环形数组存储窗口
protected final AtomicReferenceArray<WindowWrap<T>> array;
public WindowWrap<T> currentWindow() {
return currentWindow(TimeUtil.currentTimeMillis());
}
public WindowWrap<T> currentWindow(long timeMillis) {
long timeId = timeMillis / windowLengthInMs;
int idx = (int)(timeId % array.length());
// 计算窗口开始时间
long windowStart = timeId * windowLengthInMs;
while (true) {
WindowWrap<T> old = array.get(idx);
// 1. 桶为空,创建新桶
if (old == null) {
WindowWrap<T> window = new WindowWrap<>(windowLengthInMs, windowStart, newEmptyBucket());
if (array.compareAndSet(idx, null, window)) {
return window;
} else {
Thread.yield();
}
}
// 2. 桶的时间正好,直接使用
else if (windowStart == old.windowStart()) {
return old;
}
// 3. 桶的时间过期了,重置
else if (windowStart > old.windowStart()) {
if (updateLock.tryLock()) {
try {
return resetWindowTo(old, windowStart);
} finally {
updateLock.unlock();
}
} else {
Thread.yield();
}
}
// 4. 桶的时间在未来(并发问题),等待
else {
return new WindowWrap<>(windowLengthInMs, windowStart, newEmptyBucket());
}
}
}
}
优点 ✅:
- 解决了临界问题
- 流量统计更精确
- 能应对突发流量
缺点 ❌:
- 实现复杂
- 内存占用较大
- 性能略低于固定窗口
3️⃣ 漏桶算法(Leaky Bucket)
核心思想
就像一个漏水的桶:
请求流入
↓↓↓
┌─────────┐
│ ╔═══╗ │ 桶的容量固定
│ ║ ║ │
│ ║░░░║ │ ← 桶中的水(待处理请求)
│ ║░░░║ │
│ ╚═══╝ │
└────┬────┘
│←── 固定速率流出
↓
处理请求
关键特点:
- 进水不定:请求随时可能来
- 出水恒定:处理速率固定
- 水满溢出:超过容量的请求被拒绝
代码实现
public class LeakyBucketRateLimiter {
private final int capacity; // 桶的容量
private final int leakRate; // 漏出速率(每秒)
private AtomicInteger water = new AtomicInteger(0); // 当前水量
private long lastLeakTime = System.currentTimeMillis();
public LeakyBucketRateLimiter(int capacity, int leakRatePerSecond) {
this.capacity = capacity;
this.leakRate = leakRatePerSecond;
}
public synchronized boolean allowRequest() {
long now = System.currentTimeMillis();
// 先漏水:计算从上次到现在漏掉了多少水
long elapsedTime = now - lastLeakTime;
int leaked = (int) (elapsedTime * leakRate / 1000);
if (leaked > 0) {
int newWater = Math.max(0, water.get() - leaked);
water.set(newWater);
lastLeakTime = now;
}
// 判断是否还有空间
if (water.get() < capacity) {
water.incrementAndGet();
return true;
}
return false; // 桶满了,拒绝请求
}
}
使用示例
// 桶容量100,每秒漏出10个请求
LeakyBucketRateLimiter limiter = new LeakyBucketRateLimiter(100, 10);
for (int i = 0; i < 200; i++) {
if (limiter.allowRequest()) {
System.out.println("请求" + i + "通过");
processRequest();
} else {
System.out.println("请求" + i + "被拒绝(桶满了)");
}
Thread.sleep(50); // 模拟请求间隔
}
生活例子 🚰
水龙头和水桶:
- 你拧开水龙头(请求涌入),水流忽大忽小
- 水桶底部有个小孔(固定处理速率)
- 水龙头流速 > 漏水速度 → 桶会满 → 溢出(拒绝请求)
- 水龙头流速 < 漏水速度 → 桶不会满 → 所有水都能流出
优点 ✅:
- 平滑输出:无论输入多大,输出速率恒定
- 防止突发:能削峰填谷
- 简单实现:逻辑清晰
缺点 ❌:
- 无法应对突发:即使系统闲置,也不能快速处理
- 可能造成等待:请求需要排队
4️⃣ 令牌桶算法(Token Bucket)⭐
核心思想
系统定期发放令牌,有令牌才能处理请求:
定期生成令牌
↓↓↓
┌─────────┐
│ ◉ ◉ ◉ │ ← 令牌桶
│ ◉ ◉ ◉ │ 容量固定
│ ◉ ◉ │
└────┬────┘
↓
拿令牌 → 处理请求
没令牌 → 拒绝/等待
关键特点:
- 令牌生成速率固定:比如每秒生成100个
- 桶有容量上限:比如最多存1000个
- 可以积攒令牌:闲时积累,忙时使用
- 支持突发流量:一次性拿多个令牌
代码实现(简化版)
public class TokenBucketRateLimiter {
private final long capacity; // 桶的容量
private final long refillRate; // 令牌生成速率(每秒)
private AtomicLong tokens; // 当前令牌数
private long lastRefillTime; // 上次填充时间
public TokenBucketRateLimiter(long capacity, long refillRatePerSecond) {
this.capacity = capacity;
this.refillRate = refillRatePerSecond;
this.tokens = new AtomicLong(capacity); // 初始满令牌
this.lastRefillTime = System.currentTimeMillis();
}
public synchronized boolean allowRequest() {
return allowRequest(1);
}
public synchronized boolean allowRequest(int tokensNeeded) {
// 先补充令牌
refillTokens();
// 判断令牌是否足够
if (tokens.get() >= tokensNeeded) {
tokens.addAndGet(-tokensNeeded);
return true;
}
return false;
}
private void refillTokens() {
long now = System.currentTimeMillis();
long elapsedTime = now - lastRefillTime;
// 计算应该生成的令牌数
long newTokens = elapsedTime * refillRate / 1000;
if (newTokens > 0) {
long currentTokens = tokens.get();
long updatedTokens = Math.min(capacity, currentTokens + newTokens);
tokens.set(updatedTokens);
lastRefillTime = now;
}
}
// 获取当前可用令牌数
public long availableTokens() {
refillTokens();
return tokens.get();
}
}
Google Guava实现
import com.google.common.util.concurrent.RateLimiter;
public class GuavaRateLimiterExample {
public static void main(String[] args) {
// 每秒生成5个令牌
RateLimiter rateLimiter = RateLimiter.create(5.0);
for (int i = 0; i < 10; i++) {
// acquire()会阻塞等待,直到获取到令牌
double waitTime = rateLimiter.acquire();
System.out.println("等待了 " + waitTime + " 秒,处理请求" + i);
}
}
}
高级特性:预热限流(Warm Up)
// 从初始速率逐渐提升到目标速率
RateLimiter limiter = RateLimiter.create(
100.0, // 目标速率:每秒100个
3, // 预热时间:3秒
TimeUnit.SECONDS
);
// 前3秒速率从 100/3 ≈ 33 逐渐提升到 100
应用场景:
- 系统刚启动,缓存还是空的
- 数据库连接池还没完全建立
- 需要预热才能达到最佳性能
支持突发流量
// 桶容量1000,每秒生成100个令牌
TokenBucketRateLimiter limiter = new TokenBucketRateLimiter(1000, 100);
// 系统闲置了10秒,积累了1000个令牌
// 突然来了500个请求,可以瞬间处理!
for (int i = 0; i < 500; i++) {
if (limiter.allowRequest()) {
processRequest(); // 全部通过!
}
}
生活例子 🎫
游乐园门票:
- 游乐园每小时发放100张门票(生成令牌)
- 门票可以积累,最多积累1000张(桶容量)
- 游客拿着门票就能进入(消费令牌)
- 早上游客少,门票积累了500张
- 中午游客突然增多,500个游客可以同时进入!
优点 ✅:
- 支持突发流量:积累的令牌可以瞬间使用
- 流量平滑:长期平均速率受控
- 灵活性高:可以支持预热、动态调整
缺点 ❌:
- 实现稍复杂:需要定期生成令牌
- 分布式难:多机器令牌同步困难
🆚 四大算法对比
| 算法 | 能否应对突发 | 流量平滑度 | 实现复杂度 | 适用场景 |
|---|---|---|---|---|
| 固定窗口 | ❌ | ⭐ | ⭐⭐⭐ | 简单计数限流 |
| 滑动窗口 | ⚡ | ⭐⭐⭐ | ⭐⭐ | 精确统计场景 |
| 漏桶 | ❌ | ⭐⭐⭐⭐⭐ | ⭐⭐ | 平滑输出、消息队列 |
| 令牌桶 | ✅ | ⭐⭐⭐⭐ | ⭐⭐ | 互联网API、网关 |
场景选择决策树
开始
├─ 需要应对突发流量?
│ ├─ 是 → 令牌桶 ⭐⭐⭐
│ └─ 否 ↓
│
├─ 需要绝对平滑的输出?
│ ├─ 是 → 漏桶 ⭐⭐⭐
│ └─ 否 ↓
│
├─ 需要精确统计?
│ ├─ 是 → 滑动窗口 ⭐⭐⭐
│ └─ 否 ↓
│
└─ 追求极致性能?
└─ 是 → 固定窗口 ⭐⭐
🌐 分布式限流实现
Redis + Lua实现令牌桶
-- token_bucket.lua
local key = KEYS[1] -- 令牌桶的key
local capacity = tonumber(ARGV[1]) -- 桶容量
local rate = tonumber(ARGV[2]) -- 生成速率(每秒)
local requested = tonumber(ARGV[3]) -- 请求的令牌数
local now = tonumber(ARGV[4]) -- 当前时间戳
-- 获取当前令牌数和上次更新时间
local token_data = redis.call('hmget', key, 'tokens', 'last_time')
local tokens = tonumber(token_data[1]) or capacity
local last_time = tonumber(token_data[2]) or 0
-- 计算应该生成的令牌数
local elapsed = math.max(0, now - last_time)
local new_tokens = math.min(capacity, tokens + elapsed * rate)
-- 判断令牌是否足够
if new_tokens >= requested then
new_tokens = new_tokens - requested
redis.call('hmset', key, 'tokens', new_tokens, 'last_time', now)
redis.call('expire', key, 60)
return 1 -- 允许请求
else
return 0 -- 拒绝请求
end
Java调用代码
@Service
public class RedisRateLimiter {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private DefaultRedisScript<Long> script;
@PostConstruct
public void init() {
script = new DefaultRedisScript<>();
script.setScriptSource(new ResourceScriptSource(
new ClassPathResource("token_bucket.lua")
));
script.setResultType(Long.class);
}
public boolean allowRequest(String key, long capacity, long rate, long requested) {
List<String> keys = Collections.singletonList(key);
Long result = redisTemplate.execute(
script,
keys,
capacity,
rate,
requested,
System.currentTimeMillis() / 1000
);
return result != null && result == 1;
}
}
Sentinel的分布式限流
@Service
public class OrderService {
// 基于QPS的限流
@SentinelResource(
value = "createOrder",
blockHandler = "handleBlock"
)
public Order createOrder(OrderRequest request) {
// 创建订单逻辑
return orderRepository.save(request.toOrder());
}
// 限流后的处理方法
public Order handleBlock(OrderRequest request, BlockException ex) {
log.warn("订单创建被限流: {}", request);
throw new BusinessException("系统繁忙,请稍后再试");
}
}
// 配置限流规则
@Configuration
public class SentinelConfig {
@PostConstruct
public void initFlowRules() {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
rule.setResource("createOrder");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS); // QPS限流
rule.setCount(100); // 每秒100个请求
rule.setStrategy(RuleConstant.STRATEGY_DIRECT); // 直接拒绝
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
}
🎯 实战案例
案例1:电商秒杀系统
需求:
- 10万人抢100个商品
- 保护后端服务不被打垮
- 给用户友好的提示
方案:多级限流
@RestController
@RequestMapping("/seckill")
public class SeckillController {
// 第一层:网关层限流(令牌桶)
// 配置:每秒允许1000个请求进入系统
// 第二层:接口层限流(滑动窗口)
@SentinelResource(
value = "seckill",
blockHandler = "seckillBlock"
)
@PostMapping("/buy")
public Result<String> seckill(@RequestParam Long productId,
@RequestParam Long userId) {
// 第三层:热点参数限流
// 对特定商品ID进行单独限流
// 第四层:Redis库存预扣减
String stockKey = "stock:" + productId;
Long stock = redisTemplate.opsForValue().decrement(stockKey);
if (stock < 0) {
// 回滚
redisTemplate.opsForValue().increment(stockKey);
return Result.fail("商品已售罄");
}
// 异步创建订单
seckillService.createOrderAsync(productId, userId);
return Result.success("抢购成功,订单生成中...");
}
// 限流后的友好提示
public Result<String> seckillBlock(Long productId, Long userId, BlockException ex) {
log.warn("秒杀被限流: product={}, user={}", productId, userId);
return Result.fail("人数过多,请稍后再试");
}
}
案例2:OpenAPI限流
需求:
- 不同用户不同限额
- VIP用户有更高限额
- 按小时、按天统计
方案:多维度限流
@Component
public class ApiRateLimiter {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public boolean checkLimit(String apiKey, String api) {
// 获取用户等级
UserLevel level = getUserLevel(apiKey);
// 多个维度的限流
return checkMinuteLimit(apiKey, api, level)
&& checkHourLimit(apiKey, api, level)
&& checkDayLimit(apiKey, api, level);
}
private boolean checkMinuteLimit(String apiKey, String api, UserLevel level) {
String key = String.format("limit:minute:%s:%s:%s",
apiKey, api, getCurrentMinute());
int limit = level == UserLevel.VIP ? 100 : 10; // VIP 100次/分钟
Long count = redisTemplate.opsForValue().increment(key);
redisTemplate.expire(key, 1, TimeUnit.MINUTES);
return count <= limit;
}
private boolean checkHourLimit(String apiKey, String api, UserLevel level) {
String key = String.format("limit:hour:%s:%s:%s",
apiKey, api, getCurrentHour());
int limit = level == UserLevel.VIP ? 5000 : 500; // VIP 5000次/小时
Long count = redisTemplate.opsForValue().increment(key);
redisTemplate.expire(key, 1, TimeUnit.HOURS);
return count <= limit;
}
private boolean checkDayLimit(String apiKey, String api, UserLevel level) {
String key = String.format("limit:day:%s:%s:%s",
apiKey, api, getCurrentDay());
int limit = level == UserLevel.VIP ? 100000 : 10000; // VIP 10万次/天
Long count = redisTemplate.opsForValue().increment(key);
redisTemplate.expire(key, 1, TimeUnit.DAYS);
return count <= limit;
}
}
💡 最佳实践
1. 限流提示要友好
❌ 糟糕的提示:
{
"code": 429,
"message": "Too Many Requests"
}
✅ 友好的提示:
{
"code": 429,
"message": "您的操作过于频繁,请稍后再试",
"retryAfter": 60,
"tips": "VIP用户无此限制,升级VIP享更多权益",
"currentLimit": "10次/分钟",
"remainingQuota": 0
}
2. 监控和告警
@Component
public class RateLimiterMonitor {
@Autowired
private MeterRegistry meterRegistry;
public void recordBlock(String resource, String reason) {
// 记录被限流的次数
Counter.builder("rate_limiter.blocked")
.tag("resource", resource)
.tag("reason", reason)
.register(meterRegistry)
.increment();
// 触发告警
if (getBlockedCount(resource) > 1000) {
alertService.send("限流告警",
resource + " 在1分钟内被限流超过1000次");
}
}
}
3. 动态调整限流阈值
@Service
public class DynamicRateLimitService {
@Scheduled(fixedRate = 60000) // 每分钟检查一次
public void adjustRateLimit() {
// 获取当前系统负载
double cpuUsage = systemMonitor.getCpuUsage();
double memoryUsage = systemMonitor.getMemoryUsage();
// 根据负载动态调整
int baseQps = 1000;
int adjustedQps;
if (cpuUsage > 80 || memoryUsage > 80) {
// 系统压力大,降低限流阈值
adjustedQps = (int) (baseQps * 0.7);
} else if (cpuUsage < 50 && memoryUsage < 50) {
// 系统空闲,提高限流阈值
adjustedQps = (int) (baseQps * 1.3);
} else {
adjustedQps = baseQps;
}
updateRateLimitRule("api", adjustedQps);
}
}
🎓 面试高频问题
Q1:令牌桶和漏桶的区别?
A:
| 对比项 | 令牌桶 | 漏桶 |
|---|---|---|
| 处理速率 | 可以突发,有令牌就能处理 | 恒定速率,严格匀速 |
| 令牌/水 | 定期生成令牌 | 定期漏出水 |
| 闲时 | 积累令牌,可应对突发 | 无法积累,闲置就浪费 |
| 应用 | API限流、网关 | 消息队列、流量整形 |
生活比喻:
- 令牌桶:健身房月卡,可以连续来几天,也可以攒着不来
- 漏桶:传送带,无论你放多少东西,输出速度永远是固定的
Q2:分布式环境下如何限流?
A:
方案1:Redis集中式限流
// 优点:全局统一、精确
// 缺点:依赖Redis、有网络开销
方案2:Nginx限流
# 限制每个IP每秒10个请求
limit_req_zone $binary_remote_addr zone=myLimit:10m rate=10r/s;
server {
location /api/ {
limit_req zone=myLimit burst=20 nodelay;
}
}
方案3:本地限流 + 动态调整
// 每个节点独立限流
// 通过配置中心动态调整各节点限额
// 优点:性能高、无中心依赖
// 缺点:不够精确
Q3:高并发下如何优化限流性能?
A:
- 使用原子操作
// 使用AtomicLong代替synchronized
private AtomicLong counter = new AtomicLong(0);
- 减少锁粒度
// 分段锁:将限流器拆分为多个
RateLimiter[] limiters = new RateLimiter[10];
int index = Math.abs(userId.hashCode() % 10);
limiters[index].acquire();
- 使用本地缓存
// 缓存限流配置,避免频繁查询Redis
LoadingCache<String, RateLimiter> cache = CacheBuilder.newBuilder()
.expireAfterWrite(1, TimeUnit.MINUTES)
.build(new CacheLoader<String, RateLimiter>() {
public RateLimiter load(String key) {
return RateLimiter.create(getRate(key));
}
});
🎉 总结
核心要点回顾
-
四大算法:
- 固定窗口:简单但有临界问题
- 滑动窗口:精确但复杂
- 漏桶:平滑但无法突发
- 令牌桶:灵活,最常用 ⭐
-
选择依据:
- 需要应对突发?→ 令牌桶
- 需要绝对平滑?→ 漏桶
- 需要精确统计?→ 滑动窗口
- 追求简单高效?→ 固定窗口
-
分布式方案:
- Redis + Lua(精确)
- Nginx限流(入口)
- 本地限流(高性能)
记忆口诀 📝
限流算法有四种,
令牌漏桶窗口型。
令牌桶里攒令牌,
突发流量能应对。
漏桶恒速很平滑,
输出速率不会变。
滑动窗口最精确,
统计数据很准确。
固定窗口虽简单,
临界问题要小心。
分布式用Redis好,
Lua脚本保原子。
监控告警不能少,
动态调整更灵活!
📚 参考资料
- Guava RateLimiter源码
- Sentinel限流原理
- Token Bucket Algorithm - Wikipedia
- Leaky Bucket Algorithm - Wikipedia
最后送你一句话:
"限流不是为了拒绝用户,而是为了保护系统,给所有用户更好的体验。"
愿你的系统永不宕机,流量永远丝滑! 🚀✨
表情包时间 🎭
当流量正常时:
😊 系统运行平稳,用户体验良好
当流量突增但有限流:
🛡️ 限流器启动,保护系统稳定
当流量突增但没限流:
💥 服务器:我太难了...(宕机)
当用户被限流时:
😅 "系统繁忙,请稍后再试"
总比看到500错误强吧!