需求分析:
1、什么是接口限流:
接口限流是一种控制访问特定接口的请求数量的机制,以防止系统过载并确保服务的稳定性和可用性。在软件架构中,接口限流通常用于以下几个目的:
- 资源保护:防止服务器因为过多的并发请求而耗尽资源,如数据库连接、线程或内存。
- 性能保证:通过限制请求的速率,确保系统在高负载下仍能保持可接受的响应时间。
- 防止滥用:避免恶意用户或自动化脚本对系统进行滥用,如防止暴力破解尝试或分布式拒绝服务(DDoS)攻击。
- 服务降级:在系统负载过高时,通过限流可以优先保证核心业务逻辑的运行,而非核心服务可以暂时降级。
- 成本控制:通过限流可以避免不必要的硬件扩展,从而控制成本。
- 遵守协议:满足服务级别协议(SLA)的要求,确保服务提供商能够按照协议提供服务。
接口限流可以通过多种方式实现,包括:
- 时间窗口计数器:在固定的时间窗口内(如每秒)限制请求的数量。
- 滑动窗口计数器:使用多个时间窗口来平滑请求速率,避免在时间窗口边界处的请求峰值。
- 令牌桶算法:以固定速率生成令牌,请求在获取令牌后才能被处理,允许一定程度的突发流量。
- 漏桶算法:请求按照固定速率从桶中流出,超出桶容量的请求被丢弃,平滑处理请求。
在实际应用中,接口限流可以通过编程实现(如使用Java中的并发工具),也可以通过使用现成的限流库(如Google的Guava库中的RateLimiter
),或者利用反向代理和负载均衡器(如Nginx)来实现。此外,一些云服务提供商也提供了限流服务,可以直接集成到云环境中。
本次使用AOP+自定义注解+redis的方法来实现部分关键接口的限流
2、简单的原理如图所示
3、设计思路:
1、写一个自定义注解接收一个参数count 次/分钟
2、以方法名+用户ids作为key,次数作为value
3、每次请求前先从redis拿数据决定怎么处理
4、什么是AOP(面向切面编程)
AOP 是一种编程范式,它允许程序者将横切关注点(如日志记录、事务管理、安全性等)与业务逻辑分离,从而提高程序的模块化和可维护性。在 Java 中,AOP 的实现通常依赖于特定的框架,如 Spring AOP 或 AspectJ。
5、AOP 的核心概念:
- 切面(Aspect) :表示一个模块化的横切关注点。
- 连接点(Join point) :程序执行的特定点,如方法的调用或处理异常。
- 切点(Pointcut) :匹配连接点的表达式,用于指定切面应该在何处应用。
- 通知(Advice) :在切点处执行的动作,如前置、后置、环绕等。
- 目标对象(Target Object) :被通知(Advice)的对象。
- 代理(Proxy) :用于在不修改目标对象的情况下,通过引入额外功能(如日志、事务)来创建目标对象的一个代理。
6、AOP可以简单的如下图描述:
最终打算使用AOP+自定义注解的方式来实现
7、自定义注解
在 Java 中,注解(Annotation)是一种特殊的接口,它可以用来标记类、方法或变量,以提供元数据。自定义注解允许开发者定义自己的注解类型,以便于在代码中标记特定的行为或配置。
自定义注解可以包含以下元素:
-
元注解(Meta-Annotations) :
-
用于注解其它注解的注解,提供关于注解的信息。常见的元注解包括:
@Target
:指定注解可以用于哪些元素(如类、方法、参数等)。@Retention
:指定注解的保留策略,决定注解在何时可用(如源码、编译时、运行时)。@Documented
:指定注解应该被Javadoc工具记录在文档中。@Inherited
:指定注解是否被子类继承。
-
-
成员变量:
- 自定义注解可以有0个或多个成员变量,这些变量被称为注解的属性。成员变量的数据类型必须是基本数据类型、String、Class、enum、注解或这些类型的数组。
-
默认值:
- 注解的属性可以指定默认值,这样在使用注解时可以省略该属性。
-
标记注解(Marker Annotations) :
- 如果自定义注解不包含任何属性,它就是一个标记注解,用来简单地标记某个元素。
-
单成员注解:
- 如果自定义注解只包含一个成员变量,那么这个成员变量可以不用声明,而是直接在注解使用时指定该值。
开始实现:
1、首先得先写一个自定义的注解
java
package com.ruoyi.common.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Limit {
/**
* 限制次数(每分钟)
* @return
*/
int count() default 5;
}
2、写一个切面类来处理逻辑
java
@Aspect
@Component
public class LimitAspect {
@Autowired
private RedisTemplate redisTemplate;
// 定义一个切点,匹配所有标记Limit注解的方法
@Pointcut("@annotation(com.common.annotation.Limit)")
public void LimitMethod() {}
// 前置通知:在方法执行前执行
@Before("LimitMethod()")
public void beforeMethod(JoinPoint joinPoint) {
//获取注解的count
MethodSignature signature = (MethodSignature)joinPoint.getSignature();
Limit annotation = signature.getMethod().getAnnotation(Limit.class);
int count = annotation.count();
//拼接redis的key
String name = signature.getMethod().getName();
//这里看业务自己选择
String userId = "1";
String key = name + userId;
Object o = redisTemplate.opsForValue().get(key);
//第一次访问
if (o == null) {
redisTemplate.opsForValue().increment(key, 1);
// 设置键的过期时间为五分钟(300秒)
redisTemplate.expire(key, 5, TimeUnit.MINUTES);
} else {
int redisCount = (int) o;
//未超过阈值
if (redisCount < count) {
redisTemplate.opsForValue().increment(key, 1);
}
//达到阈值后
else {
throw new RuntimeException("超过请求限制,请五分钟后在请求");
}
}
}
}
3、简单写个接口测试一下:
less
@Limit(count = 10)
@GetMapping(value = "/test")
public AjaxResult test() {
return AjaxResult.success();
}
测试结果:
未点击时
第十一次时
有条件的朋友可以来个一件三连支持一下
如果可以的话顺便关注一下公众号:想摆烂的码农小郑