• 手写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);
          
    }
    }

     疯狂点击:

     

  • 相关阅读:
    深入Java虚拟机(4)——网络移动性
    安装ftp碰到的问题及解决方法
    6.设置ListView的Item的高度无效
    hdu1181(变形课)
    ZooKeeper分布式集群部署及问题
    管理线程之创建线程
    Linux系统编程——多线程实现多任务
    Request.Params用法,后台接收httpget参数
    resharper警告 :linq replace with single call to FirstOrDefault
    SQL Prompt几个快捷键
  • 原文地址:https://www.cnblogs.com/toov5/p/9992600.html
Copyright © 2020-2023  润新知