利用固定窗口计数算法限流,精准控制第三方 API 调用频率

news2024/11/14 17:55:43

文章目录

    • 使用场景
    • 使用固定窗口计数算法管理调用频率
    • 使用测试
    • 一秒钟执行五次
    • 适用场景

使用场景

在调用第三方 API 时,我们通常会在 API 文档中看到对接口访问频率的限制。为了确保不超出这些限制,使用限流算法来控制 API 调用频率是至关重要的。

在这里插入图片描述

在作为 API 的使用者时,我们通常需要参考并使用限流算法来控制 API 调用的频率,确保不超过提供方设定的访问限制。例如,我们可以使用固定窗口计数算法来有效管理调用频率。

如果你是 API 的提供者,常用的限流算法则会有所不同。令牌桶算法及其变体通常被广泛应用,它们不仅能有效控制请求速率,还允许一定程度的突发流量,从而提升系统的灵活性和可靠性。

在这里插入图片描述

使用固定窗口计数算法管理调用频率

LimitUtil 实现的限流机制属于 固定窗口计数算法(Fixed Window Counter) 的一种变体。

代码中下面两个参数至关重要,根据自己的需求修改。

  1. N:时间窗口内最多允许的请求次数。这个参数直接决定了在指定时间窗口内可以处理的最大请求量。如果超过这个数量,多余的请求将被延迟或拒绝。
  2. bucket:时间窗口的大小。这个参数定义了时间窗口的长度(通常以毫秒为单位)。它决定了在多长时间内会统计和限制请求次数。

通过合理设置这两个参数,可以有效控制 API 的调用频率,避免超出限制。

代码如下:

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class LimitUtil {
    // 用于循环访问 requestTimeList 的索引,volatile 保证多线程之间的可见性
    private static volatile int seq = 0;

    // 时间窗口大小,单位为毫秒,这里设置为1000毫秒(即1秒)
    private static final int bucket = 1000;

    // 用于存储最近 N 次请求的时间戳列表,volatile 保证线程安全
    private static volatile List<Long> requestTimeList;

    // 每个 bucket 时间窗口内最多允许的请求次数,这里按需求自定义
    private static final int N = 1;

    static {
        // 初始化 requestTimeList,为长度为 N 的列表,所有值均为 0L
        requestTimeList = new ArrayList<>(Collections.nCopies(N, 0L));
    }

    // 私有构造函数,防止实例化该工具类
    private LimitUtil() {
    }

    /**
     * 限流实现,使用 synchronized 修饰,表示对整个类上锁,保证线程安全
     *
     * @throws InterruptedException 如果线程被中断,则抛出该异常
     */
    public static synchronized void tryBeforeRun() throws InterruptedException {
        // 获取当前系统时间(毫秒)
        long now = System.currentTimeMillis();

        // 计算当前请求时间与 seq 索引处的上一次请求时间的间隔
        long interval = now - requestTimeList.get(seq);

        if (interval < 0) {
            // 如果当前时间早于上次请求时间(即 interval < 0),需要等待到上次请求的下一个时间点再执行
            // 计算等待时间,并让线程等待
            Thread.sleep(bucket - interval);

            // 递归调用自己,确保限流逻辑执行
            tryBeforeRun();
        }

        if (interval < bucket) {
            // 如果时间间隔小于 bucket 时间窗口,说明请求太快,需要延迟执行
            requestTimeList.set(seq, requestTimeList.get(seq) + bucket);
            Thread.sleep(bucket - interval);
        } else {
            // 否则,更新当前索引处的请求时间为当前时间
            requestTimeList.set(seq, now);
        }

        // 更新 seq,指向下一个索引位置,采用取模操作实现循环
        seq = (seq + 1) % requestTimeList.size();
    }
}

使用测试

我下面使用 100 个线程并发调用 API,并在调用 API 之前引入限流方法。通过打印每次 API 调用的时间戳,可以观察并验证限流算法的效果以及 API 调用频率的变化。

    @Test
    public void testLimitUtil() {
        // 100个线程并发测试
        ThreadUtil.concurrencyTest(100, () -> {
            try {
                LimitUtil.tryBeforeRun();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.api();
        });
    }

    private void api() {
        long now = System.currentTimeMillis();
        System.out.println("API 调用时间: " + now / 1000 + "秒,由线程 " + Thread.currentThread().getName() + " 执行");
        // 模拟业务逻辑的处理
    }

设定的 N = 1 和 bucket = 1000 的参数配置。

API 调用时间: 1724903993秒,由线程 hutool-4 执行
API 调用时间: 1724903994秒,由线程 hutool-9 执行
API 调用时间: 1724903995秒,由线程 hutool-7 执行
API 调用时间: 1724903996秒,由线程 hutool-6 执行
API 调用时间: 1724903997秒,由线程 hutool-8 执行
API 调用时间: 1724903998秒,由线程 hutool-2 执行
API 调用时间: 1724903999秒,由线程 hutool-5 执行
API 调用时间: 1724904000秒,由线程 hutool-100 执行
更多的就不展示了。

通过测试结果,可以确认:
限流机制有效:API 调用频率严格控制在每秒一次,符合设定的参数。
无并发问题:每个 API 调用的时间戳显示在不同的秒数,说明限流机制成功避免了多个线程在同一时间窗口内同时调用 API。

一秒钟执行五次

一秒钟执行五次只需要把N调整为5即可。

    // 每个 bucket 时间窗口内最多允许的请求次数,这里按需求自定义
    private static final int N = 5;

测试部分结果如下,符合预期,一秒钟执行最多执行5次api。

API 调用时间: 1724910227秒,由线程 hutool-25 执行
API 调用时间: 1724910227秒,由线程 hutool-23 执行
API 调用时间: 1724910227秒,由线程 hutool-10 执行
API 调用时间: 1724910227秒,由线程 hutool-24 执行
API 调用时间: 1724910227秒,由线程 hutool-5 执行

API 调用时间: 1724910228秒,由线程 hutool-8 执行
API 调用时间: 1724910228秒,由线程 hutool-38 执行
API 调用时间: 1724910228秒,由线程 hutool-3 执行
API 调用时间: 1724910228秒,由线程 hutool-9 执行
API 调用时间: 1724910228秒,由线程 hutool-4 执行

代码逻辑演示如下。

在这里插入图片描述

适用场景

这种算法适用于简单的场景,比如在1秒内只允许某个操作执行一次。它的实现简单,适合于限制在某段时间内的请求次数,但可能会在窗口边界处出现短时间内的突发流量问题(即"临界点问题"),这也是固定窗口计数算法的一般特点。

临界点问题验证

    public static void main(String[] args) throws InterruptedException {
        testCriticalPoint();
    }

    public static void testCriticalPoint() throws InterruptedException {
        // 第一个请求,在接近时间窗口的末尾发起
        Thread.sleep(950); // 等待950毫秒,使其接近1秒窗口的结束
        System.out.println("第一次请求: " + System.currentTimeMillis() / 1000 + " 秒");
        LimitUtil.tryBeforeRun();

        // 第二个请求,在下一个时间窗口的开始发起
        Thread.sleep(50); // 等待50毫秒,跨越到下一个时间窗口
        System.out.println("第二次请求: " + System.currentTimeMillis() / 1000 + " 秒");
        LimitUtil.tryBeforeRun();
    }

运行结果:

第一次请求: 1724909724 秒
第二次请求: 1724909724 秒

在上述测试中,LimitUtil 在这两个请求之间没有进行限流,这说明在临界点时 LimitUtil 允许超出预期的请求数量,临界点问题确实存在。

分析:
临界点问题发生的原因在于,固定窗口计数算法只关注窗口内的请求数量,而没有考虑跨越窗口边界时的请求情况。
在这个例子中,虽然两个请求是在不同的时间窗口发起的,但由于时间窗口的计算是以固定的时间单位为基础(例如以秒为单位),因此可能会在窗口边界附近发生短时间内处理多个请求的情况。

解决方案:
为了避免临界点问题,可以考虑使用以下方法:

**滑动窗口计数算法(**Sliding Window Counter):
通过对请求的时间戳进行更精细的记录,并在窗口内的每个时刻统计请求数量,避免在窗口边界处出现突发流量。

令牌桶算法(Token Bucket Algorithm):
通过生成和消耗令牌来控制请求速率,允许一定的突发流量,同时控制平均请求速率,平滑处理流量。

滑动窗口平均算法(Sliding Window Rate Limiting):
将时间窗口进一步细分为更小的子窗口,计算多个子窗口的平均请求数量,避免在窗口边界处出现流量峰值。

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

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

相关文章

Spike-in:微生态16S扩增子绝对定量重磅上线!

16S扩增子测序是一种广泛应用于微生物群落分析的技术&#xff0c;主要用于研究环境样本中微生物的种类、丰度及其生态关系。 然而&#xff0c;传统的16S扩增子测序通常只能提供相对丰度数据&#xff0c;无法准确反映样本中各微生物的绝对数量&#xff0c;导致在一定程度上掩盖…

论文理解【LLM-agent】—— 【Reflexion】Language Agents with Verbal Reinforcement Learning

文章链接&#xff1a;Reflexion: Language Agents with Verbal Reinforcement Learning代码&#xff1a;GitHub - noahshinn/reflexion发表&#xff1a;NIPS 2023领域&#xff1a;LLM agent一句话总结&#xff1a;传统强化学习 Agent 通过和环境交互进行试错学习&#xff0c;但…

python-比身高

题目描述 班上有n个同学。现在同学们排成了一队&#xff0c;每个同学都想知道在自己前面有多少个同学比自己高。现在告诉你班上同学们排好队后每个同学的身高&#xff0c;请告诉每个人在他们前面有多少人比他们高。输入&#xff1a; 输入共两行。 第一行一个整数n。 第二行n个整…

实战|任意用户漏洞挖掘分享

吉祥知识星球http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247485367&idx1&sn837891059c360ad60db7e9ac980a3321&chksmc0e47eebf793f7fdb8fcd7eed8ce29160cf79ba303b59858ba3a6660c6dac536774afb2a6330&scene21#wechat_redirect 《网安面试指南》…

变压器电压调节

电压调节是衡量变压器在不同负载条件下维持恒定次级电压的能力的标准&#xff0c;因为输出次级电压可能不是我们所期望的。 当变压器的初级绕组通电时&#xff0c;它会产生次级电压和电流&#xff0c;其量由变压器匝数比 (TR) 决定。如果单相变压器的降压匝数比为 2:1&#xf…

【论文阅读】一种针对多核神经网络处理器的窃取攻击(2020)

摘要 攻击者可以通过侧信道信息(Side-channel)完成模型窃取攻击[17]. [17] Hua W Z, Zhang Z R, Suh G E. Reverse Engineering Convolutional Neural Networks through Side-channel Information Leaks[C]. 2018 55th ACM/ESDA/IEEE Design Automation Conference (DAC), 2018…

Large Language Models(LLMs) Concepts

1、Introduction to Large Language Models(LLM) 1.1、Definition of LLMs Large: Training data and resources.Language: Human-like text.Models: Learn complex patterns using text data. The LLM is considered the defining moment in the history of AI. Some appl…

HMI触屏网关-VISION如何与Modbus TCP从机通信

上文&#xff1a;HMI触屏网关-VISION如何与Modbus RTU从机通信-CSDN博客 1. 硬件连接 Modbus TCP协议采用网口通信的方式&#xff0c;因此&#xff0c;只需要保证网关的LAN口IP和Modbus TCP从机的IP在同一网段即可。 Modbus TCP从机参数说明&#xff1a; 2. VISION创建Modbu…

怎么将ts格式转mp4?必须掌握的4种视频转换方法

当今&#xff0c;视频格式转换变得愈发重要。当我们面对不太常见的ts格式&#xff0c;想要将其转换为更通用的mp4时&#xff0c;掌握正确的转换方法尤为关键。今天&#xff0c;我们将分享4种实现ts格式转mp4的必备方法。每一种方法都有其独特优势&#xff0c;满足不同需求。 我…

027、架构_资源_GTM

系统级GTM:默认的GTM,当创建分片集群时,如果不创建实例级GTM,则会用系统级GTM 本章节主要介绍GTM 集群的新增、删除、配置、绑定等管理操作。 新增GTM集群 摘要新增GTM集群,与租户相绑定,可查看绑定租户与配置集群参数设置,租户可重绑定其他正常可用的GTM集群。 步骤1.…

windows 编译libx264报错问题之解决

编译过程参考&#xff1a;Win10环境下 编译 和 运行 x264_x.264下载使用教程-CSDN博客 一、gcc not found 在https://www.msys2.org/ 下载Mingw后&#xff0c;安装 pacman -S mingw-w64-x86_64-gcc 安装完成后&#xff0c;执行gcc -v提示找不到gcc 解决办法&#xff1a; …

迎接开学第一天!请查收这份2024开学必备好物清单!

新的学期正悄然来临&#xff0c;开学第一天校园里即将迎来一张张充满朝气的面孔。无论是重返课堂的老生还是满怀期待的新生&#xff0c;开学季总是充满了新的希望与挑战。为了帮助学生们更好地适应即将到来的学习生活&#xff0c;我们精心准备了这份2024开学必备好物清单。从提…

Java提高篇——Java 异常处理

阅读目录 异常的概念异常的体系结构Java 异常的处理机制异常处理的基本语法异常链自定义异常总结 回到顶部 异常的概念 异常是程序中的一些错误&#xff0c;但并不是所有的错误都是异常&#xff0c;并且错误有时候是可以避免的。 比如说&#xff0c;你的代码少了一个分号&…

FreeRTOS指南 -- 基础知识

裸机 / OS 裸机编程&#xff1a;单任务系统的方式&#xff0c;框架是在main( )函数中循环的处理&#xff0c;实时性差&#xff0c;在大循环中再紧急的函数没轮到只能等着&#xff0c;虽然在中断中处理一些紧急任务&#xff0c;但是在大型嵌入式系统中&#xff0c;这样的单任务系…

深入探索MySQL数据库结构设计:实战案例解析,打造高效、可扩展的数据存储方案

作者简介&#xff1a;我是团团儿&#xff0c;是一名专注于云计算领域的专业创作者&#xff0c;感谢大家的关注 座右铭&#xff1a; 云端筑梦&#xff0c;数据为翼&#xff0c;探索无限可能&#xff0c;引领云计算新纪元 个人主页&#xff1a;团儿.-CSDN博客 前言&#xff1a;…

BERT 高频面试题八股文——基础知识篇

基础知识 1. 问&#xff1a;请简述自然语言处理(NLP)的主要研究目标是什么&#xff1f; 答&#xff1a;NLP的主要研究目标是使计算机能够理解、解释和生成人类语言。 2. 问&#xff1a;什么是BERT模型&#xff0c;它为什么重要&#xff1f; 答&#xff1a;BERT是一种预训练…

超级会员卡积分收银系统源码,一站式解决方案,可以收银的小程序 带完整的安装代码包以及搭建部署教程

系统概述 超级会员卡积分收银系统源码&#xff0c;是一款专为零售行业设计的综合性管理软件系统。该系统以高效的收银功能为核心&#xff0c;结合会员管理、积分系统、商品管理、库存监控、报表分析等多个功能模块&#xff0c;旨在帮助商家实现线上线下一体化经营&#xff0c;…

海康二次开发学习笔记7-流程相关操作

流程相关操作 流程的相关操作包括选择路径,导入流程,导出流程,运行流程等. 在开始前,扩展优化一下写法,供其他地方重复调用. /// <summary>/// 消息显示区显示消息/// </summary>/// <param name"msg"></param>public void AddMsg(string …

【windows】windows 如何实现 ps aux | grep xxx -c 统计某个进程数的功能?

windows 如何实现 ps aux | grep xxx -c 统计某个进程数的功能&#xff1f; 在Windows中&#xff0c;要实现类似Linux中ps aux | grep xxx -c的功能&#xff0c;即统计某个特定进程的数量&#xff0c;可以使用PowerShell或命令提示符&#xff08;cmd.exe&#xff09;来实现。 …

osgearth添加地形夸张系数VerticalScale时报E0393:不允许指针指向不完整的类类型的解决方法

如下图1所示: 图1 error C2027: 使用了未定义类型“osgEarth::TerrainEngineNode” E0393:不允许指针指向不完整的类类型“osgEarth::TerrainEngineNode”