小明:最近我在做项目,遇到了接口被恶意刷新的问题。是不是要在 Spring Boot 中搞个限流?但是,怎么做才能既简单又优雅呢?
老王:嘿嘿,小明,看来你是要挑战一下如何在 Spring Boot 中优雅地实现限流了!限流是一个非常重要的环节,尤其是对于高并发的系统。你可以通过多种方式实现,但是今天我给你推荐几种最优雅的方式,让你的系统既高效又不丑陋。
1. 让我们从简单的内存限流开始
对于一些低并发、简单的应用,内存限流已经足够了,直接通过 Spring Boot 自带的工具就可以实现。而且,这种方式既简单又直接。
小王:内存限流?那不就是把请求数限制在一个固定的范围内嘛。
怎么做?
我们可以通过 Spring Boot 的 AOP(面向切面编程) 技术来实现简单的限流。这样,你就能在不修改太多代码的情况下,动态地控制接口的访问频率。
首先,使用一个自定义的注解 @RateLimit
来标记需要限流的方法。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
int limit() default 5; // 每秒最大请求次数
}
然后,使用 AOP 拦截这些方法,并进行限流判断:
@Aspect
@Component
public class RateLimitAspect {
private final Map<String, Integer> requestCount = new HashMap<>();
@Around("@annotation(rateLimit)")
public Object around(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {
String methodName = joinPoint.getSignature().toShortString();
int limit = rateLimit.limit();
// 获取当前时间的秒数作为请求标识
int currentSecond = LocalDateTime.now().getSecond();
requestCount.putIfAbsent(methodName + currentSecond, 0);
if (requestCount.get(methodName + currentSecond) >= limit) {
throw new TooManyRequestsException("请求过于频繁,请稍后再试!");
}
// 增加请求次数
requestCount.put(methodName + currentSecond, requestCount.get(methodName + currentSecond) + 1);
return joinPoint.proceed();
}
}
简单的内存限流,就是通过记录每秒请求次数,并在请求超过限制时抛出异常,轻松搞定。
2. 使用 Redis 来实现分布式限流
当系统规模扩大,单一应用已经无法应对高并发时,内存限流就显得不太够用了。此时,分布式限流就显得尤为重要。Redis 是一个非常适合用来做限流的工具,因为它的性能高,支持高并发,而且可以共享数据。
小王:Redis 限流?就是用 Redis 来记录每个请求的次数吗?
怎么做?
依赖 Redis:首先,你需要在 Spring Boot 中配置 Redis。
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
创建限流工具类:用 Redis 来记录请求次数,每秒都会更新。
@Component
public class RedisRateLimiter {
@Autowired
private StringRedisTemplate redisTemplate;
public boolean isAllowed(String key, int limit) {
String currentKey = "rate_limit:" + key + ":" + LocalDateTime.now().getSecond();
// 如果当前请求在 Redis 中不存在,表示首次请求,设置为 1
Boolean exists = redisTemplate.hasKey(currentKey);
if (exists == null || !exists) {
redisTemplate.opsForValue().set(currentKey, "1", 1, TimeUnit.SECONDS);
return true;
}
// 如果当前请求次数大于限流值,拒绝请求
String count = redisTemplate.opsForValue().get(currentKey);
if (Integer.parseInt(count) >= limit) {
return false; // 请求超限
}
// 否则,增加请求次数
redisTemplate.opsForValue().increment(currentKey);
return true;
}
}
在控制器中使用限流:
@RestController
public class MyController {
@Autowired
private RedisRateLimiter redisRateLimiter;
@GetMapping("/some-api")
public String someApi(@RequestParam String userId) {
if (!redisRateLimiter.isAllowed(userId, 5)) {
throw new TooManyRequestsException("请求过于频繁,请稍后再试!");
}
return "请求成功";
}
}
这种方式使用 Redis 来存储每秒的请求次数,保证在分布式环境下每个用户或 IP 都可以有独立的限流控制。
3. 使用 Guava 的 RateLimiter 做限流
除了 Redis,还有一个轻量级的限流方式,Guava 的 RateLimiter
类。这个库提供了一个令牌桶算法,非常适合用来做流量控制。
小王:Guava 的 RateLimiter?这听起来很像一个流量“限制器”。
怎么做?
依赖 Guava:首先,添加 Guava 依赖。
<!-- pom.xml -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1-jre</version>
</dependency>
创建 RateLimiter 实例:
@Configuration
public class RateLimiterConfig {
@Bean
public RateLimiter rateLimiter() {
// 每秒生成 5 个令牌
return RateLimiter.create(5.0);
}
}
在服务中使用限流:
@RestController
public class MyController {
@Autowired
private RateLimiter rateLimiter;
@GetMapping("/api")
public String api() {
// 尝试获取一个令牌,如果没有令牌就阻塞
if (!rateLimiter.tryAcquire()) {
throw new TooManyRequestsException("请求过于频繁,请稍后再试!");
}
return "请求成功";
}
}
Guava 的 RateLimiter
是基于令牌桶算法的,适合于节流控制,避免过高的请求负载。
4. Spring Cloud Gateway 限流
如果你使用的是 Spring Cloud Gateway 作为 API 网关,它也提供了非常优雅的限流解决方案,支持基于请求的 IP、用户、URL 等多种策略。
小王:Spring Cloud Gateway 直接做限流?好像有点高级的样子。
怎么做?
你只需要在 application.yml
中配置限流策略:
spring:
cloud:
gateway:
routes:
- id: rate_limited_route
uri: https://example.com
predicates:
- Path=/api/**
filters:
- name: RequestRateLimiter
args:
key: "user"
redis-rate-limiter.replenish-rate: 5
redis-rate-limiter.burst-capacity: 10
通过这种配置,Spring Cloud Gateway 会自动管理请求的限流,开发者无需写代码。
总结:
看!限流的方式有很多种,可以根据不同的业务需求和技术栈选择最适合的。无论是简单的内存限流、分布式的 Redis 限流,还是轻量级的 Guava 限流,甚至是通过 Spring Cloud Gateway 提供的网关限流,都是非常优雅且实用的做法。
限流不仅能保护你的系统免受恶意请求的攻击,还能提高系统的稳定性和可用性。想要做一个高效的系统,限流这一步绝对是必不可少的!