首页 > 其他分享 >Springboot+Guava实现单机令牌桶限流

Springboot+Guava实现单机令牌桶限流

时间:2023-05-27 15:13:43浏览次数:47  
标签:令牌 return Springboot Resp 限流 key import Guava public

令牌桶算法

系统会维护一个令牌(token)桶,以一个恒定的速度往桶里放入令牌(token),这时如果有请求进来想要被处理,则需要先从桶里获取一个令牌(token),当桶里没有令牌(token)可取时,则该请求将被拒绝服务。令牌桶算法通过控制桶的容量、发放令牌的速率,来达到对请求的限制。

==========================

Google开源工具包Guava提供了限流工具类RateLimiter,该类基于令牌桶算法实现流量限制,使用十分方便,而且十分高效

Pom中引入依赖

<properties>
  <spring-boot-start-version>2.6.9</spring-boot-start-version>
  <guava-version>31.1-jre</guava-version>
  </properties>
  <!--单机限流-->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
        </dependency>

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

编写注解,带上注解的接口实现限流控制

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;
import java.util.concurrent.TimeUnit;

/**
 * @description: Guava限流单机
 * @author: GuoTong
 * @createTime: 2023-05-27 14:30
 * @since JDK 1.8 OR 11
 **/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface GuavaAopLimit {

    /**
     * 资源的key,唯一:不同的接口,不同的流量控制
     */
    String key() default "";

    /**
     * 最多的访问限制次数
     */
    double permitsPerSecond();

    /**
     * 获取令牌最大等待时间
     */
    long timeout();

    /**
     * 获取令牌最大等待时间,单位(例:分钟/秒/毫秒) 默认:毫秒
     */
    TimeUnit timeunit() default TimeUnit.MILLISECONDS;

    /**
     * 得不到令牌的提示语
     */
    String msg() default "Guava单机限流令牌桶没了";
}

编写切面

import com.google.common.collect.Maps;
import com.google.common.util.concurrent.RateLimiter;
import com.gton.annotation.GuavaAopLimit;
import com.gton.handler.RedisLimitException;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Map;

/**
 * @description: :使用AOP切面拦截限流注解
 * @author: GuoTong
 * @createTime: 2023-05-27 14:32
 * @since JDK 1.8 OR 11
 **/
@Slf4j
@Aspect
@Component
public class GuavaLimitAspect {
    /**
     * 不同的接口,不同的流量控制
     * map的key为 Limiter.key
     */
    private final Map<String, RateLimiter> limitMap = Maps.newConcurrentMap();

    @Around("@annotation(com.gton.annotation.GuavaAopLimit)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        //拿limit的注解
        GuavaAopLimit limit = method.getAnnotation(GuavaAopLimit.class);
        if (limit != null) {
            //key作用:不同的接口,不同的流量控制
            String key = limit.key();
            RateLimiter rateLimiter = null;
            //验证缓存是否有命中key
            if (!limitMap.containsKey(key)) {
                // 创建令牌桶
                rateLimiter = RateLimiter.create(limit.permitsPerSecond());
                limitMap.put(key, rateLimiter);
                log.info("新建了令牌桶={},容量={}", key, limit.permitsPerSecond());
            }
            rateLimiter = limitMap.get(key);
            // 拿令牌
            boolean acquire = rateLimiter.tryAcquire(limit.timeout(), limit.timeunit());
            // 拿不到命令,直接返回异常提示
            if (!acquire) {
                log.debug("令牌桶={},获取令牌失败", key);
                throw new RedisLimitException(limit.msg());
            }
        }
        return joinPoint.proceed();
    }
}

定义统一响应结构

/**
 * @description: 通用返回对象
 * 贫血型模型
 * @author: GuoTong
 * @createTime: 2022-09-24 13:16
 * @since JDK 1.8 OR 11
 **/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class Resp<T> implements Serializable {
    //状态码
    protected String code;

    //提示信息
    protected String msg;

    //返回的数据
    protected T data;


    /**
     * Description:常用返回数据抽离
     */
    public static <T> Resp<T> LoginOk() {
        return new Resp<T>().
                setCode(ContextCommonMsg.LOGIN_SUCCESS_CODE).
                setMsg(ContextCommonMsg.LOGIN_SUCCESS_MSG);
    }

    public static <T> Resp<T> LoginFail() {
        return new Resp<T>().
                setCode(ContextCommonMsg.LOGIN_FAIL_CODE).
                setMsg(ContextCommonMsg.LOGIN_FAIL_MSG);
    }

    public static <T> Resp<T> error(String errorMsg) {
        return new Resp<T>().
                setCode(ContextCommonMsg.FAIL_CODE).
                setMsg(errorMsg);
    }

    public static <T> Resp<T> error() {
        return new Resp<T>().
                setCode(ContextCommonMsg.FAIL_CODE).
                setMsg(ContextCommonMsg.FAIL_MSG);
    }

    public static <T> Resp<T> error(String errorMsg, String failCode) {
        return new Resp<T>().
                setCode(failCode).
                setMsg(errorMsg);
    }

    public static <T> Resp<T> error(String errorMsg, String failCode, T data) {
        return new Resp<T>().
                setCode(failCode).
                setData(data).
                setMsg(errorMsg);
    }

    public static <T> Resp<T> Ok(T data) {
        return new Resp<T>().
                setCode(ContextCommonMsg.SUCCESS_CODE).
                setMsg(ContextCommonMsg.SUCCESS_MSG).
                setData(data);
    }

    public static <T> Resp<T> Ok() {
        return new Resp<T>().
                setCode(ContextCommonMsg.SUCCESS_CODE).
                setMsg(ContextCommonMsg.SUCCESS_MSG);
    }

    public static <T> Resp<T> Ok(T data, String msg) {
        return new Resp<T>().
                setCode(ContextCommonMsg.SUCCESS_CODE).
                setMsg(msg).
                setData(data);
    }

    public static <T> Resp<T> Ok(T data, String msg, String successCode) {
        return new Resp<T>().
                setCode(successCode).
                setMsg(msg).
                setData(data);
    }

}

自定义异常:用于限流控制失败

/**
 * @description: 限流异常
 * @author: GuoTong
 * @createTime: 2023-05-27 13:46
 * @since JDK 1.8 OR 11
 **/
public class RedisLimitException extends RuntimeException {

    public RedisLimitException(String msg) {
        super(msg);
    }

}

springboot异常捕获


/**
 * @description: 全局异常处理:
 * @author: GuoTong
 * @createTime: 2022-11-27 11:17
 * @since JDK 1.8 OR 11
 **/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    // 全局异常拦截
    @ExceptionHandler
    public Resp handlerException(Exception e) {
        e.printStackTrace();
        log.error("服务执行异常---->{}", e.getMessage());
        return Resp.error(e.getMessage());
    }
	
    @ExceptionHandler(value = RedisLimitException.class)
    public Resp handlerException(RedisLimitException e) {
        log.error("限流触发---->{}", e.getMessage());
        return Resp.error(e.getMessage());
    }

接口使用测试


/**
 * @description:
 * @author: GuoTong
 * @createTime: 2022-11-25 00:02
 * @since JDK 1.8 OR 11
 **/
@RestController
public class AA {

    @GetMapping("/guava")
    @GuavaAopLimit(key = "limit_guava", permitsPerSecond = 1, timeout = 500, timeunit = TimeUnit.MILLISECONDS, msg = "基于Guava+AOP实现的单机限流!")
    public Resp okGuava() {
        return Resp.Ok();
    }

}

测试

image

参考:https://zhuanlan.zhihu.com/p/621267375?utm_id=0

标签:令牌,return,Springboot,Resp,限流,key,import,Guava,public
From: https://www.cnblogs.com/gtnotgod/p/17436759.html

相关文章

  • java基于springboot+vue时间管理系统、日记管理系统,附源码+数据库+lw文档+PPT
    1、项目介绍本次设计任务是要设计一个时间管理系统,通过这个系统能够满足时间管理的管理功能。系统的主要功能包括首页,个人中心,系统公告管理,用户管理,时间分类管理,事件数据管理,目标数据管理,用户日记管理等功能。管理员可以根据系统给定的账号进行登录,登录后可以进入时间管理系统,对......
  • SpringBoot打jar包
    pom<build><plugins><!--设定JDK版本--><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configur......
  • 8、Hystrix 线程池隔离与接口限流
    线程池隔离技术的设计原则Hystrix采取了bulkhead舱壁隔离技术,来将外部依赖进行资源隔离,进而避免任何外部依赖的故障导致本服务崩溃线程池隔离,学术名称:bulkhead,舱壁隔离外部依赖的调用在单独的线程中执行,这样就能跟调用线程隔离开来,避免外部依赖调用timeout耗时过长,导致调用线程......
  • springboot添加多个环境的配置文件
    1,在resources目录下面新建application.properties,application-dev.properties,application-pre.properties,application-prod.properties2,在application.properties只添加一行要激活的环境,其他不用添加spring.profiles.active=dev3,其他属性文件根据需要配置不同的属性......
  • springboot2.7使用log4j2的maven配置
    先排查自带的<!--排除自带的--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><exclusions>......
  • SpringBoot集成JWT(极简版)
    话不多说,直接上代码接口统一前缀设置importorg.springframework.context.annotation.Configuration;importorg.springframework.web.bind.annotation.RestController;importorg.springframework.web.servlet.config.annotation.PathMatchConfigurer;importorg.springframewo......
  • JAVA语言springboot框架实现的求职招聘管理系统
    技术架构技术框架:SpringBoot+FreeMarker+JPA+MySQL5.7运行环境:jdk8+IntelliJIDEA+maven3+宝塔面板宝塔部署教程回到IDEA,点击编辑器右侧maven图标,执行package,完成后就会在根目录里生成一个target目录,在里面会打包出一个jar文件。宝塔新建一个数据库,导入数据库文件......
  • JAVA语言开发springboot框架实现的自动化立体智慧仓库WMS
    技术架构技术框架:SpringBoot+layui+HTML+CSS+JS运行环境:jdk8+IntelliJIDEA+maven3+宝塔面板宝塔部署教程回到IDEA,点击编辑器右侧maven图标,执行package,完成后就会在根目录里生成一个target目录,在里面会打包出一个jar文件。宝塔新建一个数据库,导入数据库文件,数据......
  • idea显示springboot多服务启动界面service
    如果是多模块的微服务,idea提供了一个可以多服务启动的界面services,如果你的项目里没看到这个界面:那么你需要在顶级的maven工程中找到这个配置,然后找到componentname="RunDashboard"这个节点整个替换掉:<componentname="RunDashboard"><optionname="configurationTypes">......
  • SpringBoot2.0实现SpringCloud config自动刷新之坑点
    在使用rabbitmq之后并不能实现客户端的配置自动刷新,原因是我参考的资料都是springboot1.x的,Springboot2.0的改动较大,之前1.0的/bus/refresh全部整合到actuador里面了,所以之前1.x的management.security.enabled全部失效,不适用于2.0适用于2.0的配置是这样的:management:endpoin......