限流算法概述
在高并发系统中,限流是保护服务稳定性的重要手段。常用的限流算法主要有两种:
漏桶算法
漏桶算法以固定速率处理请求,像水从漏桶中匀速流出一样,能够平滑网络流量,但无法应对突发流量。
令牌桶算法
令牌桶算法以固定速率向桶中添加令牌,请求处理前需要获取令牌,只有拿到令牌的请求才会被处理。这种算法既能够限制平均速率,又允许一定程度的突发流量,更加灵活实用。
Guava RateLimiter 深度解析
核心原理
Guava RateLimiter 基于令牌桶算法实现,其工作原理如下图所示:
https://example.com/ratelimiter-diagram.png
系统以固定频率向令牌桶中添加令牌(例如每秒10个),业务请求在处理前需要从桶中获取令牌。获取令牌的方式支持两种模式:
- 阻塞等待:直到成功获取令牌
- 立即返回:获取失败时直接返回,不等待
实战应用
下面通过完整的Spring MVC示例演示RateLimiter的实际应用。
1. 添加Maven依赖
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>
2. 限流服务封装
将限流逻辑封装到独立的服务类中,提高代码复用性:
@Service
public class AccessLimitService {
// 创建每秒产生5个令牌的限流器
private final RateLimiter rateLimiter = RateLimiter.create(5.0);
/**
* 尝试获取令牌(非阻塞方式)
* @return true-获取成功,false-获取失败
*/
public boolean tryAcquire() {
return rateLimiter.tryAcquire();
}
/**
* 阻塞方式获取令牌
* @return 等待时间(秒)
*/
public double acquire() {
return rateLimiter.acquire();
}
}
3. Controller层实现
@Controller
public class HelloController {
private static final SimpleDateFormat sdf =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Autowired
private AccessLimitService accessLimitService;
@RequestMapping("/access")
@ResponseBody
public String access() {
// 尝试获取令牌
if (accessLimitService.tryAcquire()) {
// 模拟业务处理耗时500毫秒
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return "thread interrupted";
}
return "access success [" + sdf.format(new Date()) + "]";
} else {
return "access limit [" + sdf.format(new Date()) + "]";
}
}
}
4. 并发测试验证
创建测试客户端验证限流效果:
public class AccessClient {
private final ExecutorService fixedThreadPool =
Executors.newFixedThreadPool(10);
/**
* 发送HTTP GET请求
*/
public static String sendGet(URL url) {
StringBuilder result = new StringBuilder();
try (BufferedReader in = new BufferedReader(
new InputStreamReader(url.openConnection().getInputStream()))) {
String line;
while ((line = in.readLine()) != null) {
result.append(line);
}
} catch (Exception e) {
System.out.println("发送GET请求出现异常: " + e.getMessage());
}
return result.toString();
}
public void access() throws Exception {
final URL url = new URL("http://localhost:8080/guavalimitdemo/access");
// 提交10个并发请求
for (int i = 0; i < 10; i++) {
fixedThreadPool.submit(() -> {
System.out.println(sendGet(url));
});
}
fixedThreadPool.shutdown();
fixedThreadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
}
public static void main(String[] args) throws Exception {
AccessClient client = new AccessClient();
client.access();
}
}
测试结果分析
运行测试程序后,部分请求成功获取令牌并执行,其余请求因无法获取令牌而被限流。值得注意的是,虽然配置的是每秒5个令牌,但实际可能会允许稍多的请求通过,这是因为RateLimiter在设计上允许一定的突发流量。
RateLimiter API 详解
核心方法说明
| 方法 | 描述 |
|---|---|
double acquire() | 获取1个许可,阻塞直到获取成功 |
double acquire(int permits) | 获取指定数量的许可,阻塞直到获取成功 |
static RateLimiter create(double permitsPerSecond) | 创建固定速率的限流器 |
static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit) | 创建含预热期的限流器 |
double getRate() | 获取配置的稳定速率(许可数/秒) |
void setRate(double permitsPerSecond) | 动态更新速率 |
boolean tryAcquire() | 尝试获取1个许可,立即返回结果 |
boolean tryAcquire(int permits) | 尝试获取指定数量许可,立即返回结果 |
boolean tryAcquire(long timeout, TimeUnit unit) | 在指定超时时间内尝试获取许可 |
boolean tryAcquire(int permits, long timeout, TimeUnit unit) | 在指定超时时间内尝试获取指定数量许可 |
使用示例
假设我们需要控制任务提交速率,确保每秒不超过2个任务:
public class TaskProcessor {
// 创建每秒2个许可的限流器
private final RateLimiter rateLimiter = RateLimiter.create(2.0);
public void submitTasks(List<Runnable> tasks, Executor executor) {
for (Runnable task : tasks) {
rateLimiter.acquire(); // 可能需要阻塞等待
executor.execute(task);
}
}
public boolean trySubmitTask(Runnable task, Executor executor) {
if (rateLimiter.tryAcquire()) {
executor.execute(task);
return true;
}
return false;
}
}
进阶特性
预热模式
RateLimiter支持预热模式,在系统启动阶段平滑地增加处理速率:
// 创建预热型限流器:每秒10个许可,预热时间5秒
RateLimiter rateLimiter = RateLimiter.create(10.0, 5, TimeUnit.SECONDS);
在5秒的预热期内,RateLimiter会从较低速率逐渐增加到目标速率,避免冷启动时的流量冲击。
动态速率调整
RateLimiter支持运行时动态调整速率:
rateLimiter.setRate(20.0); // 将速率调整为每秒20个许可
注意事项
- 分布式环境限制:Guava RateLimiter适用于单机限流场景,分布式系统需要结合Redis等中间件实现集群限流。
- 精度考虑:虽然配置的是每秒N个令牌,但实际可能会有N+1的突发通过量,这在设计系统容量时需要予以考虑。
- 资源清理:使用完毕后应及时关闭相关的线程池和资源。
- 异常处理:在生产环境中需要完善异常处理机制,确保限流器异常时不影响核心业务流程。
总结
Guava RateLimiter提供了强大而灵活的限流能力,通过令牌桶算法既保证了系统的平均处理速率,又允许合理的突发流量。本文通过原理分析、实战示例和API详解,展示了如何在实际项目中有效使用RateLimiter进行流量控制。对于分布式系统,可以基于类似的算法思想,结合分布式缓存实现集群级别的限流方案。
430

被折叠的 条评论
为什么被折叠?



