目录
应用场景
1. 防止流量过大导致系统崩溃:当流量突然增大时,限流可以确保系统不会因为过大的流量导致资源耗尽和系统崩溃。
2. 防止恶意请求:限流可以用来针对一些恶意请求进行限制,避免这些请求对系统造成损害。
3. 避免线程池耗尽:当请求很多时,后端的线程池可能会耗尽,导致许多请求得不到响应。加上限流可以避免这种情况的发生。
4. 防止缓存击穿:当缓存失效时,后端系统会接收大量的请求。限流可以确保后端系统不会因为缓存失效而被打垮。
5. 控制流量并发度:有些系统会根据自身的处理能力来控制并发度,限流可以起到控制流量和并发度的作用。
常见策略
1. 客户端限流:限制客户端向服务器发起请求的频率。可以通过在客户端实现限流算法来实现。
2. 服务端限流:限制服务器处理请求的频率。可以在服务器上实现限流来达到这个目的。
3. 应用层限流:在应用层根据业务逻辑对某些请求进行限流。比如限制对重要资源的访问频率。
4. 网络层限流:在网络设备上进行流量控制,限制到达服务器的请求频率。
常用技术
1. 计数器: 使用一个计数器来统计请求数,如果达到阈值则拒绝请求。这是一种简单的限流方式,可以使用 Java 的 AtomicInteger 来实现计数器。
2. 滑动窗口: 维持一个固定大小的窗口,统计窗口内的请求数,如果达到阈值则拒绝请求。这种方式比简单的计数器更精确,可以使用 Java 的 LinkedBlockingQueue 来实现滑动窗口。
3. 漏桶算法: 维持一个桶的大小,如果桶满了则拒绝请求,否则放行请求。漏桶的大小控制了限流速率,可以使用 Java 的 Semaphore 来实现漏桶算法。
4. 令牌桶算法: 定期生成令牌放入桶中,如果桶中有令牌则允许请求并消耗一个令牌,否则拒绝请求。令牌生成的速率控制了限流速率,可以使用 Java 的 ScheduledExecutorService 和 Semaphore 来实现令牌桶算法。
5. Guava 的 RateLimiter: Google Guava 中的 RateLimiter 实现了令牌桶算法,可以直接使用。它提供简单易用的限流功能,对并发请求有很好的支持。
代码实现
计数器
public boolean limit() {
count++;
if (count > threshold) {
return false; // 达到限流阈值,拒绝请求
}
return true;
}
滑动窗口
Deque<Long> window = new ArrayDeque<>();
long now = System.currentTimeMillis();
window.add(now);
if (window.size() > windowSize) {
long oldest = window.pollFirst();
if (now - oldest < interval) {
return false; // 窗口内请求数达到限流阈值,拒绝请求
}
}
return true;
漏桶算法
Semaphore semaphore = new Semaphore(bucketSize);
if (semaphore.tryAcquire()) {
return true; // 获取令牌成功,放行请求
}
return false; // 桶中无令牌,拒绝请求
令牌桶算法
ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(1);
Semaphore semaphore = new Semaphore(0);
scheduled.scheduleAtFixedRate(() -> {
int added = Math.min(bucketSize - semaphore.availablePermits(), addRate);
semaphore.release(added); // 按速率添加令牌
}, 0, 1, TimeUnit.SECONDS);
if (semaphore.tryAcquire()) {
return true; // 获取令牌成功,放行请求
}
return false; // 桶中无令牌,拒绝请求
Guava RateLimiter
RateLimiter limiter = RateLimiter.create(2); // 每秒 2 个请求
if (limiter.tryAcquire()) {
return true; // 获取令牌成功,放行请求
}
return false; // 限流,拒绝请求
优缺点
这些限流技术各有优缺点:
1. 计数器:
- 优点:简单易实现
- 缺点:不够精确,无法控制速率,窗口大小不可配置
2. 滑动窗口:
- 优点:比计数器更精确,窗口大小可配置
- 缺点:实现复杂度高于计数器
3. 漏桶算法:
- 优点:可以限制速率,实现简单
- 缺点:不够精确,无法应对突发流量
4. 令牌桶算法:
- 优点:可以限制速率,且能应对突发流量
- 缺点:实现较复杂
5. Guava RateLimiter:
- 优点:实现简单,可以限制速率,能应对突发流量
- 缺点:速率控制不够灵活,不能实现更复杂的限流策略
综上,我的推荐是:如果需要一个简单的限流器,使用 Guava RateLimiter。它实现简单,功能足够。如果需要更精确的速率控制或实现更复杂的限流策略,自己实现基于令牌桶算法的限流器。如果对限流精度要求不高,可以使用简单的计数器或滑动窗口。基于应用场景选择合适的限流技术非常重要。没有一种技术适用于所有场景,所以理解每个技术的优缺点很关键。此外,限流还可以从多个 dimensionality 进行
比如:
- 对某用户进行限流
- 对某资源进行限流
- 基于客户端 IP 进行限流
- 等等所以在设计一个限流系统时,需要全面考虑到这些因素。
应用场景
1. 计数器:
- 场景:需要一个简单的限流器,限流精度要求不高。
- 例如:限制某资源的访问次数,简单防止窃取资源。
- 框架:简单的应用层限流实现
2. 滑动窗口:
- 场景:需要限制一定窗口内的请求次数,且窗口大小可配置。
- 例如:限制一定时间窗口内的登录失败次数,以防暴力破解登录。
-框架:Nginx 等代理服务器
3. 漏桶算法:
- 场景:需要固定速率地限制请求,可应对一定程度的突发流量。
- 例如:限制接口调用的速率,防止流量骤增导致系统超负荷。
- 框架:Hystrix 等容错库
4. 令牌桶算法:
- 场景:需要更精确地控制请求通过的速率,并能应对更高的突发流量。
- 例如:限制对重要资源的访问速率,保证资源不会因为突发大流量而超负荷。
- 框架:Sentinel、Resilience4j 等流控框架
5. Guava RateLimiter:
- 场景:需要一个简单的速率限制器,不需要很精确的速率控制和复杂的限流策略。
- 例如:简单限制 API 调用频率,防止占用过多服务器资源。
-框架:Google Guava
除此之外,限流技术在其他产品和框架中也有应用:
- AWS WAF:基于速率限制和 web 防火墙限制恶意流量
- Nginx 限流模块:基于速率限制和漏桶算法进行限流
- Envoy 路由器:基于速率限制和令牌桶算法进行限流
- API 网关:对 API 请求进行限流,一般基于速率限制实现
- 数据库代理:基于速率限制和漏桶算法限制数据库连接数和查询频率
- 缓存代理:基于速率限制和令牌桶算法限制缓存击穿场景下的请求流量
- 等等
可以看到,这些限流技术可以应用于很多场景,但每个技术都有其最适用的场景。在选择限流技术时,需要考虑:
- 需要控制的对象(用户、资源等)
- 限流精度要求(速率控制是否精确)
- 是否需要应对突发流量
- 限流策略的复杂度
- 等等
综合考虑这些因素再选择最适合的限流技术。
工具类封装
代码包里这个工具类实现了计数器限流、滑动窗口限流、漏桶限流和令牌桶限流四种方式。可以根据需要选择相应的限流方法进行流量控制。这样一个集成多种限流技术的工具类可以很好地应用于不同的限流场景,而不需要每次都自己实现限流算法。
注意:这里的令牌桶和漏桶的限流是线程数层面的限流,无法用在业务层面做数据限速。