悲痛使人们的心贴近了,世俗的欢乐无法使之分离。心中忧愁的纽带要比喜悦的纽带更牢固,用眼泪洗濯过的爱情,始终是纯洁,美丽和永存的
限流(Rate Limiting)是一种计算机系统中常见的流量控制技术,用于限制系统或者应用程序中某个时间段内的请求或者事件数量。限流的目的是避免系统因为过多的请求或者事件而过载、崩溃或者出现雪崩效应。
限流技术的主要目的是对系统进行流量控制,防止系统负载过高,导致性能下降,甚至崩溃。通常情况下,限流技术可以采用以下方式:
-
固定窗口限流:将请求计数器与固定的时间窗口结合使用,每个时间窗口内只允许通过一定数量的请求。
-
滑动窗口限流:将请求计数器与滑动的时间窗口结合使用,计数器在时间轴上滑动,以统计时间窗口内的请求数量。
-
令牌桶限流:将请求放入一个令牌桶中,每个请求必须获取一个令牌才能执行。
-
漏桶限流:通过固定的速率将请求放入一个漏桶中,漏桶会以固定的速率处理请求。
限流技术的主要功能是对请求进行计数和统计,并且对超出限制的请求进行限制、拒绝或者延迟处理。通过限流技术,可以避免系统因为过载而崩溃,提高系统的稳定性和可用性,保证系统正常运行。
限流技术的优势在于,可以对系统的流量进行有效的控制和调度,避免系统负载过高,从而提高系统的稳定性和可用性。另外,限流技术还可以预防恶意攻击和异常行为,保护系统的安全性。
限流技术的劣势在于,需要开发者手动编写大量的代码来实现限流算法,如果实现不当可能会出现错误。另外,限流技术可能会对系统的性能产生一定的影响,因此需要开发者进行充分的测试和优化。
限流的原理是通过一定的算法和策略,对请求进行计数和统计,然后根据设定的阈值和限制规则,对请求进行拒绝、延迟或者降级处理,从而控制系统的负载和流量。
限流的实现需要根据具体的业务场景和系统特点来选择合适的限流算法和策略。合理地实现限流算法可以有效地保护系统的稳定性和可用性,提高系统的抗压能力和可靠性。
限流方案
- 固定窗口限流
固定窗口限流是将请求计数器与固定的时间窗口结合使用,每个时间窗口内只允许通过一定数量的请求。例如,将每秒钟分成 10 个时间窗口,每个时间窗口内最多只能通过 100 个请求。如果超过了这个限制,请求将被阻塞或者丢弃。固定窗口限流的优点是实现简单,缺点是不够灵活,可能会出现突发流量导致系统崩溃的问题。
- 滑动窗口限流
滑动窗口限流是将请求计数器与滑动的时间窗口结合使用,计数器在时间轴上滑动,以统计时间窗口内的请求数量。例如,将每秒钟分成 10 个时间窗口,每个时间窗口内最多只能通过 100 个请求。计数器每隔 0.1 秒就会向前滑动一个时间窗口,以统计最近 1 秒钟内的请求数量。滑动窗口限流的优点是灵活,可以根据具体情况进行调整,缺点是实现相对复杂。
- 令牌桶限流
令牌桶限流是将请求放入一个令牌桶中,每个请求必须获取一个令牌才能执行。例如,在每秒钟内最多只能生成 100 个令牌,每个请求需要消耗一个令牌才能执行。如果令牌桶中的令牌数量不足,请求将被阻塞或者丢弃。令牌桶限流的优点是实现相对简单,支持短时突发流量,缺点是可能会因为长时间的流量波动而出现限流不平稳的问题。
- 漏桶限流
漏桶限流是通过固定的速率将请求放入一个漏桶中,漏桶会以固定的速率处理请求。例如,将漏桶的容量设定为 1000,处理速度为每秒钟 100 个请求,当漏桶中的请求数量超过 1000 时,新的请求将被阻塞或者丢弃。漏桶限流的优点是能够平滑处理流量,缺点是实现较为复杂,对于短时间内的突发流量可能不够有效。
固定窗口限流
在 Spring Boot 中实现固定窗口限流可以采用拦截器(Interceptor)的方式对请求进行拦截和限流控制,主要步骤如下:
-
定义一个拦截器类,在
preHandle
方法中实现固定窗口限流的逻辑。 -
在拦截器中维护一个计数器和一个时间窗口的大小,例如每秒钟只允许通过 100 个请求。
-
在
preHandle
方法中,记录当前时间窗口内通过的请求数量,并判断该请求是否超过了限制。 -
如果请求通过限流检查,则允许请求继续执行;否则返回一个限流提示信息,拒绝该请求。
-
将拦截器注册到 Spring Boot 的拦截器链中,以实现对请求的限流控制。
下面是一个基于 Spring Boot 的固定窗口限流实现示例:
@Component
public class RateLimitInterceptor implements HandlerInterceptor {
private static final int MAX_REQUESTS = 100; // 每秒钟最多通过 100 个请求
private static final int WINDOW_SIZE = 10; // 时间窗口大小为 10 秒
private int[] window = new int[WINDOW_SIZE]; // 存储每个时间窗口内通过的请求数量
private int cursor = 0; // 当前时间窗口的指针
public RateLimitInterceptor() {
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
executor.scheduleAtFixedRate(this::resetWindow, WINDOW_SIZE, WINDOW_SIZE, TimeUnit.SECONDS);
}
private void resetWindow() {
synchronized (this) {
window = new int[WINDOW_SIZE];
cursor = 0;
}
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取当前时间窗口的请求数量
int count;
synchronized (this) {
count = window[cursor];
}
// 判断当前请求数量是否超过限制
if (count >= MAX_REQUESTS) {
// 超过限制,拒绝该请求
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
response.getWriter().write("Too many requests. Please try again later.");
return false;
}
// 请求数量未超过限制,允许通过该请求
synchronized (this) {
window[cursor] = count + 1;
cursor = (cursor + 1) % WINDOW_SIZE;
}
return true;
}
}
在上述代码中,我们新增了一个构造函数,在其中创建了一个定时器,定时重置时间窗口计数器。resetWindow
方法用于重置计数器,它在定时器中被调用,通过 synchronized
关键字实现线程安全。在 preHandle
方法中,我们需要对操作时间窗口的代码进行加锁,确保线程安全。通过一个长度为 1000 的数组 window
来记录每个时间窗口内通过的请求数量,通过一个 cursor
变量来指向当前的时间窗口。在 preHandle
方法中,记录当前时间窗口内通过的请求数量,并判断该请求是否超过了限制。如果请求通过限流检查,则允许请求继续执行;否则返回一个限流提示信息,拒绝该请求。
将拦截器注册到 Spring Boot 的拦截器链中,可以在配置类中添加如下配置:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private RateLimitInterceptor rateLimitInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(rateLimitInterceptor);
}
}
通过上述方式,可以在 Spring Boot 中实现固定窗口限流,对请求进行限流控制,以保护系统的稳定性和安全性。需要注意的是,固定窗口限流的实现比较简单,但是无法应对瞬时的流量波动,可能会导致系统响应时间变慢或者系统崩溃的问题。因此,在实际使用中,需要结合其他限流算法和策略,以实现更加有效的限流控制。
流量峰值被限制
可能会出现流量峰值被限制的情况
假设一个固定窗口限流算法中,设置了每 10 秒钟最多通过 100 个请求,如果在前 9 秒钟内已经通过了 90 个请求,那么在接下来的 1 秒钟内,只能通过 10 个请求,如果此时有一个请求流量突然增加到 20 个请求,那么前 10 个请求可以顺利通过,但是后面的 10 个请求将被拒绝,因为计数器已经达到了最大限制。这就是流量峰值被限制的情况,如果这种情况发生的频率较高,那么整个系统的吞吐量将受到很大的影响,导致请求响应时间变慢,用户体验变差。
滑动窗口限流
在 Spring Boot 中实现滑动窗口限流可以采用拦截器(Interceptor)的方式对请求进行拦截和限流控制,主要步骤如下:
-
定义一个拦截器类,在
preHandle
方法中实现滑动窗口限流的逻辑。 -
在拦截器中维护一个计数器和一个滑动窗口的大小,例如每秒钟只允许通过 100 个请求,滑动窗口大小为 10 秒。
-
在
preHandle
方法中,记录当前时间窗口内通过的请求数量,并判断该请求是否超过了限制。 -
如果请求通过限流检查,则允许请求继续执行;否则返回一个限流提示信息,拒绝该请求。
-
将拦截器注册到 Spring Boot 的拦截器链中,以实现对请求的限流控制。
下面是一个基于 Spring Boot 的滑动窗口限流实现示例:
@Component
public class SlidingWindowRateLimiter implements HandlerInterceptor {
private static final int MAX_REQUESTS = 100; // 每秒钟最多通过 100 个请求
private static final int WINDOW_SIZE = 10; // 时间窗口大小为 10 秒
private static final int BUCKET_SIZE = 1000; // 滑动窗口的容量为 1000 个请求
private int[] window = new int[BUCKET_SIZE]; // 存储滑动窗口内通过的请求数量
private int cursor = 0; // 当前时间窗口的指针
private int count = 0; // 当前滑动窗口内通过的请求数量
public SlidingWindowRateLimiter() {
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
executor.scheduleAtFixedRate(this::resetWindow, WINDOW_SIZE, WINDOW_SIZE, TimeUnit.SECONDS);
}
private void resetWindow() {
synchronized (this) {
window[cursor] -= count;
count = 0;
cursor = (cursor + 1) % BUCKET_SIZE;
}
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取当前滑动窗口内的请求数量
int count;
synchronized (this) {
for (int i = 0; i < BUCKET_SIZE; i++) {
count += window[i];
}
}
// 判断当前请求数量是否超过限制
if (count >= MAX_REQUESTS) {
// 超过限制,拒绝该请求
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
response.getWriter().write("Too many requests. Please try again later.");
return false;
}
// 请求数量未超过限制,允许通过该请求
synchronized (this) {
window[(cursor + count) % BUCKET_SIZE]++;
count++;
}
return true;
}
}
在上述代码中,通过一个长度为 10 的数组 window
来记录滑动窗口内通过的请求数量,通过一个 head
变量和一个 tail
变量来指向滑动窗口的头和尾。在 preHandle
方法中,删除过期的请求计数,并判断
该请求是否超过了限制。如果请求通过限流检查,则允许请求继续执行;否则返回一个限流提示信息,拒绝该请求。
将拦截器注册到 Spring Boot 的拦截器链中,可以在配置类中添加如下配置:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private SlidingWindowRateLimiter slidingWindowRateLimiter;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(slidingWindowRateLimiter);
}
}
通过上述方式,可以在 Spring Boot 中实现滑动窗口限流,对请求进行限流控制,以保护系统的稳定性和安全性。滑动窗口限流相比于固定窗口限流可以更好地应对瞬时的流量波动,但是实现的复杂度和性能开销也相应增加,需要根据实际情况进行选择。
令牌桶限流
在 Spring Boot 中实现令牌桶限流可以采用拦截器(Interceptor)的方式对请求进行拦截和限流控制,主要步骤如下:
-
定义一个拦截器类,在
preHandle
方法中实现令牌桶限流的逻辑。 -
在拦截器中维护一个令牌桶,每秒钟往桶中放入一定数量的令牌,例如每秒钟往桶中放入 100 个令牌。
-
在
preHandle
方法中,判断令牌桶中是否还有令牌,如果有则允许请求继续执行,并从令牌桶中取出一个令牌;否则返回一个限流提示信息,拒绝该请求。 -
将拦截器注册到 Spring Boot 的拦截器链中,以实现对请求的限流控制。
下面是一个基于 Spring Boot 的令牌桶限流实现示例:
@Component
public class TokenBucketRateLimiter implements HandlerInterceptor {
private static final int MAX_REQUESTS = 100; // 每秒钟最多通过 100 个请求
private static final int TOKENS_PER_SECOND = 100; // 每秒钟往桶中放入 100 个令牌
private int tokens = 0; // 当前桶中的令牌数量
private long lastRefillTime = System.currentTimeMillis(); // 上次放入令牌的时间
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 计算当前应该放入多少个令牌
long now = System.currentTimeMillis();
long elapsedTime = now - lastRefillTime;
int count = (int) (elapsedTime * TOKENS_PER_SECOND / 1000);
// 放入令牌
tokens = Math.min(tokens + count, MAX_REQUESTS);
lastRefillTime = now;
// 判断当前令牌桶中是否还有令牌
if (tokens > 0) {
// 令牌桶中还有令牌,允许请求通过并取出一个令牌
tokens--;
return true;
} else {
// 令牌桶中没有令牌,拒绝该请求
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
response.getWriter().write("Too many requests. Please try again later.");
return false;
}
}
}
在上述代码中,通过一个变量 tokens
来记录当前桶中的令牌数量,通过一个变量 lastRefillTime
来记录上次放入令牌的时间。在 preHandle
方法中,计算当前应该放入多少个令牌,放入令牌后判断当前令牌桶中是否还有令牌,如果有则允许请求继续执行,并从令牌桶中取出一个令牌;否则返回一个限流提示信息,拒绝该请求。
将拦截器注册到 Spring Boot 的拦截器链中,可以在配置类中添加如下配置:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private TokenBucketRateLimiter tokenBucketRateLimiter;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(tokenBucketRateLimiter);
}
}
通过上述方式,可以在 Spring Boot 中实现令牌桶限流,对请求进行限流控制,以保护系统的稳定性和安全性。令牌桶限流相比于滑动窗口限流和固定窗口限流可以更好地应对流量突发的情况,但是实现的复杂度和性能开销也相应增加,需要根据实际情况进行选择。
可以控制请求的平均速率,同时也可以应对突发流量?
令牌桶限流算法可以控制请求的平均速率,同时也可以应对突发流量。该算法的基本思想是维护一个令牌桶,令牌桶中存放一定数量的令牌,每个请求必须获取一个令牌才能执行。令牌桶会以固定的速率生成令牌,并且令牌桶中的令牌数量有上限,可以通过设置令牌生成速率和令牌桶容量来控制系统的处理速率和突发流量。
具体来说,令牌桶限流算法的流程如下:
- 初始化一个令牌桶,包含一定数量的令牌。
- 以固定的速率往令牌桶中添加令牌。
- 当请求到达时,从令牌桶中获取一个令牌。
- 如果令牌桶中没有可用的令牌,则拒绝该请求。
这种算法可以平滑控制请求的处理速率,同时也可以应对突发流量。如果系统在某一时刻需要处理大量的请求,那么令牌桶中存储的一定数量的令牌可以缓冲短时间内的流量突发,保证系统的稳定性和可靠性。如果请求的速率比较稳定,则可以通过调整令牌生成速率和令牌桶容量来达到预期的限流效果。
漏桶限流
在 Spring Boot 中实现漏桶限流可以采用拦截器(Interceptor)的方式对请求进行拦截和限流控制,主要步骤如下:
-
定义一个拦截器类,在
preHandle
方法中实现漏桶限流的逻辑。 -
在拦截器中维护一个漏桶,通过固定的速率将请求放入漏桶中,例如每秒钟只允许通过 100 个请求。
-
在
preHandle
方法中,判断漏桶中是否还有足够的空间容纳该请求,如果有则允许请求继续执行,并在漏桶中留下一个空位;否则返回一个限流提示信息,拒绝该请求。 -
将拦截器注册到 Spring Boot 的拦截器链中,以实现对请求的限流控制。
下面是一个基于 Spring Boot 的漏桶限流实现示例:
@Component
public class LeakyBucketRateLimiter implements HandlerInterceptor {
private static final int MAX_REQUESTS = 100; // 每秒钟最多通过 100 个请求
private static final int BUCKET_SIZE = 100; // 漏桶的容量为 100 个请求
private static final int REFILL_INTERVAL = 1000; // 每秒钟固定放入 100 个请求
private int count = 0; // 当前漏桶中的请求数量
private long lastRefillTime = System.currentTimeMillis(); // 上次放入请求的时间
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 计算当前应该放入多少个请求
long now = System.currentTimeMillis();
long elapsedTime = now - lastRefillTime;
int count = (int) (elapsedTime * REFILL_INTERVAL / 1000);
// 放入请求
this.count = Math.max(this.count - count, 0);
lastRefillTime = now;
// 判断漏桶中是否还有足够的空间容纳该请求
if (this.count < BUCKET_SIZE) {
// 漏桶中还有空位,允许请求通过并留下一个空位
this.count++;
return true;
} else {
// 漏桶已满,拒绝该请求
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
response.getWriter().write("Too many requests. Please try again later.");
return false;
}
}
}
在上述代码中,通过一个变量 count
来记录当前漏桶中的请求数量,通过一个变量 lastRefillTime
来记录上次放入请求的时间。在 preHandle
方法中,计算当前应该放入多少个请求,放入请求后判断漏桶中是否还有足够的空间容纳该请求,如果有则允许请求继续执行,并在漏桶中留下一个空位;否则返回一个限流提示信息,拒绝该请求。
将拦截器注册到 Spring Boot 的拦截器链中,可以在配置类中添加如下配置:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LeakyBucketRateLimiter leakyBucketRateLimiter;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(leakyBucketRateLimiter);
}
}
通过上述方式,可以在 Spring Boot 中实现漏桶限流,对请求进行限流控制,以保护系统的稳定性和安全性。漏桶限流相比于其他几种限流算法,实现的复杂度和性能开销比较低,但是可能无法很好地应对瞬时的流量突发,需要根据实际情况进行选择。
无法应对短时间内的流量突发?
漏桶限流算法的本质是通过固定的速率处理请求,将请求以恒定的速率放入漏桶中,然后按照固定的速率处理漏桶中的请求,这样可以控制系统的处理速率,避免了短时间内的流量突发对系统的影响。但是,由于漏桶的容量是固定的,如果在短时间内有大量的请求涌入,漏桶就会被填满,此时新的请求就无法被处理,这就导致了无法应对短时间内的流量突发。
比如,假设一个漏桶限流算法中,设置了漏桶容量为 100,处理速率为 10 个请求/秒,如果在前 9 秒钟内已经处理了 80 个请求,此时有 50 个请求同时涌入漏桶中,这时漏桶就会被填满,剩余的 40 个请求就无法被处理。虽然漏桶算法可以有效平滑流量,但是它对于短时间内的流量突发仍然有一定的局限性。
总结
-
固定窗口限流的优点是实现简单,计数器容易维护,适用于稳定的请求流量。缺点是无法应对突发流量,时间窗口一到计数器会被清空,可能会出现流量峰值被限制的情况。
-
滑动窗口限流的优点是可以平滑处理突发流量,同时能够准确计算请求的通过率。缺点是实现复杂,需要维护滑动窗口的数据结构,同时也有计数器溢出的风险。
-
令牌桶限流的优点是可以控制请求的平均速率,同时也可以应对突发流量。缺点是实现相对复杂,需要维护令牌桶数据结构,而且令牌的生成速率对限流的效果有重要影响。
-
漏桶限流的优点是可以平滑处理请求的速率,避免了突发流量对系统的影响。缺点是实现相对复杂,需要维护漏桶数据结构,而且无法应对短时间内的流量突发。
综合而言,不同的限流技术适用于不同的场景,开发人员需要根据实际情况进行选择。需要考虑的因素包括请求的特点、系统的处理能力、限流的粒度等等。