Springboot+Guava实现单机令牌桶限流
令牌桶算法
系统会维护一个令牌(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();
}
}
测试

参考:https://zhuanlan.zhihu.com/p/621267375?utm_id=0
Springboot+Guava实现单机令牌桶限流的更多相关文章
- coding++:高并发解决方案限流技术-使用RateLimiter实现令牌桶限流-Demo
RateLimiter是guava提供的基于令牌桶算法的实现类,可以非常简单的完成限流特技,并且根据系统的实际情况来调整生成token的速率. 通常可应用于抢购限流防止冲垮系统:限制某接口.服务单位时 ...
- 高并发解决方案限流技术-----使用RateLimiter实现令牌桶限流
1,RateLimiter是guava提供的基于令牌桶算法的实现类,可以非常简单的完成限流特技,并且根据系统的实际情况来调整生成token的速率.通常可应用于抢购限流防止冲垮系统:限制某接口.服务单位 ...
- ASP.NET Core中使用令牌桶限流
在限流时一般会限制每秒或每分钟的请求数,简单点一般会采用计数器算法,这种算法实现相对简单,也很高效,但是无法应对瞬时的突发流量. 比如限流每秒100次请求,绝大多数的时间里都不会超过这个数,但是偶尔某 ...
- Redis令牌桶限流
一 .场景描述 在开发接口服务器的过程中,为了防止客户端对于接口的滥用,保护服务器的资源, 通常来说我们会对于服务器上的各种接口进行调用次数的限制.比如对于某个 用户,他在一个时间段(interval ...
- 【springcloud】2.eureka源码分析之令牌桶-限流算法
国际惯例原理图 代码实现 package Thread; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomi ...
- 漏桶、令牌桶限流的Go语言实现
限流 限流又称为流量控制(流控),通常是指限制到达系统的并发请求数. 我们生活中也会经常遇到限流的场景,比如:某景区限制每日进入景区的游客数量为8万人:沙河地铁站早高峰通过站外排队逐一放行的方式限制同 ...
- 令牌桶限流思路分享(PHP+Redis实现机制)
一 .场景描述 在开发接口服务器的过程中,为了防止客户端对于接口的滥用,保护服务器的资源, 通常来说我们会对于服务器上的各种接口进行调用次数的限制.比如对于某个 用户,他在一个时间段(interval ...
- Go 分布式令牌桶限流 + 兜底策略
上篇文章提到固定时间窗口限流无法处理突然请求洪峰情况,本文讲述的令牌桶线路算法则可以比较好的处理此场景. 工作原理 单位时间按照一定速率匀速的生产 token 放入桶内,直到达到桶容量上限. 处理请求 ...
- SpringBoot进阶教程(六十七)RateLimiter限流
在上一篇文章nginx限流配置中,我们介绍了如何使用nginx限流,这篇文章介绍另外一种限流方式---RateLimiter. v限流背景 在早期的计算机领域,限流技术(time limiting)被 ...
- 分布式接口幂等性、分布式限流:Guava 、nginx和lua限流
接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用. 举个最简单的例子,那就是支付,用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常,此 ...
随机推荐
- SpringBoot开发简单接口流程
SpringBoot开发接口 初始化 新建项目 (1)使用 IDEA 的过程,新建Project,左侧选 Spring Initializr,点Next (2)选 8 版本,点Next (3)左侧选择 ...
- 【HarmonyOS】详解低代码端云一体化开发之连接器
[关键字] 元服务.低代码平台.端云一体化开发.连接器.拖拽式UI [1.写在前面] 前面我们写了两篇文章分别介绍了低代码平台的基本使用和端云一体化开发中数据模型的使用,有需要的可以了解一下,文章地 ...
- 智能合约HardHat框架环境的搭建
1.首先创建一个npm项目 PS C:\Users\lcds\blockchainprojects> mkdir hardhatcontract PS C:\Users\lcds\blockch ...
- RLHF技术在智能金融中的应用:提高金融智能化和自动化水平”
目录 引言 随着人工智能技术的不断发展和普及,金融智能化和自动化水平也得到了显著提高.在这个时代,RLHF(Reinforcement Learning with Human Feedback)技术已 ...
- Self-Instruct 论文解读:利用大模型自己给自己生成指令数据,指令数据自动生成
总览 大规模"指令调整"的语言模型,即指令微调的LLM,已经表现出非凡的零样本能力,尤其是推广新任务上. 然而,这些模型严重依赖于人类编写的指令数据,而这些数据通常在数量.多样性和 ...
- 2023-06-28:你想要用小写字母组成一个目标字符串 target。 开始的时候,序列由 target.length 个 ‘?‘ 记号组成 而你有一个小写字母印章 stamp。 在每个回合,你可
2023-06-28:你想要用小写字母组成一个目标字符串 target. 开始的时候,序列由 target.length 个 '?' 记号组成 而你有一个小写字母印章 stamp. 在每个回合,你可以 ...
- 数仓性能调优:大宽表关联MERGE性能优化
摘要:本文主要为大家讲解在数仓性能调优过程中,关于大宽表关联MERGE性能优化过程. 本文分享自华为云社区<GaussDB(DWS)性能调优:大宽表关联MERGE性能优化>,作者:譡里个檔 ...
- Unity中的PostProcessScene:深入解析与实用案例
Unity中的PostProcessScene:深入解析与实用案例 在Unity游戏开发中,我们经常需要对场景进行后处理,以实现更丰富的视觉效果.Unity提供了一个名为PostProcessScen ...
- 给SqlSugar一个优化建议
声明:本作者无恶意只是觉得这个功能很不错,平常工作当中经常用到,自己框架也做了相应的支持,本着技术共享目的. 一.对象组合设置列更新支持 建议度:高 业务场景 1.更新列表需统一设置 例如:修改人ID ...
- 《逆向工程核心原理》之DLL注入
DLL注入 DLL注入指的是向运行中的其他进程强制插入特定的DLL文件.从技术细节来说,DLL注入命令其他进程自行调用LoadLibrary() API,加载(Loading)用户指定的DLL文件.D ...