

SpringBoot使用自定义注解+AOP+Redis实现接口限流 - Jae1995
source link: https://www.cnblogs.com/jae-tech/p/16625091.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

SpringBoot使用自定义注解+AOP+Redis实现接口限流
为什么要限流
系统在设计的时候,我们会有一个系统的预估容量,长时间超过系统能承受的TPS/QPS阈值,系统有可能会被压垮,最终导致整个服务不可用。为了避免这种情况,我们就需要对接口请求进行限流。
所以,我们可以通过对并发访问请求进行限速或者一个时间窗口内的的请求数量进行限速来保护系统或避免不必要的资源浪费,一旦达到限制速率则可以拒绝服务、排队或等待。
限流背景
系统有一个获取手机短信验证码的接口,因为是开放接口,所以为了避免用户不断的发送请求获取验证码,防止恶意刷接口的情况发生,于是用最简单的计数器方式做了限流,限制每个IP每分钟只能请求一次,然后其他每个手机号的时间窗口限制则是通过业务逻辑进行判断。一般一些接口访问量比较大的,可能会压垮系统的,则需要加入流量限制!如:秒杀等...
实现限流
1、引入依赖
<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>
2、自定义限流注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RateLimiter { /** * 限流key */ String key() default Constants.RATE_LIMIT_KEY; /** * 限流时间,单位秒 */ int time() default 60; /** * 限流次数 */ int count() default 100; /** * 限流类型 */ LimitType limitType() default LimitType.DEFAULT; /** * 限流后返回的文字 */ String limitMsg() default "访问过于频繁,请稍候再试"; }
3、限流切面
@Aspect @Component public class RateLimiterAspect { private final static Logger log = LoggerFactory.getLogger(RateLimiterAspect.class); @Autowired private RedisUtils redisUtils; @Before("@annotation(rateLimiter)") public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable { int time = rateLimiter.time(); int count = rateLimiter.count(); long total = 1L; String combineKey = getCombineKey(rateLimiter, point); try { if(redisUtils.hasKey(combineKey)){ total = redisUtils.incr(combineKey,1); //请求进来,对应的key加1 if(total > count) throw new ServiceRuntimeException(rateLimiter.limitMsg()); }else{ redisUtils.set(combineKey,1,time); //初始化key } } catch (ServiceRuntimeException e) { throw e; } catch (Exception e) { throw new ServiceRuntimeException("网络繁忙,请稍候再试"); } } /** * 获取限流key * @param rateLimiter * @param point * @return */ public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) { StringBuffer stringBuffer = new StringBuffer(rateLimiter.key()); if (rateLimiter.limitType() == LimitType.IP) { stringBuffer.append(IpUtils.getIpAddr(ServletUtils.getRequest())).append("-"); } MethodSignature signature = (MethodSignature) point.getSignature(); Method method = signature.getMethod(); Class<?> targetClass = method.getDeclaringClass(); stringBuffer.append(targetClass.getName()).append("-").append(method.getName()); return stringBuffer.toString(); } }
4、写一个简单的接口进行测试
@RestController public class TestController { @RateLimiter(time = 60, count = 1, limitType = LimitType.IP, limitMsg = "一分钟内只能请求一次,请稍后重试") @GetMapping("/hello") public ResultMsg hello() { return ResultMsg.success("Hello World!"); } }
5、全局异常拦截
@RestControllerAdvice public class GlobalExceptionHandler { private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); /** * 业务异常 */ @ExceptionHandler(ServiceRuntimeException.class) public ResultMsg handleServiceException(ServiceRuntimeException e, HttpServletRequest request) { return ResultMsg.error(e.getMessage()); } /** * 系统异常 */ @ExceptionHandler(Exception.class) public ResultMsg handleException(Exception e, HttpServletRequest request) { return ResultMsg.error("系统异常"); } }
6、接口测试
1)第一次发送,正常返回结果

2)一分钟内第二次发送,返回错误,限流提示

好了,大功告成啦
还有其他的限流方式,如滑动窗口限流方式(比计数器更严谨)、令牌桶等...,有兴趣的小伙伴可以学习一下
附源码
Recommend
-
4
环境:SpringBoot2.3.8.RELEASE + JDK1.8 本文教你如何在SpringBoot环境下使得自定义的注解能够使用${xxx}表达式。 相关依赖
-
4
-
7
【DB系列】SpringBoot缓存注解@Cacheable之自定义key策略及缓存失效时间指定 ...
-
6
在实际生产项目中,经常需要对如身份证信息、手机号、真实姓名等的敏感数据进行加密数据库存储,但在业务代码中对敏感信息进行手动加解密则十分不优雅,甚至会存在错加密、漏加密、业务人员需要知道实际的加密规则等的情况。 本文将介绍使用springboot+m...
-
5
SpringBoot整合SpringSecurityOauth2实现鉴权-动态权限
-
3
一个注解搞定SpringBoot接口定制属性加解密 - 福隆苑居士 - 博客园 上个月公司另一个团队做的新项目上线后大体上运行稳定,但包括研发负...
-
5
SpringBoot下Validation自定义验证注解(简单实现) 精选 原创 代码不是马 2022-10-...
-
9
我们在企业级的开发中,必不可少的是对日志的记录,实现有很多种方式,常见的就是基于AOP+注解进行保存,但是考虑到程序的流畅和效率,我们可以使用异步进行保存,小编最近在spring和springboot源码中看到有很多的监听处理贯...
-
6
1 springBoot的依赖 确定项目中包含可以注解的依赖 <dependency> <groupId>org.springframework.boot</groupId> <...
-
7
SpringBoot + 自定义注解 + AOP 高级玩法打造通用开关 最近在工作中迁移代码的时候发现了以前自己写的一个通用开...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK