手写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 { 
//[email protected] 
//[email protected] 比如加上了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); 
} 
}

 疯狂点击:

 手写RateLimiter详解编程语言

 

原创文章,作者:Maggie-Hunter,如若转载,请注明出处:https://blog.ytso.com/tech/pnotes/16132.html

(0)
上一篇 2021年7月19日 19:00
下一篇 2021年7月19日 19:00

相关推荐

发表回复

登录后才能评论