限流算法
1、固定窗口
含义:
在一个固定长度的时间窗口内限制请求数量,每来一个请求,请求次数加一,如果请求数量超过最大限制,就拒绝该请求 优点:
实现简单,容易理解。 缺点:
①限流不够平滑。例如:限流是每秒3个,在第一毫秒发送了3个请求,达到限流,窗口剩余时间的请求都将会被拒绝,体验不好。 ②无法处理窗口边界问题。因为是在某个时间窗口内进行流量控制,所以可能会出现窗口边界效应,即在时间窗口的边界处可能会有大量的请求被允许通过,从而导致突发流量
public class FixWindowLimiter {
public static long threshold = 10 ;
public static long windowUnit = 1000 ;
public static long count = 0 ;
public static long lastTime = 0 ;
public boolean limit ( ) {
long currentTime = System . currentTimeMillis ( ) ;
if ( currentTime - lastTime > windowUnit) {
count = 0 ;
lastTime = currentTime;
}
if ( count < threshold) {
count++ ;
return true ;
}
return false ;
}
}
2、滑动窗口
定义:
每来一个请求,就向后推一个时间窗口,计算这个窗口内的请求数量。如果请求数量超过限制就拒绝请求,否则就处理请求,并记录请求的时间戳。另外还需要一个任务清理过期的时间戳。 优点:
解决了固定窗口算法的窗口边界问题,避免突发流量压垮服务器。 缺点:
还是存在限流不够平滑的问题
public class SlidingWindowLimiter {
public static long threshold = 10 ;
public static long windowUnit = 1000 ;
public static List < Long > requestList = new ArrayList < > ( ) ;
public boolean limit ( ) {
long currentTime = System . currentTimeMillis ( ) ;
int sizeOfValid = this . sizeOfValid ( currentTime) ;
if ( sizeOfValid < threshold) {
requestList. add ( currentTime) ;
return true ;
}
return false ;
}
private int sizeOfValid ( long currentTime) {
int sizeOfValid = 0 ;
for ( Long requestTime : requestList) {
if ( currentTime - requestTime <= windowUnit) {
sizeOfValid++ ;
}
}
return sizeOfValid;
}
private void clean ( ) {
requestList. removeIf ( requestTime -> System . currentTimeMillis ( ) - requestTime > windowUnit) ;
}
}
3、漏桶算法
定义:
一个固定容量的漏桶,按照固定速率出水(处理请求); 当流入水(请求数量)的速度过大会直接溢出(请求数量超过限制则直接拒绝)。桶里的水(请求)不够则无法出水(桶内没有请求则不处理) 优点:
①平滑流量。由于漏桶算法以固定的速率处理请求,可以有效地平滑和整形流量,避免流量的突发和波动(类似于消息队列的削峰填谷的作用)。 ②防止过载。当流入的请求超过桶的容量时,可以直接丢弃请求,防止系统过载。 缺点:
①无法处理突发流量:由于漏桶的出口速度是固定的,无法处理突发流量。例如,即使在流量较小的时候,也无法以更快的速度处理请求。 ②可能会丢失数据:如果入口流量过大,超过了桶的容量,那么就需要丢弃部分请求。在一些不能接受丢失请求的场景中,这可能是一个问题。 ③不适合速率变化大的场景
public class LeakyBucketLimiter {
public static long threshold = 10 ;
public static long count = 0 ;
public static long leakRate = 5 ;
public static long lastLeakTime = System . currentTimeMillis ( ) ;
public boolean limit ( ) {
this . leak ( ) ;
if ( count < threshold) {
count++ ;
return true ;
}
return false ;
}
private void leak ( ) {
long currentTime = System . currentTimeMillis ( ) ;
long leakWater = ( currentTime - lastLeakTime) * leakRate / 1000 ;
count = Math . max ( count - leakWater, 0 ) ;
lastLeakTime = currentTime;
}
}
4、令牌桶
定义:
统以固定的速率向桶中添加令牌; 当有请求到来时,会尝试从桶中移除一个令牌,如果桶中有足够的令牌,则请求可以被处理或数据包可以被发送; 如果桶中没有令牌,那么请求将被拒绝; 桶中的令牌数不能超过桶的容量,如果新生成的令牌超过了桶的容量,那么新的令牌会被丢弃 令牌桶算法的一个重要特性是,它能够应对突发流量。当桶中有足够的令牌时,可以一次性处理多个请求,这对于需要处理突发流量的应用场景非常有用 优点:
①可以处理突发流量:令牌桶算法可以处理突发流量。当桶满时,能够以最大速度处理请求。这对于需要处理突发流量的应用场景非常有用。 ②限制平均速率:在长期运行中,数据的传输率会被限制在预定义的平均速率(即生成令牌的速率)。 ③灵活性:与漏桶算法相比,令牌桶算法提供了更大的灵活性。例如,可以动态地调整生成令牌的速率。 缺点:
①可能导致过载:如果令牌产生的速度过快,可能会导致大量的突发流量,这可能会使网络或服务过载。 ②需要存储空间:令牌桶需要一定的存储空间来保存令牌,可能会导致内存资源的浪费。 ③实现稍复杂:相比于计数器算法,令牌桶算法的实现稍微复杂一些
public class TokenBucketLimiter {
public static long threshold = 10 ;
public static long count = 0 ;
public static long tokenRate = 5 ;
public static long lastRefillTime = System . currentTimeMillis ( ) ;
public boolean limit ( ) {
this . refillTokens ( ) ;
if ( count > 0 ) {
count-- ;
return true ;
}
return false ;
}
private void refillTokens ( ) {
long currentTime = System . currentTimeMillis ( ) ;
long refillTokens = ( currentTime - lastRefillTime) * tokenRate / 1000 ;
count = Math . min ( count + refillTokens, threshold) ;
lastRefillTime = currentTime;
}
}