在互联网飞速发展的今天,随着系统用户规模的不断扩大和分布式架构的广泛应用,API 接口的稳定性和性能成为系统设计中至关重要的因素。无论是应对突发的流量高峰,还是防止恶意爬虫的恶意请求,限流策略都已成为现代系统不可或缺的一部分。
为什么需要接口限流?
- 防止系统过载: 在短时间内大量的请求可能导致系统资源耗尽,进而导致服务降级甚至宕机。通过限流,我们可以有效控制流量的上限,确保系统在高负载下仍然能够提供稳定的服务。
- 保护关键资源: 一些关键的 API 接口可能涉及到数据库、缓存等有限资源的操作,如果不加限制,可能会导致资源耗尽,影响系统整体性能。限流可以确保这些关键资源的访问量在可控范围内。
- 应对恶意攻击: 分布式拒绝服务攻击(DDoS)是常见的网络攻击手段,攻击者通过发送大量请求瘫痪系统。限流策略可以作为第一道防线,快速识别并过滤掉异常流量,减少攻击对系统的影响。
- 提升用户体验: 在用户访问量大的情况下,如果不加以控制,可能会出现系统响应速度下降的情况,影响用户体验。合理的限流策略能够为用户提供更加稳定和一致的服务质量。
- 公平资源分配: 在多用户、多租户的场景下,限流能够确保系统资源的公平分配,避免某个用户或租户独占资源,影响其他用户的正常使用。
为了解决上述问题,我们可以在 API 接口上实施限流策略,使得系统能够在高并发环境下保持稳定,并且能够合理应对各类突发情况。在本文中,我们将探讨如何在 SpringBoot 3.3 中,通过简单的配置和代码实现 API 接口的限流。
运行效果:
图片
图片
若想获取项目完整代码以及其他文章的项目源码,且在代码编写时遇到问题需要咨询交流,欢迎加入下方的知识星球。
项目结构
我们将从项目的结构开始,先了解一下本示例项目的文件布局。
rate-limiter/
├── src/
│ ├── main/
│ │ ├── java/com/icoderoad/ratelimiter/
│ │ │ ├── controller/
│ │ │ │ └── RateLimiterController.java
│ │ │ ├── config/
│ │ │ │ └── RateLimiterConfig.java
│ │ │ ├── properties/
│ │ │ │ └── RateLimiterProperties.java
│ │ │ └── application/
│ │ │ └── SpringBootRateLimiterApplication.java
│ │ ├── resources/
│ │ │ ├── templates/
│ │ │ │ └── index.html
│ │ │ └── application.yml
└── pom.xml
接下来,我们将逐步搭建项目,实现 API 接口限流功能。
引入依赖
在 pom.xml 中引入以下依赖:
<?xml versinotallow="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.icoderoad</groupId>
<artifactId>ratelimiter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>ratelimiter</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
<guava.version>31.1-jre</guava.version>
</properties>
<dependencies>
<!-- Spring Boot Web Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Guava 用于限流 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<!-- Thymeleaf 模板引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置限流参数
在 src/main/resources/application.yml 中配置限流参数:
server:
port: 8080
rate-limiter:
permits-per-second: 5 # 每秒许可数
warmup-period: 0 # 预热时间(秒)
timeout: 0 # 获取许可的超时时间(秒)
参数说明:
- permits-per-second: 每秒允许的请求数量。
- warmup-period: 限流器预热时间,用于平滑地增加到最大速率。
- timeout: 获取许可的超时时间,0 表示立即返回获取结果。
创建限流配置属性类
在 src/main/java/com/icoderoad/ratelimiter/properties/RateLimiterProperties.java 中创建配置属性类,用于映射 application.yml 中的配置:
package com.icoderoad.ratelimiter.propertie;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import lombok.Data;
@Data
@Component
@ConfigurationProperties(prefix = "rate-limiter")
public class RateLimiterProperties {
/**
* 每秒许可数
*/
private double permitsPerSecond;
/**
* 预热时间(秒)
*/
private long warmupPeriod;
/**
* 获取许可的超时时间(秒)
*/
private long timeout;
}
说明:
- 使用 @ConfigurationProperties 注解将配置属性映射到类中,便于在代码中使用。
- 提供对应的 Getter 和 Setter 方法,方便 Spring Boot 自动注入配置。
配置 RateLimiter
在 src/main/java/com/icoderoad/ratelimiter/config/RateLimiterConfig.java 中创建限流器配置:
package com.icoderoad.ratelimiter.config;
import java.util.concurrent.TimeUnit;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.google.common.util.concurrent.RateLimiter;
import com.icoderoad.ratelimiter.propertie.RateLimiterProperties;
@Configuration
public class RateLimiterConfig {
/**
* 配置 RateLimiter Bean
*
* @param properties 注入的限流配置属性
* @return RateLimiter 实例
*/
@Bean
public RateLimiter rateLimiter(RateLimiterProperties properties) {
if (properties.getWarmupPeriod() > 0) {
// 创建带有预热期的 RateLimiter
return RateLimiter.create(
properties.getPermitsPerSecond(),
properties.getWarmupPeriod(),
TimeUnit.SECONDS
);
} else {
// 创建标准的 RateLimiter
return RateLimiter.create(properties.getPermitsPerSecond());
}
}
}
说明:
- 根据配置文件中的参数动态创建 RateLimiter 实例。
- 支持带有预热期的限流器配置,满足不同场景下的需求。
创建控制器
在 src/main/java/com/icoderoad/ratelimiter/controller/RateLimiterController.java 中创建控制器,处理 API 请求:
package com.icoderoad.ratelimiter.controller;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.google.common.util.concurrent.RateLimiter;
import com.icoderoad.ratelimiter.propertie.RateLimiterProperties;
@Controller
public class RateLimiterController {
@Autowired
private RateLimiter rateLimiter;
@Autowired
private RateLimiterProperties properties;
/**
* 测试限流接口
*
* @return 请求结果
*/
@GetMapping("/api/test")
@ResponseBody
public ResponseEntity<String> testApi() {
boolean acquired = rateLimiter.tryAcquire(properties.getTimeout(), TimeUnit.SECONDS);
if (acquired) {
// 允许请求,返回成功响应
return ResponseEntity.ok("请求成功!");
} else {
// 拒绝请求,返回限流响应
return ResponseEntity.status(429).body("请求过多,请稍后再试!");
}
}
}
说明:
- 使用 rateLimiter.tryAcquire(timeout, TimeUnit.SECONDS) 方法尝试获取许可,支持超时等待。
- 根据获取许可的结果返回对应的响应:
成功获取:返回 200 状态码和成功消息。
获取失败:返回 429 状态码和错误提示。
创建前端页面
在 src/main/resources/templates/index.html 中创建前端页面,使用 Thymeleaf、Bootstrap 和 jQuery 实现:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>API 限流测试</title>
<!-- 引入 Bootstrap CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
<!-- 自定义样式 -->
<style>
body {
padding-top: 50px;
}
</style>
</head>
<body>
<div class="container">
<h1 class="mb-4">API 限流测试</h1>
<button id="testButton" class="btn btn-primary">发送请求</button>
<div id="alertPlaceholder" class="mt-3"></div>
</div>
<!-- 引入 jQuery -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- 引入 Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
$(document).ready(function () {
$('#testButton').click(function () {
$.ajax({
url: '/api/test',
method: 'GET',
success: function (response) {
showAlert(response, 'success');
},
error: function (xhr) {
if (xhr.status === 429) {
showAlert(xhr.responseText, 'danger');
} else {
showAlert('发生未知错误,请稍后重试。', 'warning');
}
}
});
});
/**
* 显示提示信息
* @param message 消息内容
* @param type 提示类型('success', 'danger', 'warning' 等)
*/
function showAlert(message, type) {
const alertHtml = `
<div class="alert alert-${type} alert-dismissible fade show" role="alert">
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
`;
$('#alertPlaceholder').html(alertHtml);
}
});
</script>
</body>
</html>
说明:
- 引入资源:
使用 CDN 加载 Bootstrap 和 jQuery,确保资源的快速和稳定加载。
- 页面结构:
一个按钮用于触发 API 请求。
一个占位符 div 用于显示提示信息。
JavaScript 逻辑:
使用 jQuery 监听按钮点击事件,发送 AJAX 请求到 /api/test 接口。
根据响应结果,调用 showAlert 函数,在页面上显示不同类型的提示信息。
showAlert 函数使用 Bootstrap 的 Alert 组件,提供友好的用户提示。
效果展示:
- 请求成功: 显示绿色的成功提示。
- 请求被限流: 显示红色的错误提示,提示用户请求过多。
- 未知错误: 显示黄色的警告提示,提示发生未知错误。
启动应用
在 src/main/java/com/icoderoad/ratelimiter/application/SpringBootRateLimiterApplication.java 中启动 Spring Boot 应用:
package com.icoderoad.ratelimiter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(scanBasePackages = "com.icoderoad.ratelimiter")
public class RatelimiterApplication {
public static void main(String[] args) {
SpringApplication.run(RatelimiterApplication.class, args);
}
}
说明:
- 使用 @SpringBootApplication 注解标注主启动类,并指定扫描的基础包路径。
- 运行 main 方法即可启动应用。
8. 测试与验证
步骤:
- 启动应用: 运行主启动类,启动 Spring Boot 应用。
- 访问页面: 在浏览器中访问 http://localhost:8080/,看到 API 限流测试页面。
- 发送请求:点击“发送请求”按钮,观察页面提示信息。
- 正常情况: 如果请求未超过限流阈值,显示绿色的“请求成功!”提示。
- 限流情况: 如果在短时间内连续多次点击按钮,超过配置的每秒许可数,将显示红色的“请求过多,请稍后再试!”提示。
- 调整配置: 可以修改 application.yml 中的限流参数,重新启动应用,测试不同的限流策略效果。
示例演示:
- 设置 permits-per-second 为 2,表示每秒允许 2 个请求。
- 连续快速点击按钮,多数请求将被限流,提示用户稍后重试。
9. 总结
通过本文的示例,我们成功地在 Spring Boot 3.3 中实现了简单而有效的 API 接口限流功能。我们利用了 Guava 提供的 RateLimiter 工具,结合 Spring Boot 的配置属性管理和依赖注入机制,实现了灵活可配的限流策略。同时,通过前端页面的简单设计和友好提示,使得用户能够清晰地感知到限流机制的存在和作用。