当前位置: 首页 > news >正文

《框架封装 · 优雅接口限流方案》

📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗
🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,欢迎多多交流。👍

文章目录

    • 写在前面的话
    • 接口限流方案
      • 设计先行
      • 实战方案
        • Step1、定义自定义注解
        • Step2、加载规则注解
        • Step3、限流规则加载
        • Step4、定义限流拦截类
        • Step5、利用切面检测限流效果
      • 开发使用
      • 策略切换
    • 总结陈词

CSDN.gif

写在前面的话

接口限流是一种控制应用程序或服务访问速率的技术措施,主要用于防止因请求过多导致系统过载、响应延迟或服务崩溃。在高并发场景下,合理地实施接口限流对于保障系统的稳定性和可用性至关重要。
本篇文章介绍一下在框架封装过程中,如何优雅的实现接口限流方案,希望能帮助到大家。

技术栈:后端 SpringCloud + 前端 Vue/Nuxt

关联文章 - 程序猿入职必会:
《程序猿入职必会(1) · 搭建拥有数据交互的 SpringBoot 》
《程序猿入职必会(2) · 搭建具备前端展示效果的 Vue》
《程序猿入职必会(3) · SpringBoot 各层功能完善 》
《程序猿入职必会(4) · Vue 完成 CURD 案例 》
《程序猿入职必会(5) · CURD 页面细节规范 》
《程序猿入职必会(6) · 返回结果统一封装》
《程序猿入职必会(7) · 前端请求工具封装》
《程序猿入职必会(8) · 整合 Knife4j 接口文档》
《程序猿入职必会(9) · 用代码生成器快速开发》
《程序猿入职必会(10) · 整合 Redis(基础篇)》

相关博文 - 学会 SpringMVC 系列
《学会 SpringMVC 系列 · 基础篇》
《学会 SpringMVC 系列 · 剖析篇(上)》
《学会 SpringMVC 系列 · 剖析入参处理》
《学会 SpringMVC 系列 · 剖析出参处理》
《学会 SpringMVC 系列 · 返回值处理器》
《学会 SpringMVC 系列 · 消息转换器 MessageConverters》
《学会 SpringMVC 系列 · 写入拦截器 ResponseBodyAdvice》
《程序猿入职必会(1) · 搭建拥有数据交互的 SpringBoot 》


接口限流方案

设计先行

先确定一下要实现的效果,再开始编码工作。
限流操作要尽可能灵活,那可以做到控制器方法的层面。
同时,又要支持多个参数组合。那可以考虑自定义注解的方式。
最好还可以支持多种限流策略,那可以选择使用条件注解配置的方式。


实战方案

Step1、定义自定义注解

这步骤没什么特殊的,定义一个限流注解,方便添加。
一些和限流相关的参数考虑进去。

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimit {/*** 限流名称,例如 TestLimit*/String value() default "";/*** 指定时间内允许通过的请求数*/int count();/*** 限流时间,单位秒*/int durationSeconds();/*** 限流模式*/MetricType metricType() default MetricType.TYPE_REQUEST_AMOUNT;/*** 限流消息提示*/String failureMsg() default "";enum MetricType {/*** 直接拒绝*/TYPE_REQUEST_AMOUNT}}
Step2、加载规则注解

可以借助 SpringBoot 的初始化事件监听机制,在项目启动的时候完成这个动作。
部分示例代码如下,主要逻辑为:
1、找出所有控制器接口方法;
2、过滤出存在 RateLimit 注解的方法;
3、构建为限流实体 RateLimitStrategy;
4、调用具体策略类,注册生效这些规则;

public void load() {log.info("开始加载限流规则");List<RateLimitStrategy> rules = SpringUtil.getRequestMappingHandlerMappingBean().getHandlerMethods().entrySet().stream().filter(e -> !e.getKey().getPatternsCondition().getPatterns().isEmpty()).filter(e -> e.getValue().hasMethodAnnotation(RateLimit.class)).map(e -> {HandlerMethod handlerMethod = e.getValue();RateLimit rateLimit = handlerMethod.getMethodAnnotation(RateLimit.class);String resourceId = StrUtil.isBlank(rateLimit.value())? MethodUtil.getMethodSign(handlerMethod.getMethod()): rateLimit.value();return createRule(rateLimit, resourceId);}).collect(Collectors.toList());log.info("共找到{}条规则,开始注册规则...", rules.size());this.rateLimitRuleRegister.registerRules(rules);log.info("限流规则注册完成");
}private static RateLimitStrategy createRule(RateLimit rateLimit, String resourceId) {RateLimitStrategy.MetricType metricType = RateLimitStrategy.MetricType.valueOf(rateLimit.metricType().name());return RateLimitStrategy.newBuilder().setName(resourceId).setMetricType(metricType).setThreshold(rateLimit.count()).setStatDuration(rateLimit.durationSeconds()).setStatDurationTimeUnit(TimeUnit.SECOND).setLimitMode(RateLimitStrategy.LimitMode.MODE_LOCAL).build();
}
Step3、限流规则加载

前面提到加载完成后,开始注册规则。
这里先以 Sentinel 为例实现限流策略加载,自定义 SentinelRateLimitRuleRegister 实现 RateLimitRuleRegister 接口的 registerRules 方法。
这里预留了 RateLimitRuleRegister 接口,是为后续策略切换留下扩展方式。

public class SentinelRateLimitRuleRegister implements RateLimitRuleRegister {@Overridepublic void registerRules(List<RateLimitStrategy> rateLimitStrategies) {if (rateLimitStrategies.isEmpty()) {return;}Map<RateLimitStrategy.MetricType, List<RateLimitStrategy>> ruleMap = rateLimitStrategies.stream().collect(Collectors.groupingBy(RateLimitStrategy::getMetricType));// 暂时只考虑支持流控规则List<FlowRule> flowRules = ruleMap.get(RateLimitStrategy.MetricType.TYPE_REQUEST_AMOUNT).stream().map(rateLimitStrategy -> {double threshold = rateLimitStrategy.getThreshold() * 1.0 / rateLimitStrategy.getStatDuration();FlowRule flowRule = new FlowRule();// 资源名,资源名是限流规则的作用对象flowRule.setResource(rateLimitStrategy.getName());// 限流阈值类型,QPS 或线程数模式,这里使用 QPS 模式flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);// 限流阈值flowRule.setCount(threshold <= 1 ? 1 : threshold);// 单机模式flowRule.setClusterMode(false);// 流控效果(直接拒绝 / 排队等待 / 慢启动模式),不支持按调用关系限流,默认直接拒绝flowRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);return flowRule;}).collect(Collectors.toList());if (!flowRules.isEmpty()) {FlowRuleManager.loadRules(flowRules);}}
}
Step4、定义限流拦截类

还是以 Sentinel 为例说明,这里预留ApiRateLimiter接口,也是为后续扩展准备。

public class SentinelApiRateLimiter implements ApiRateLimiter {@Overridepublic boolean accept(String resourceId) {boolean entry = SphO.entry(resourceId);if (entry) {SphO.exit();}return entry;}
}
Step5、利用切面检测限流效果

定义一个切面,对包含 RateLimit 注解的方法生效,调用相应限流策略类,执行其 accept 方法,看是否正常。

@Aspect
@RequiredArgsConstructor
@Slf4j
public class ApiRateLimitAspect {private final ApiRateLimiter apiRateLimiter;private final RateLimitFailureResultProvider rateLimitFailureResultProvider;@Around("@annotation(rateLimit)")public Object rateLimitAspect(ProceedingJoinPoint proceedingJoinPoint, RateLimit rateLimit) throws Throwable {// 规则资源IDString resourceId;// 优先使用注解上的资源ID,如果注解上没有配置资源ID,则使用方法签名作为资源IDif (StrUtil.isBlank(rateLimit.value())) {Signature signature = proceedingJoinPoint.getSignature();MethodSignature methodSignature = (MethodSignature) signature;Method targetMethod = methodSignature.getMethod();resourceId = MethodUtil.getMethodSign(targetMethod);} else {resourceId = rateLimit.value();}boolean accept;try {// 尝试获取令牌accept = this.apiRateLimiter.accept(resourceId);} catch (Exception e) {accept = true;log.error("[RateLimit] 限流异常: {}", e.getMessage());}if (!accept) {String failureMessage = this.rateLimitFailureResultProvider.getFailureMessage(rateLimit.failureMsg());throw new RateLimitException(failureMessage);}return proceedingJoinPoint.proceed();}
}

开发使用

上面的若干步骤,是由框架层面封装的。
针对具体开发人员,使用起来就简单多了。
Step1、选择需要限流的控制层方法,添加@RateLimit注解,下方代表该接口每秒最多只能被调用2次。

@RateLimit(count = 2, durationSeconds = 1)
@RequestMapping(value = "/simple3")
public ResultVO simple3() throws Exception {return ResultVO.success("简单测试接口成功");
}

Step2、启动项目,高频访问该接口,会提示报错信息。

{"code":"10100","data":"","message":"请求过于频繁,请稍后再试!","error":"","traceId":"fbc8590f4038347c","guide":""}

策略切换

前面示例可以看到,很多 Sentinel 的策略逻辑,都预留了接口,这个也是为后续扩展策略准备的。
如果还想使用其他模式实现限流,例如 Guava 方式,那可以利用自动配置类 + 条件注解的模式实现。
部分代码如下:

@SuppressWarnings("UnstableApiUsage")
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RateLimiter.class)
@ConditionalOnProperty(prefix = OnelinkRateLimitProperties.PREFIX, name = "module", havingValue = "guava")
static class GuavaRateLimitAutoConfiguration {@Beanpublic GuavaApiRateLimiter guavaApiRateLimiter() {return new GuavaApiRateLimiter();}@Beanpublic RateLimitRuleRegister guavaRateLimitRuleRegister(GuavaApiRateLimiter guavaApiRateLimiter) {return new GuavaRateLimitRuleRegister(guavaApiRateLimiter);}
}@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(SphO.class)
@ConditionalOnProperty(prefix = OnelinkRateLimitProperties.PREFIX, name = "module", havingValue = "sentinel", matchIfMissing = true)
static class SentinelRateLimitAutoConfiguration {@Beanpublic SentinelApiRateLimiter sentinelApiRateLimit() {return new SentinelApiRateLimiter();}@Beanpublic RateLimitRuleRegister sentinelRateLimitRuleRegister() {return new SentinelRateLimitRuleRegister();}}

总结陈词

此篇文章介绍了关于限流方案的封装,上方提供的是部分代码,仅供学习参考。
💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。

CSDN_END.gif

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 第R2周:Pytorch实现:LSTM-火灾温度预测
  • 20240812软考架构-------软考36-40答案解析
  • Haproxy知识点
  • sp eric靶机渗透测试
  • 【学习笔记】Day 13
  • RuoYi-Vue新建模块
  • 复杂SQL查询案例分析:计算每个月的累积唯一用户数
  • LVS详解
  • 【已解决】AttributeError: ‘diet’ object has no attribute ‘has_key’
  • 前端性能优化方法
  • 快速拷贝复制工具软件@拷贝工具@多线程拷贝@robocopy
  • 视频汇聚平台智能边缘分析一体机分析平台摄像头异常位移算法识别检测
  • 串行通信协议--CAN(Controller Area Network Bus,控制器局域网总线)
  • Python 异步编程:Sqlalchemy 异步实现方式
  • HarmonyOS ArkTS 构建布局
  • 【每日笔记】【Go学习笔记】2019-01-10 codis proxy处理流程
  • CNN 在图像分割中的简史:从 R-CNN 到 Mask R-CNN
  • java第三方包学习之lombok
  • Js基础知识(一) - 变量
  • Otto开发初探——微服务依赖管理新利器
  • rabbitmq延迟消息示例
  • Spring Boot快速入门(一):Hello Spring Boot
  • Yeoman_Bower_Grunt
  • 记一次用 NodeJs 实现模拟登录的思路
  • 栈实现走出迷宫(C++)
  • Semaphore
  • ​MPV,汽车产品里一个特殊品类的进化过程
  • ​七周四次课(5月9日)iptables filter表案例、iptables nat表应用
  • #Linux杂记--将Python3的源码编译为.so文件方法与Linux环境下的交叉编译方法
  • #pragma预处理命令
  • (1)Map集合 (2)异常机制 (3)File类 (4)I/O流
  • (152)时序收敛--->(02)时序收敛二
  • (Redis使用系列) Springboot 使用Redis+Session实现Session共享 ,简单的单点登录 五
  • (Redis使用系列) SpringBoot 中对应2.0.x版本的Redis配置 一
  • (zhuan) 一些RL的文献(及笔记)
  • (多级缓存)缓存同步
  • (附源码)spring boot球鞋文化交流论坛 毕业设计 141436
  • (附源码)ssm经济信息门户网站 毕业设计 141634
  • (附源码)计算机毕业设计SSM疫情下的学生出入管理系统
  • (机器学习的矩阵)(向量、矩阵与多元线性回归)
  • (剑指Offer)面试题41:和为s的连续正数序列
  • (六)DockerCompose安装与配置
  • (转)大道至简,职场上做人做事做管理
  • (自适应手机端)响应式新闻博客知识类pbootcms网站模板 自媒体运营博客网站源码下载
  • .Net Core 笔试1
  • .NET 漏洞分析 | 某ERP系统存在SQL注入
  • .NET单元测试
  • .NET开源的一个小而快并且功能强大的 Windows 动态桌面软件 - DreamScene2
  • .Net面试题4
  • @RequestBody详解:用于获取请求体中的Json格式参数
  • @ResponseBody
  • @transactional 方法执行完再commit_当@Transactional遇到@CacheEvict,你的代码是不是有bug!...
  • [ C++ ] STL---string类的使用指南
  • [ vulhub漏洞复现篇 ] Apache Flink目录遍历(CVE-2020-17519)
  • [AutoSar]工程中的cpuload陷阱(三)测试