Redis + AOP + 自定义注解实现限流的攻略分为以下几个步骤:
1. 集成 Redis
Redis 是一种基于内存的数据存储系统,它可以高效地存储和操作数据,特别适合用于缓存和限流等场景。我们首先需要将 Redis 集成到项目中。
可以使用官方的 Java 客户端 Jedis 来访问 Redis。在 Maven 中引入 Jedis 的依赖,并配置 Redis 的连接地址和密码:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.5.3</version>
</dependency>
@Configuration
public class RedisConfig {
@Bean
public RedisConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setHostName("localhost");
config.setPort(6379);
config.setPassword("password");
return new LettuceConnectionFactory(config);
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory());
template.setDefaultSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
}
2. 定义自定义注解
我们根据业务需求定义一个自定义注解 @RateLimit
,该注解用于限制某个方法的访问频率。该注解包含三个参数:
value
:限制的时间窗口,单位为秒。limit
:时间窗口内的最大访问次数。key
:访问次数计数器的键名,可以使用 SpEL 表达式动态生成。
我们在代码中使用 @RateLimit
注解来标记需要进行限流的方法。在 AOP 中,我们通过解析该注解来实现具体的限流逻辑。
/**
* 限流注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimit {
// 限制的时间窗口,单位为秒
int value() default 1;
// 时间窗口内的最大访问次数
int limit() default 10;
// 访问次数计数器的键名
String key() default "";
}
3. 实现 AOP 拦截器
我们使用 AOP 拦截器来实现限流功能。在进入被拦截的方法之前,检查该方法是否被 @RateLimit
注解标记,如果是,则使用 Redis 实现计数器来限制访问频率。
在实现 AOP 拦截器时,我们需要使用 @Around
注解来标记拦截器方法,并使用 ProceedingJoinPoint
对象来访问拦截的方法和它的参数。在拦截器中,我们首先解析 @RateLimit
注解,并根据注解参数来确定时间窗口和访问次数限制。然后,我们通过 Redis 的 incr()
命令来对访问次数计数器进行累加,并使用 expire()
命令设置计数器的过期时间。最后,如果计数器的值超过了访问次数限制,我们就抛出 RateLimitException
异常。
/**
* 限流拦截器
*/
@Aspect
@Component
public class RateLimitAspect {
private static final Logger logger = LoggerFactory.getLogger(RateLimitAspect.class);
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Around("@annotation(rateLimit)")
public Object limit(ProceedingJoinPoint point, RateLimit rateLimit) throws Throwable {
Signature signature = point.getSignature();
String methodName = signature.getName();
String className = signature.getDeclaringTypeName();
Object[] args = point.getArgs();
String key = rateLimit.key();
if (StringUtils.isBlank(key)) {
key = className + "#" + methodName;
}
int limit = rateLimit.limit();
int value = rateLimit.value();
long count = redisTemplate.opsForValue().increment(key, 1);
redisTemplate.expire(key, value, TimeUnit.SECONDS);
logger.debug("Request {}#{}, rate limit is {}/{}", className, methodName, count, limit);
if (count > limit) {
throw new RateLimitException("You have been rate-limited");
}
return point.proceed(args);
}
}
4. 测试限流
我们通过两个示例来演示如何使用 @RateLimit
注解和限流拦截器来限制方法的访问频率。
首先,我们定义一个接口 HelloService
,其中有两个方法 hello
和 world
,分别被 @RateLimit
注解标记:
public interface HelloService {
@RateLimit(value = 1, limit = 5)
String hello(String name);
@RateLimit(key = "#name", value = 1, limit = 2)
String world(String name);
}
hello
方法有一个时间窗口为 1 秒,访问次数限制为 5 的限制;world
方法则根据参数动态生成计数器的键名,并设置时间窗口为 1 秒,访问次数限制为 2。
现在,我们使用 Spring Boot 来实现 HelloService
接口,并调用其中的两个方法:
@RestController
public class HelloController implements HelloService {
@Override
public String hello(String name) {
return "Hello, " + name;
}
@Override
public String world(String name) {
return "World, " + name;
}
}
我们使用 Postman 工具来模拟请求,并设置每秒钟发起 10 次请求。在限流功能生效之前,我们可以得到 10 次正常响应;在限流功能生效之后,我们可以看到只有前 5 次请求得到了响应,后 5 次请求因为超过了访问次数限制而得到响应码为 429 的错误响应。
下面是两个示例的详细代码:
示例 1:固定时间窗口限流
@RestController
public class FixedWindowController {
@RateLimit(value = 1, limit = 5)
@GetMapping("/fixed")
public String fixedWindow() {
return "Success";
}
}
示例 2:滑动时间窗口限流
@RestController
public class SlidingWindowController {
@RateLimit(key = "#name", value = 1, limit = 2)
@GetMapping("/sliding/{name}")
public String slidingWindow(@PathVariable String name) {
return "Hello, " + name;
}
}
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Redis+AOP+自定义注解实现限流 - Python技术站