手写RateLimiter
自定义注解 封装
如果需要让接口实现限流RateLimiter使用
网关:一般拦截所有的接口 实现限流 秒杀 抢购 或者大流量的接口才会实现限流。灵活
不是所有接口都需要限流 秒杀等接口需要限流
设计: 加注解的才可以实现限流
注解形式而不是网关形式 只有需要限流的才加这个注解
传统的方式整合RateLimiter有很大缺点:代码重复量特别大,而且本身不支持注解方式
限流代码可以写在网关,相当于针对所有接口实现限流,维护性不强
不是所有的接口都需要限流 一般限流主要针对大流量,比如秒杀抢购
分析案例:
定义一个自定义注解
Spring Boot整合 spring aop
使用环绕通知
判断请求方法上是否有 注解
如果有 使用反射获取注解方法上的参数
调用原生RateLImiter方法创建令牌
如果获取令牌超时 直接调用服务降级(自己定义)
如果能够获取令牌 直接进入实际请求方法
本案例没有用到 扫包 直接请求过来走方法的
首先自定义注解:
引入maven依赖:
<!-- springboot 整合AOP --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>pom
pom:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.toov5</groupId> <artifactId>springboot-guava</artifactId> <version>0.0.1-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.0.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>25.1-jre</version> </dependency> <!-- springboot 整合AOP --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> </dependencies> </project>
封装注解:
package com.toov5.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ExtRateLimiter { //以秒为单位 固定的速录往桶中添加 double permitsPerSecond(); //在规定的时间内,如果没有获取到令牌的话,直接走降级处理 long timeout(); }
aop封装:
package com.toov5.aop; import java.io.IOException; import java.io.PrintWriter; import java.lang.reflect.Method; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import com.google.common.util.concurrent.RateLimiter; import com.toov5.annotation.ExtRateLimiter; //aop环绕通知 判断拦截所有springmvc请求,判断请求方法上是否存在ExtRateLimiter @Aspect //aop两种方式 注解 和 xml方式 @Component public class RateLimiterAop { // 存放接口是否已经存在 private static ConcurrentHashMap<String, RateLimiter> rateLimiterMap = new ConcurrentHashMap<String, RateLimiter>(); //定义切入点 拦截 @Pointcut("execution(public * com.toov5.controller.*.*(..))") //所有类 所有方法 任意参数 public void rlAop() { } //使用aop环绕通知判断拦截所有springmvc请求,判断方法上是否存在ExRanteLimiter @Around("rlAop()") public Object doBefore(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { //1、判断请求方法上是否存在@RxtRateLimiter注解 //2、如果请求方法上存在此注解@RxtRateLimiter注解 比如加上了RequestMapping表示请求方法 Method sinatureMethod = getSinatureMethod(proceedingJoinPoint); if (sinatureMethod == null) { //直接报错 return null; } //3、使用Java的反射机制获取拦截方法上自定义注解的参数 ExtRateLimiter extRateLimiter = sinatureMethod.getDeclaredAnnotation(ExtRateLimiter.class); if (extRateLimiter==null) { //方法上没有注解 //直接放行代码 进入实际请求方法中 proceedingJoinPoint.proceed(); } //4、调用原生RateLimiter创建令牌 double permitsPerSecond = extRateLimiter.permitsPerSecond(); //获取参数 long timeout = extRateLimiter.timeout(); //调用原生的RateLimiter创建令牌 保证每个请求对应的是单例的RateLimiter 一个请求一个RateLimiter 使用hashMap RateLimiter.create(permitsPerSecond); String requestURI = getRequestUrl(); RateLimiter rateLimiter = null; if (rateLimiterMap.containsKey(requestURI)) { //如果检测到 rateLimiter = rateLimiterMap.get(requestURI); }else { //如果没有 则添加 rateLimiter = RateLimiter.create(permitsPerSecond); rateLimiterMap.put(requestURI, rateLimiter); } //5、获取桶中的令牌,如果没有有效期获取到令牌,直接调用降级方法。 boolean tryAcquire = rateLimiter.tryAcquire(timeout,TimeUnit.MILLISECONDS); if (!tryAcquire) { //服务降级 fallback(); return null; } //6、否则 直接进入到实际请求方法中 return proceedingJoinPoint.proceed(); } private void fallback() throws IOException { //在aop编程中获取响应 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletResponse response = attributes.getResponse(); //防止乱码 response.setHeader("Content-type", "text/html;charset=UTF-8"); PrintWriter writer = response.getWriter(); try { writer.println("亲,别抢了"); } catch (Exception e) { writer.close(); } } private String getRequestUrl() { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); return request.getRequestURI(); } private Method getSinatureMethod(ProceedingJoinPoint proceedingJoinPoint) { //获取到目标代理对象 MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature(); //获取当前aop拦截的方法 Method method = signature.getMethod(); return method; } }
controller
package com.toov5.controller; import java.util.concurrent.TimeUnit; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.google.common.util.concurrent.RateLimiter; import com.toov5.annotation.ExtRateLimiter; import com.toov5.service.OrderService; @RestController public class IndexController { @Autowired private OrderService orderService; //create方法中传入一个参数 以秒为单位固定的速率值 1r/s 往桶中存入一个令牌 RateLimiter rateLimiter = RateLimiter.create(1); //独立线程!它自己是个线程 //相当于接口每秒只能接受一个客户端请求 @RequestMapping("/addOrder") public String addOrder() { //限流放在网关 获取到当前 客户端从桶中获取对应的令牌 结果表示从桶中拿到令牌等待时间 //如果获取不到令牌 就一直等待 double acquire = rateLimiter.acquire(); System.out.println("从桶中获取令牌等待时间"+acquire); boolean tryAcquire=rateLimiter.tryAcquire(500,TimeUnit.MILLISECONDS); //如果在500sms没有获取到令牌 直接走降级 if (!tryAcquire) { System.out.println("别抢了,等等吧!"); return "别抢了,等等吧!"; } //业务逻辑处理 boolean addOrderResult = orderService.addOrder(); if (addOrderResult) { System.out.println("恭喜抢购成功!等待时间"); return "恭喜抢购成功!"; } return "抢购失败!"; } //以每秒1个的速度往桶中添加令牌 @RequestMapping("/findIndex") @ExtRateLimiter(permitsPerSecond=1.0,timeout=500) public void findIndex() { System.out.println("findIndex"+System.currentTimeMillis()); } }
service
package com.toov5.service; import org.springframework.stereotype.Service; @Service public class OrderService { public boolean addOrder() { System.out.println("db...正在操作订单表数据库"); return true; } }
启动类
package com.toov5; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } }
疯狂点击: