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

news2024/9/26 3:32:12

📢 大家好,我是 【战神刘玉栋】,有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 {
    @Override
    public 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 {
    @Override
    public 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 {

        // 规则资源ID
        String resourceId;
        // 优先使用注解上的资源ID,如果注解上没有配置资源ID,则使用方法签名作为资源ID
        if (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 {

    @Bean
    public GuavaApiRateLimiter guavaApiRateLimiter() {
        return new GuavaApiRateLimiter();
    }

    @Bean
    public 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 {

    @Bean
    public SentinelApiRateLimiter sentinelApiRateLimit() {
        return new SentinelApiRateLimiter();
    }

    @Bean
    public RateLimitRuleRegister sentinelRateLimitRuleRegister() {
        return new SentinelRateLimitRuleRegister();
    }

}

总结陈词

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

CSDN_END.gif

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2053017.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

权限审批也能这么人性化?没错,可道云teamOS让团队关系更和谐

作为一位企业管理者&#xff0c;我深知权限审批在企业管理中的重要性。它不仅仅是一个简单的流程&#xff0c;更是保障企业信息安全、提升团队协作效率的关键环节。 然而&#xff0c;过去我们常常面临权限审批流程繁琐、效率低下的问题&#xff0c;这不仅影响了我们的工作效率…

如何在 Odoo 16 Studio 模块中自定义视图和报告

为了有效地运营公司&#xff0c;需要定制的软件系统。Odoo 平台提供针对单个应用程序量身定制的管理解决方案和用户友好的界面&#xff0c;以便开发应用程序&#xff0c;而无需更复杂的后端功能。该平台支持使用简单的拖放功能和内置工具创建和修改更多定制的 Odoo 应用程序。企…

ubuntu如何监控Xvfb虚拟显示器

在Ubuntu中监控Xvfb显示器主要涉及到使用VNC服务器来远程访问这个环境。以下是一些基本步骤&#xff1a; 安装Xvfb和相关工具: 使用apt安装Xvfb和x11vnc&#xff0c;x11vnc是一个VNC服务器&#xff0c;可以远程访问Xvfb创建的虚拟桌面环境。 sudo apt-get install xvfb sudo ap…

Ciallo~(∠・ω・ )⌒☆第十九篇 mysql windows、Ubuntu安装与远程连接配置

一、安装windows版本的mysql &#xff08;一&#xff09;、安装mysql 1. 2. 3. 4. 5. &#xff08;二&#xff09;、测试mysql 这些步骤完成后记得去配置环境变量&#xff0c;path为mysql的安装目录这里我选择的是默认路径&#xff1a; C:\Program Files\MySQL\MySQL Serve…

零基础学习Redis(5) -- redis单线程模型介绍

前面我们提到过&#xff0c;redis是单线程的&#xff0c;这期我们详细介绍一下redis的单线程模型 1. redis单线程模型 redis只使用一个线程处理所有的请求&#xff0c;并不是redis服务器进程内部只有一个线程&#xff0c;其实也存在多个线程&#xff0c;只不过多个线程是在处…

MySQL常用函数、语法案例

本人MySQL5.7版本 表结构 假设有一个名为 order_summary 的表&#xff0c;其字段如下&#xff1a; order_id (INT): 订单的唯一标识符 customer_id (VARCHAR): 顾客的唯一标识符 order_date (DATETIME): 订单创建时间 total_amount (DECIMAL): 订单总金额 payment_status (E…

贪心+多维度dp

前言&#xff1a;处理简单版本的时候&#xff0c;想到了贪心&#xff0c;以及暴力求解顺便剪枝一下&#xff0c;要注意边界问题 haed版本的时候&#xff0c;完全行不通了&#xff0c;m的范围到了200&#xff0c;这是不可以暴力求解的 但是我不知道如何定义状态转移方程&#…

Hutool糊涂包JSON相关方法汇总

目录 1. JSON 对象 (JSONObject) 的创建 2. 向 JSONObject 添加键值对 3. 从 JSONObject 获取值 4. JSON 对象与字符串之间的转换 5. JSON 对象与 Java Bean&#xff08;POJO&#xff09;之间的转换 6. JSON 数组 (JSONArray) 的使用 7. JSON 数组与 Java List 之间的转…

Unity的UI设计

目录 创建和布局 布局与交互 性能优化 最佳实践 学习资源 Unity UI Toolkit与uGUI和IMGUI之间的具体区别和适用场景是什么&#xff1f; Unity UI Toolkit uGUI IMGUI 如何在Unity中实现响应式UI设计以适应不同设备尺寸&#xff1f; Unity UI性能优化的最新技术和方法…

8.MySQL知识巩固-牛客网练习题

目录 SQL228 批量插入数据 描述 SQL202 找出所有员工当前薪水salary情况 描述 示例1 SQL195 查找最晚入职员工的所有信息描述 示例1 SQL196 查找入职员工时间排名倒数第三的员工所有信息描述 SQL201查找薪水记录超过15条的员工号emp_no以及其对应的记录次数t 描述 SQL…

后端Web之数据库多表设计

1.概述 项目开发中,在进行数据库表结构设计时&#xff0c;会根据业务需求及业务模块之间的关系&#xff0c;分析并设计表结构&#xff0c;由于业务之间相互关联&#xff0c;所以各个表结构之间也存在着各种联系,基本上分为三种:一对多、多对多、一对一。 数据库的多表设计是关…

JavaWeb——MVC架构模式

一、概述: MVC(Model View Controller)是软件工程中的一种 软件架构模式 &#xff0c;它把软件系统分为模型、视图和控制器三个基本部分。用一种业务逻辑、数据、界面显示分离的方法组织代码&#xff0c;将业务逻辑聚集到一个部件里面&#xff0c;在改进和个性化定制界面及用户…

字符串专题——字符串相乘

1、题目解析 2、算法解析 1、解法一 使用模拟的方法&#xff1a;模拟小学的列竖式运算 细节1&#xff1a;高位相乘的时候要补上“0” 细节2&#xff1a;处理前导“0” 细节3&#xff1a;注意计算结果的顺序 2、解法二 对比解法一做优化->无进位相乘然后相加&#xff0…

Huawei Matebook e 2022 安装 archlinux 双系统

本文同步发布于我的网站 安装之前 wifi 名称修改为英文数字的&#xff0c;以防之后没法联网 准备好 U 盘并使用 GPT 分区表写入最新的 arch 镜像。 基础安装 开机按 F2 进入 UEFI/BIOS 设置&#xff0c;将 Secure Boot&#xff08;安全启动&#xff09;关闭&#xff0c;按…

AI学习记录 - transformers 的 linear 词映射层的详细分析, CrossEntropyLoss 函数解析

创作不易&#xff0c;有用的话点个赞。。。。。。 1. 假设条件 词汇表&#xff1a;假设词汇表包含四个词汇&#xff1a;[token_0, token_1, token_2, token_3]。 模型的输出概率分布&#xff1a;模型的输出经过 Softmax 转换后&#xff0c;得到概率分布&#xff1a;[0.1,0.5,…

JavaScript - Api学习 Day1(WebApi、操作DOM对象)

应用编程接口 (API) 是编程语言中提供的结构&#xff0c;允许开发者更轻松地创建复杂的功能。、 webapi 是一套 操作网页内容(DOM) 与 浏览器窗口(BOM) 的对象Js由ECMAScript、DOM、BOM三个部分组成。 文章目录 零、前言0.1 变量声明 壹、WebAPI的认识1.1 作用1.2 什么是DOM1…

【AI大模型】解锁AI智能:从注意力机制到Transformer,再到BERT与GPT的较量

文章目录 前言一、揭秘注意力机制&#xff1a;AI的焦点如何塑造智能1.什么是注意力机制&#xff1f;2.为什么需要注意力机制&#xff1f; 二、变革先锋&#xff1a;Transformer的突破与影响力1.什么是Transformer&#xff1f;2.为什么Transformer如此重要&#xff1f; 三、路径…

《给所有人的生成式 AI 课》学习笔记(一)

前言 本文是吴恩达&#xff08;Andrew Ng&#xff09;的视频课程《Generative AI for Everyone》&#xff08;给所有人的生成式 AI 课&#xff09;的学习笔记。由于原课程为全英文视频课程&#xff08;时长约 3 个小时&#xff09;&#xff0c;且国内访问较慢&#xff0c;阅读…

零基础转行学网络安全怎么样?

在当今数字化飞速发展的时代&#xff0c;网络安全已成为备受瞩目的领域。那么&#xff0c;对于零基础的人来说&#xff0c;转行学习网络安全究竟怎么样呢? 网络安全行业正处于蓬勃发展的阶段。随着互联网的普及和信息技术的不断进步&#xff0c;网络安全问题日益凸显。政企单位…

本地私有化部署PDF处理神器Stirling PDF并实现无公网IP远程在线访问

文章目录 前言1. 安装Docker2. 本地安装部署StirlingPDF3. Stirling-PDF功能介绍4. 安装cpolar内网穿透5. 固定Stirling-PDF公网地址 前言 本篇文章我们将在Linux上使用Docker在本地部署一个开源的PDF工具——Stirling PDF&#xff0c;并且结合cpolar的内网穿透实现公网随时随…