手写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

相关推荐

发表回复

登录后才能评论