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

ops/2024/9/25 11:10:38/

📢 大家好,我是 【战神刘玉栋】,有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、定义自定义注解

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

java">@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、调用具体策略类,注册生效这些规则;

java">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 接口,是为后续策略切换留下扩展方式。

java">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接口,也是为后续扩展准备。

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

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

java">@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次。

java">@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 方式,那可以利用自动配置类 + 条件注解的模式实现。
部分代码如下:

java">@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


http://www.ppmy.cn/ops/94120.html

相关文章

docker日志容器乱码

Docker容器乱码通常是因为容器内部的应用程序、日志输出或者交互界面使用了与容器的默认编码不兼容的字符编码。例如&#xff0c;容器内的应用可能默认使用UTF-8编码&#xff0c;而容器的终端或日志系统可能使用了如ISO-8859-1的不兼容编码&#xff0c;导致显示为乱码。 1、解…

高可用Docker Swarm

高可用 Docker Swarm 安装 1. 环境介绍 **注意: 三台机器 即是主节点又是从节点 ** 主机名称swarm-01swarm-02swarm-03操作系统Centos 7Centos 7Centos 7内核版本3.10.0-957.e17.x86_643.10.0-957.e17.x86_643.10.0-957.e17.x86_64IP192.168.100.100192.168.100.200192.168.10…

下载 MC Minecraft Launcher 我的世界 启动器下载

下载地址&#xff1a; https://mc-launcher.com/wp/minecraft/ 我们下期见&#xff0c;拜拜&#xff01;

Windows 11系统SQL Server 2016 数据库安装 最新2024教程和使用

文章目录 目录 文章目录 安装流程 小结 概要安装流程技术细节小结 概要 文件可以关注作者公众号《全栈鍾猿》&#xff0c;发您 安装流程 双击运行 在资源管理器页面如图所示 点击全选-->取消勾选如图所示的3个---》点击下一步 点击下一步 安装完成&#xff0c;如图所示 &a…

动手实践生成式人工智能GAI

基于台湾大学李宏毅教授的Introduction to Generative AI 2024 Spring课程&#xff0c;总结 生成式人工智能GAI实践任务。参考资源包括课程的课件、视频和实践任务的代码。 Introduction to Generative AI 2024 Spring 也感谢B站Up主搬运的视频 李宏毅2024春《生成式人工智…

打造编程学习的“知识宝库”:高效笔记记录与整理指南

如何高效记录并整理编程学习笔记&#xff1f; 在编程学习的海洋中&#xff0c;高效的笔记记录和整理方法就像一张珍贵的航海图&#xff0c;能够帮助我们在浩瀚的知识中找到方向。如何建立一个既能快速记录又易于回顾的笔记系统&#xff1f;如何在繁忙的学习中保持笔记的条理性…

Linux·权限与工具-yum与vim

1. Linux软件包管理器 yum 1.1 什么是软件包 在Linux下安装软件&#xff0c;一个通常的办法是下载到程序的源代码&#xff0c;并进行编译&#xff0c;得到可执行程序。但这样做太麻烦了&#xff0c;于是有些人把一些常用的软件提前编译好&#xff0c;做成软件包(可以理解成Win…

高通分享:glTF 2.0扩展MPEG、3GPP在AR/VR 3D场景的沉浸式体验

日前&#xff0c;高通技术标准高级总监托马斯斯托克哈默尔&#xff08;Thomas Stockhammer&#xff09;和高通技术标准总监伊梅德布亚齐兹&#xff08;Imed Bouazizi&#xff09;撰文分享了ISO和Khronos之间是如何紧密合作&#xff0c;并最终开发出MPEG-I Scene Description IS…