google.guava 实现 限流

 

POM 文件在最后

 

调用入口 :

package com..web;

import com..anno.RateLimitAnno;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;

@RestController
public class LimiterController {

    @RateLimitAnno //自定义注解
    @RequestMapping("/limiter")
    public String limiter(HttpServletRequest request){
        StringBuilder sb = new StringBuilder("{");
        //调用业务层,操作
        sb.append("'result':'0000','msg':'成功'");
        return  sb.append("}").toString();
    }

}

 

自定义注解:

package com..anno;

import java.lang.annotation.*;

@Inherited   // 允许子类继承  元注解
@Documented  // 被 javadoc工具记录
@Target({ElementType.METHOD,ElementType.FIELD,ElementType.TYPE}) //注解可能出现在Java程序中的语法位置
@Retention(RetentionPolicy.RUNTIME)  //注解保留时间,保留至运行时
public @interface RateLimitAnno {


}

 

限流工具类:

package com..util;

import com.google.common.util.concurrent.RateLimiter;
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.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;

@Component
@Scope
@Aspect
public class RateLimitAspect {

    final double permitsPerSecond = 5.0;        //每秒生成5个令牌
    final long warmupPeriod = 1;                //在warmupPeriod时间内RateLimiter会增加它的速率,在抵达它的稳定速率或者最大速率之前
    final TimeUnit timeUnit = TimeUnit.SECONDS; //参数warmupPeriod 的时间单位
    /*
     * 创建一个稳定输出令牌的RateLimiter,保证了平均每秒不超过qps个请求
     * 当请求到来的速度超过了qps,保证每秒只处理qps个请求
     * 当这个RateLimiter使用不足(即请求到来速度小于qps),会囤积最多qps个请求
     *
     * 创建的是SmoothBursty 实例 平滑稳定
     */
    RateLimiter rateLimiter = RateLimiter.create(permitsPerSecond);

    /**
     *
     * 根据指定的稳定吞吐率和预热期来创建RateLimiter,
     * 这里的吞吐率是指每秒多少许可数(通常是指QPS,每秒多少查询),
     * 在这段预热时间内,RateLimiter每秒分配的许可数会平稳地增长直到预热期结束时达到其最大速率(只要存在足够请求数来使其饱和)。
     * 同样地,如果RateLimiter 在warmupPeriod时间内闲置不用,它将会逐步地返回冷却状态。
     * 也就是说,它会像它第一次被创建般经历同样的预热期。
     * 返回的RateLimiter 主要用于那些需要预热期的资源,这些资源实际上满足了请求(比如一个远程服务),
     * 而不是在稳定(最大)的速率下可以立即被访问的资源。
     * 返回的RateLimiter 在冷却状态下启动(即预热期将会紧跟着发生),并且如果被长期闲置不用,它将回到冷却状态
     *
     * 创建的是SmoothWarmingUp实例    平滑预热
     */
    RateLimiter rl = RateLimiter.create(permitsPerSecond,warmupPeriod,timeUnit);


    //设置业务切入点为标注了自定义注解的位置
    @Pointcut("@annotation(com.wondersgroup.anno.RateLimitAnno)")
    public void aspectService(){ }

    //统计
    int countSuccess,countFail = 0;

    //环绕通知
    @Around("aspectService()")
    public Object aroundMsg(ProceedingJoinPoint joinPoint){

        Object obj = null;

        boolean flag = rateLimiter.tryAcquire(); // 在无延迟下的情况下获得

        // 从RateLimiter获取一个许可,该方法会被阻塞直到获取到请求。
        // 如果存在等待的情况的话,告诉调用者获取到该请求所需要的睡眠时间。该方法等同于acquire(1)。
        //double waitTime = rabuyGoodsteLimiter.acquire(); 我非要得到令牌才返回

        try{

            if(flag){ //如果获取了令牌,则可以继续执行业务层面的逻辑
                obj = joinPoint.proceed();

                countSuccess++;//并发时,统计不准确!!!

            }else{
                obj = "{'result':'0001','msg':'当前系统繁忙,请重试...'}"; //未获取到令牌,直接返回

                countFail++;
            }

        }catch(Throwable ex){
            ex.printStackTrace();
        }

        System.out.println(flag +"   :    "+ obj + " success:" + countSuccess + " , fail:" + countFail);

        return obj;
    }

}

 

快速访问 :http://localhost:8080/limiter

 

 

pom 文件 :

<?xml version="1.0" encoding="UTF-8"?>
<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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.</groupId>  <!-- groupId 需完善 -->
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
        </dependency>

        <!--Redisson插件-->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.10.2</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.1</version>
        </dependency>


        <!--guava JAR-->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>23.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.4</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

 

log4j.properties:

#original#
#log4j.rootLogger = A1,R
#
#log4j.appender.A1 = org.apache.log4j.ConsoleAppender
#log4j.appender.A1.Target = System.out
#log4j.appender.A1.layout = org.apache.log4j.PatternLayout
#log4j.appender.A1.layout.ConversionPattern = [CAMTZYY] %p [%t] %c.%M(%L) | %m%n
#log4j.appender.R = org.apache.log4j.RollingFileAppender
#log4j.appender.R.File = /datum/logs/dn-workbench.log
#log4j.appender.R.MaxFileSize = 10MB
#log4j.appender.R.Threshold = ALL
#log4j.appender.R.layout = org.apache.log4j.PatternLayout
#log4j.appender.R.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss,SSS} %p [%t] %c{1}.%M()| line:%L | %m%n

#now#
log4j.rootLogger=error, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out

log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### direct messages to file mylog.log ###

#log4j.appender.file=org.apache.log4j.FileAppender
#log4j.appender.file.File=/datum/logs/user-service.log
#
#log4j.appender.file.layout=org.apache.log4j.PatternLayout
#log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### set log levels - for more verbose logging change 'info' to 'debug' ###

#log4j.logger.org.mybatis.spring = ERROR,A1,R
#log4j.logger.org.springframework = ERROR,A1,R
#log4j.logger.org.apache.commons.beanutils = ERROR,A1,R
#log4j.logger.com.icss.lkj.common.filter = DEBUG,A1,R
#log4j.logger.com.icss.lkj.front.controller = DEBUG,A1,R

#log4j.logger.org.mybatis.spring = ERROR,A1,R
#log4j.logger.org.springframework = ERROR,A1,R
#log4j.logger.org.apache.commons.beanutils = ERROR,A1,R
#log4j.logger.com.icss.lkj.common.filter = DEBUG,A1,R
#log4j.logger.com.icss.lkj.front.controller = DEBUG,A1,R

 

posted @ 2021-05-12 11:26  Li&Fan  阅读(861)  评论(0编辑  收藏  举报