• 使用AOP和Semaphore对项目中具体的某一个接口进行限流


    整体思路:

    一 具体接口,可以自定义一个注解,配置限流量,然后对需要限流的方法加上注解即可!

    二 容器初始化的时候扫描所有所有controller,并找出需要限流的接口方法,获取对应的限流量

    三 使用拦截器或者aop,对加上注解的方法进行限流,采用配置的信号量

    自定义注解

    /**
     * 限流注解
     */
    @Target(ElementType.METHOD)  //作用与方法上
    @Retention(RetentionPolicy.RUNTIME) //注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
    @Documented
    public @interface ApiRateLimit {
        int value(); //控制并发最大数量
    }
    

      

    出初始化限流配置,注意:这里设置的如果限流量一样,则两个方法一起限流,比如两个方法限流量都是5,则两个方法总共可以支持最多5个线程访问

    实际可以自己调整,加个方法名当key,则可以保证每个方法都独自限流:

    /**
     *  ApplicationContextAware实现类可以获得spring上下文
     *   间接获取ApplicationContext中的所有bean,向切面添加所有接口的配置的限流量
     */
    @Component
    public class InitApiLimit implements ApplicationContextAware {
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            Map<String, Object> beanMap = applicationContext.getBeansWithAnnotation(RestController.class);
            System.out.println(beanMap.size());
            beanMap.forEach((k,v)->{
                Class<?> controllerClass = v.getClass();
                System.out.println(controllerClass.toString());
                System.out.println(controllerClass.getSuperclass().toString());
                //获取所有声明的方法
                Method[] allMethods = controllerClass.getSuperclass().getDeclaredMethods();
                for (Method method:allMethods){
                    System.out.println(method.getName());
                    //判断方法是否使用了限流注解
                    if (method.isAnnotationPresent(ApiRateLimit.class)){
                        //获取配置的限流量,实际值可以动态获取,配置key,根据key从配置文件获取
                        int value = method.getAnnotation(ApiRateLimit.class).value();
                        String key = String.valueOf(value);
                        //key作为key.value为具体限流量,传递到切面的map中
                        ApiLimitAspect.semaphoreMap.put(key,new Semaphore(value));
                    }
                }
                System.out.println("----信号量个数:"+ApiLimitAspect.semaphoreMap.size());
            });
        }
    }
    

      注意:这里有一点需要说明,一旦使用了代理,因为是controller',没有借口,所以是cglib,会创建子类

    ,此时从容器中获取的是代理的子类,默认是不会有自定义注解的,所以得getSuperClass,从父类,即controller中获取注解信息

    编写切面,这里是最主要的,使用jdk自带的信号量:

    限流切面

    /**
     * 限流切面
     */
    @Aspect
    @Order(value = Ordered.HIGHEST_PRECEDENCE) //最高优先级
    @Component
    public class ApiLimitAspect {
        //存储限流量和方法,必须是static且线程安全,保证所有线程进入都唯一
        public static Map<String, Semaphore> semaphoreMap= new ConcurrentHashMap<>();
        //拦截所有controller 的所有方法
        @Around("execution(* com.hou.serviceorder.controller.*.*(..))")
        public Object around(ProceedingJoinPoint joinPoint){
            Object result=null;
            Semaphore semaphore=null;
            Class<?> clz = joinPoint.getTarget().getClass();//获取目标对象
            Signature signature = joinPoint.getSignature();//获取增强方法信息
            String name = signature.getName();
            String limitKey = String.valueOf(getLimitKey(clz, name));
            if(limitKey!=null && !"".equals(limitKey)){
                semaphore = semaphoreMap.get(limitKey);
                try {
                    semaphore.acquire();
                    result=joinPoint.proceed();
                } catch (Throwable e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();
                }
            }
            return result;
        }
    
        //获取拦截方法配置的限流key,没有返回null
        private Integer getLimitKey(Class<?> clz, String methodName){
            for (Method method:clz.getDeclaredMethods()){
                if(method.getName().equals(methodName)){//找出目标方法
                    if(method.isAnnotationPresent(ApiRateLimit.class)){//判断是否是限流方法
                        return method.getAnnotation(ApiRateLimit.class).value();
                    }
                }
            }
            return null;
        }
    }
    

      

    使用注解

        @ApiRateLimit(value = 5)
        @GetMapping("/name")
        public String getOrderName() throws InterruptedException {
            System.out.println("-----进入getOrder方法------");
            TimeUnit.SECONDS.sleep(2);
            return "order";
        }
    
        @ApiRateLimit(value = 5)
        @GetMapping("/order")
        public Order getOrder(String id) throws InterruptedException {
            System.out.println("-----进入getOrder方法------");
            TimeUnit.SECONDS.sleep(2);
            return new Order(id,"侯征");
        }
    

      

    测试

    public class TestSe {
    
        public static void main(String[] args) {
            //测试信号并发量
            for (int i = 0; i < 10; i++) {
                new Thread(()->{
                    //访问目标接口
                    RestTemplate restTemplate = new RestTemplate();
                    restTemplate.getForObject("http://localhost:8081/order/name",String.class);
                }).start();
            }
        }
    }
    

      会发现最多5个一起打印:

  • 相关阅读:
    执行预定义命令
    利用jemalloc优化mysql
    ssh增加密匙登录
    vsftpd增加ssl安全验证
    利用脚本获取mysql的tps,qps等状态信息
    innodb_buffer_pool_size 大小建议
    linux多核cpu下的负载查看
    DDoS deflate+iptables防御轻量级ddos攻击
    CentOS通过日志反查入侵
    shell读取文件每行,并执行命令
  • 原文地址:https://www.cnblogs.com/houzheng/p/10817175.html
Copyright © 2020-2023  润新知