固定窗口算法
固定窗口算法
-
原理:固定窗口算法通过在单位时间内维护一个计数器,限制在每个固定的时间段内请求通过的次数,以达到限流的效果。
-
优点:算法实现简单,容易理解。
-
缺点:不能应对突发流量,可能导致短时间内流量激增,从而影响服务稳定性。
java
public class FixedWindowRateLimiter {
private long windowSize; // 时间窗口大小,单位毫秒
private int maxRequestCount; // 允许通过请求数
private AtomicInteger count = new AtomicInteger(0); // 当前窗口通过的请求计数
private long windowBorder; // 窗口右边界
public FixedWindowRateLimiter(long windowSize, int maxRequestCount) {
this.windowSize = windowSize;
this.maxRequestCount = maxRequestCount;
windowBorder = System.currentTimeMillis() + windowSize;
}
public synchronized boolean tryAcquire() {
long currentTime = System.currentTimeMillis();
if (windowBorder < currentTime) {
windowBorder += windowSize;
count.set(0);
}
if (count.intValue() < maxRequestCount) {
count.incrementAndGet();
return true;
} else {
return false;
}
}
}
滑动窗口算法
滑动窗口算法
-
原理:滑动窗口算法在固定窗口的基础上进行了改进,将时间窗口划分为多个小块,每个小块维护独立的计数器,每次滑动时,减去前一个时间块内的请求数量,并添加一个新的时间块到末尾。
-
优点:流量控制更精细,能够更平滑地处理请求。
-
缺点:需要额外的存储空间来维护每一块时间内的计数器,实现相对复杂。
arduino
public class SlidingWindowRateLimiter {
private long windowSize; // 时间窗口大小,单位毫秒
private int shardNum; // 分片窗口数
private int maxRequestCount; // 允许通过请求数
private int[] shardRequestCount; // 各个窗口内请求计数
private int totalCount; // 请求总数
private int shardId; // 当前窗口下标
private long tinyWindowSize; // 每个小窗口大小,毫秒
private long windowBorder; // 窗口右边界
public SlidingWindowRateLimiter(long windowSize, int shardNum, int maxRequestCount) {
this.windowSize = windowSize;
this.shardNum = shardNum;
this.maxRequestCount = maxRequestCount;
shardRequestCount = new int[shardNum];
tinyWindowSize = windowSize / shardNum;
windowBorder = System.currentTimeMillis();
}
public synchronized boolean tryAcquire() {
long currentTime = System.currentTimeMillis();
if (currentTime > windowBorder) {
do {
shardId = (++shardId) % shardNum;
totalCount -= shardRequestCount[shardId];
shardRequestCount[shardId] = 0;
windowBorder += tinyWindowSize;
} while (windowBorder < currentTime);
}
if (totalCount < maxRequestCount) {
shardRequestCount[shardId]++;
totalCount++;
return true;
} else {
return false;
}
}
}
漏桶算法
漏桶算法
-
原理:漏桶算法通过以固定速率出水,将外部请求比作水,不断注入水桶中,如果水超过桶的最大容量则被丢弃,以此来控制数据的传输速率。
-
优点:能够有效地控制数据的传输速率,平滑处理请求。
-
缺点:不管系统负载如何,所有请求都得排队,可能导致系统资源的浪费。
csharp
public class LeakyBucketRateLimiter {
private int capacity; // 桶的容量
private AtomicInteger water = new AtomicInteger(0); // 桶中现存水量
private long leakTimeStamp; // 开始漏水时间
private int leakRate; // 水流出的速率,即每秒允许通过的请求数
public LeakyBucketRateLimiter(int capacity, int leakRate) {
this.capacity = capacity;
this.leakRate = leakRate;
}
public synchronized boolean tryAcquire() {
if (water.get() == 0) {
leakTimeStamp = System.currentTimeMillis();
water.incrementAndGet();
return water.get() < capacity;
}
long currentTime = System.currentTimeMillis();
int leakedWater = (int)((currentTime - leakTimeStamp) / 1000 * leakRate);
if (leakedWater != 0) {
int leftWater = water.get() - leakedWater;
water.set(Math.max(0, leftWater));
leakTimeStamp = System.currentTimeMillis();
}
if (water.get() < capacity) {
water.incrementAndGet();
return true;
} else {
return false;
}
}
}
令牌桶算法
令牌桶算法
-
原理:令牌桶算法通过系统以恒定的速度生成令牌,并将令牌放入令牌桶中,请求必须从令牌桶中获取一个令牌才能被处理,如果桶中没有令牌,则请求被限流。
-
优点:允许一定程度的突发请求,能够限制服务调用的平均速率,同时允许一定程度的突发调用。
-
缺点:无明显缺点,是较为理想的限流算法。
ini
RateLimiter rateLimiter = RateLimiter.create(5);
for (int i = 0; i < 10; i++) {
double time = rateLimiter.acquire();
System.out.println("等待时间:" + time + "s");
}
中间件限流
-
工具:Sentinel
-
原理:Sentinel 是 Spring Cloud Alibaba 中的熔断限流组件,通过在服务层的方法上添加注解来实现限流。
-
优点:适用于分布式、微服务架构,提供开箱即用的限流方法。
-
缺点:需要引入额外的组件。
typescript
@Service
public class QueryService {
public static final String KEY = "query";
@SentinelResource(value = KEY, blockHandler = "blockHandlerMethod")
public String query(String name) {
return "begin query, name=" + name;
}
public String blockHandlerMethod(String name, BlockException e) {
e.printStackTrace();
return "blockHandlerMethod for Query : " + name;
}
}
网关限流
-
工具:Spring Cloud Gateway
-
原理:通过配置网关规则,使用 Redis 加 Lua 脚本的方式实现令牌桶算法进行限流。
-
优点:可以灵活设置限流的维度,如请求路径、用户信息等。
-
缺点:需要配置和维护 Redis。
typescript
@Component
public class PathKeyResolver implements KeyResolver {
public Mono<String> resolve(ServerWebExchange exchange) {
String path = exchange.getRequest().getPath().toString();
return Mono.just(path);
}
}
结论
限流是确保系统稳定性的关键措施之一。文章介绍了六种限流方法,包括固定窗口算法、滑动窗口算法、漏桶算法、令牌桶算法、中间件限流和网关限流,各有优缺点,适用于不同的场景和架构。