想象一下,你开了一家网红奶茶店,只有3名店员做奶茶。
如果不做任何限制,店员会瞬间手忙脚乱,点单系统崩溃,甚至做错饮料,最后所有客人都得不到服务(服务雪崩)。
限流(Rate Limiting) 就是为了解决这个问题:在流量过大时,通过限制请求速率,牺牲一部分请求,来保证系统的整体可用性。
这是最简单粗暴的算法。我们规定单位时间(比如1秒)内,只能处理固定数量(比如5个)的请求。

import java.util.concurrent.atomic.AtomicInteger;
public class FixedWindowRateLimiter {
// 阈值:每秒最多5个请求
private static final int LIMIT = 5;
// 窗口大小:1000毫秒
private static final long WINDOW_SIZE = 1000;
private static AtomicInteger counter = new AtomicInteger(0);
private static long lastTime = System.currentTimeMillis();
public static synchronized boolean tryAcquire() {
long currentTime = System.currentTimeMillis();
// 如果当前时间已经超过了上一个窗口,重置计数器
if (currentTime - lastTime > WINDOW_SIZE) {
counter.set(0);
lastTime = currentTime;
}
// 如果未达到阈值,允许通过
if (counter.get() < LIMIT) {
counter.incrementAndGet();
return true;
}
return false;
}
}假设限制是 1秒5个。
如果在 0.8秒到1.0秒 来了5个请求,1.0秒到1.2秒 又来了5个请求。
虽然每个独立的1秒窗口都没超标,但在 0.8秒到1.2秒 这短短0.4秒内,处理了 10个 请求!这可能会瞬间压垮系统。
为了解决固定窗口的“临界突刺”问题,我们把时间窗口划分得更细。
比如把1秒分成5个小格子(每个200ms)。随着时间流逝,窗口像幻灯片一样平滑地向右移动,每次只丢弃最老的一个小格子。

import java.util.LinkedList;
import java.util.Iterator;
public class SlidingWindowRateLimiter {
private static final int LIMIT = 5;
private static final long WINDOW_SIZE = 1000;
// 使用链表记录每个请求的时间戳
private LinkedList<Long> timestamps = new LinkedList<>();
public synchronized boolean tryAcquire() {
long now = System.currentTimeMillis();
// 1. 移除窗口之外的过期请求(比如1秒前的)
while (!timestamps.isEmpty() && (now - timestamps.peekFirst() > WINDOW_SIZE)) {
timestamps.removeFirst();
}
// 2. 检查当前窗口内的请求数
if (timestamps.size() < LIMIT) {
timestamps.addLast(now);
return true;
}
return false;
}
}把请求想象成水,倒入一个漏桶中。

public class LeakyBucketRateLimiter {
private long capacity = 10; // 桶容量
private long water = 0; // 当前水量
private long leakRate = 2; // 出水速率:每秒2滴
private long lastTime = System.currentTimeMillis();
public synchronized boolean tryAcquire() {
long now = System.currentTimeMillis();
// 计算过去这段时间流出了多少水
long leakedWater = (now - lastTime) / 1000 * leakRate;
// 更新水量(不能小于0)
water = Math.max(0, water - leakedWater);
lastTime = now;
// 尝试加水(处理请求)
if (water < capacity) {
water++;
return true;
}
return false;
}
}这是目前最常用的算法(如 Google Guava 的 RateLimiter)。

工业级开发中,我们通常直接使用 Google Guava 库:
import com.google.common.util.concurrent.RateLimiter;
public class TokenBucketDemo {
// 每秒生成5个令牌
private static RateLimiter rateLimiter = RateLimiter.create(5.0);
public static void doSomething() {
// 尝试获取令牌,如果拿不到会阻塞等待(也可以用 tryAcquire 非阻塞)
rateLimiter.acquire();
System.out.println("处理请求: " + System.currentTimeMillis());
}
}上面的算法都是针对单机的。如果是微服务架构,几十台服务器,怎么办?
这时通常使用 Redis 来实现。
利用 Redis 的原子性,结合 Lua 脚本实现全系统的流量控制。
// 伪代码示例
RRateLimiter rateLimiter = redissonClient.getRateLimiter("myKey");
// 初始化:每秒最多10个
rateLimiter.trySetRate(RateType.OVERALL, 10, 1, RateIntervalUnit.SECONDS);
if (rateLimiter.tryAcquire()) {
// 处理业务
} else {
// 限流
}算法 | 特点 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
固定窗口 | 简单计数 | 实现极其简单 | 存在临界突刺问题 | 流量平稳的简单场景 |
滑动窗口 | 细分时间格 | 解决临界问题,精度高 | 实现稍繁琐 | 很多限流框架的底层实现 (如 Sentinel) |
漏桶 | 宽进严出 | 输出速率恒定,平滑流量 | 无法处理突发流量 | 保护下游严格受限的系统 |
令牌桶 | 严进宽出 | 允许突发流量 | 实现稍复杂 | 最常用,兼顾稳定与突发 (如 Guava) |
希望这篇文章能帮你搞懂限流!如果觉得有用,记得点赞收藏哦!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。