在当今的微服务架构中,接口限流是一个常见的需求,用以防止系统过载和潜在的资源耗尽。Spring Boot 提供了一种方便的方式来实施接口限流,结合 AOP(面向切面编程)和 Redis 存储限流信息,可以有效地实现这一目标。
首先,你需要在你的 pom.xml
文件中添加 Spring Boot Starter AOP 和 Spring Boot Starter Data Redis 的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
在 application.properties
或 application.yml
文件中配置 Redis 的连接信息:
# application.properties
spring.redis.host=localhost
spring.redis.port=6379
创建一个类来封装与 Redis 交互的逻辑:
@Service
public class RateLimiterService {
@Autowired
private StringRedisTemplate redisTemplate;
private static final String PREFIX = "rate_limiter:";
private static final int DEFAULT_LIMIT = 10; // 默认限流次数
private static final int DEFAULT_EXPIRE_TIME = 60; // 默认过期时间(秒)
public boolean isAllow(@NonNull String key) {
// 获取当前时间戳(毫秒)
long now = System.currentTimeMillis();
// 从 Redis 中获取限流信息(如果存在)
String value = redisTemplate.opsForValue().get(PREFIX + key);
if (value == null) { // 如果不存在,则设置默认限流信息并返回 true(允许访问)
redisTemplate.opsForValue().set(PREFIX + key, String.valueOf(DEFAULT_LIMIT), DEFAULT_EXPIRE_TIME, TimeUnit.SECONDS);
return true;
} else { // 如果存在,则解析限流信息并返回是否允许访问(根据实际需求修改)
int limit = Integer.parseInt(value); // 假设我们使用简单的计数器实现限流,这里只是简单地将计数器减一并判断是否小于零。实际应用中可能需要更复杂的策略。
if (limit > 0) { // 如果还有剩余访问次数,则更新 Redis 中的限流信息并返回 true(允许访问)
redisTemplate.opsForValue().set(PREFIX + key, String.valueOf(limit - 1), DEFAULT_EXPIRE_TIME, TimeUnit.SECONDS);
return true;
} else { // 如果已经达到限流次数,则返回 false(拒绝访问)
return false;
}
}
}
}
创建一个 AOP 切面来拦截特定接口的访问:
@Aspect
@Component
public class RateLimitAspect {
@Autowired
private RateLimiterService rateLimiterService; // 注入 Redis 存储类,用于处理限流逻辑。
@Pointcut("execution(* com.example.demo.controller.*.*(..))") // 定义切入点表达式,拦截所有 com.example.demo.controller 包下的方法。你可以根据需要修改这个表达式来拦截特定的接口。
public void controllerMethods() {} // 定义一个空的方法,作为切入点表达式的方法签名。这个方法不需要实现任何逻辑。
@Around("controllerMethods()") // 使用 @Around 注解来定义环绕通知。这个通知会在目标方法执行前后执行。你可以在这里添加自定义的逻辑来处理限流。例如:记录日志、抛出异常等。这里我们只是简单地调用 rateLimiterService 的 isAllow 方法。
public Object rateLimit(ProceedingJoinPoint joinPoint) throws Throwable {
String key = // 生成唯一的 key,用于标识用户或请求。例如,可以使用用户的 ID 或 IP 地址。
if (rateLimiterService.isAllow(key)) {
return joinPoint.proceed(); // 如果允许访问,则继续执行目标方法。
} else {
// 如果达到限流阈值,可以抛出异常或返回特定的响应。
throw new CustomRateLimitException("Rate limit exceeded");
}
}
}
在环绕通知中,你可以根据 rateLimiterService.isAllow(key)
的返回值来决定是否允许用户访问:
@Around("controllerMethods()")
public Object rateLimit(ProceedingJoinPoint joinPoint) throws Throwable {
String key = // 生成唯一的 key,用于标识用户或请求。例如,可以使用用户的 ID 或 IP 地址。
if (rateLimiterService.isAllow(key)) {
return joinPoint.proceed(); // 如果允许访问,则继续执行目标方法。
} else {
// 如果达到限流阈值,可以抛出异常或返回特定的响应。
throw new CustomRateLimitException("Rate limit exceeded");
}
}
创建一个自定义的异常类来表示限流阈值已达:
public class CustomRateLimitException extends RuntimeException {
public CustomRateLimitException(String message) {
super(message);
}
}
最后,你需要在 Spring Boot 的配置类中启用 AOP 并配置切面:
@EnableAspectJAutoProxy
@Configuration
public class AppConfig {
@Bean
public RateLimitAspect rateLimitAspect() {
return new RateLimitAspect();
}
}
通过结合 AOP 和 Redis,我们可以实现一个灵活且可扩展的接口限流机制。AOP 使得我们可以将限流逻辑与业务逻辑分离,使得代码更加清晰和易于维护。而 Redis 作为一个高性能的存储系统,可以快速地处理大量的限流请求。在实际应用中,你可能需要根据具体需求调整限流策略和 Redis 的使用方式。