5大常见高并发限流算法选型浅析

高并发场景下,如何确保系统稳定运行,成为了每一个开发工程师必须面对的挑战。**你是否曾因系统崩溃、请求超时或资源耗尽而头疼不已?**高并发限流算法或许能帮你解决这些难题。

在处理高并发请求时,应该如何选择合适的限流算法呢? 以下,我们将深入分析五种常见的高并发限流算法,帮助你做出更合适的技术选型。

在现代高并发系统中,随着用户访问量的激增和业务需求的不断扩展,限流作为一种至关重要的保护机制,被广泛应用于防止系统过载,确保系统的稳定性和可用性。

本文将深入剖析几种常见的限流算法,探讨它们的原理、优缺点并给出代码实例,帮助读者更好地理解和应用这些算法,从而在实际项目中构建更加高效、稳定的系统。

随着互联网应用的快速发展和用户流量的增加,高并发场景下的限流变得越来越重要。尤其在电商、直播、支付等行业,高并发请求的控制直接决定了用户体验和系统的稳定性。选择合适的限流算法,能有效减少服务器的负载和资源的过度占用,从而保证系统的稳定运行。

01 固定窗口算法(Fixed Window Algorithm)

图片

固定窗口算法将时间划分为固定大小的窗口(如1min),在每个窗口内允许一定数量的请求。每当请求到达时,系统会检查当前窗口内的请求数量,如果未超过限制,则允许请求;否则,拒绝请求。

class FixedWindowRateLimiter {
public:
    FixedWindowRateLimiter(int max_requests_per_win, std::chrono::seconds window_size)
        : max_requests_per_win_(max_requests_per_win), window_size_(window_size), request_count_(0) {
        window_start_time_ = std::chrono::steady_clock::now();
    }

    bool TryAcquire(int request_size) {
        std::lock_guard<std::mutex> lock(mtx_);
        auto now = std::chrono::steady_clock::now();

        // 如果当前时间在窗口内
        if (now - window_start_time_ < window_size_) {
            // 检查请求数量是否超过限制
            if (request_count_ + request_size <= max_requests_per_win_) {
                request_count_ += request_size; // 增加请求计数
                return true; // 允许请求
            } else {
                return false; // 超过最大请求数
            }
        } else {
            // 重置窗口
            window_start_time_ = now;
            request_count_ = request_size; // 重置请求计数为当前请求数量
            return true; // 允许请求
        }
    }

private:
    int max_requests_per_win_; // 最大请求数
    std::chrono::seconds window_size_; // 窗口大小
    int request_count_; // 当前请求计数
    std::chrono::steady_clock::time_point window_start_time_; // 窗口开始时间
    std::mutex mtx_; // 互斥锁
};
  • 算法简介:通过设定固定的时间窗口,在窗口内计算请求的数量,如果超出限制,则拒绝请求。每当时间窗口到期,计数器重置。
  • 适用场景:适用于请求速率较稳定、对时间窗口的要求不高的场景。
  • 案例:某银行的API接口,每秒钟限制5次请求,超过5次即拒绝。当一个时间窗口(如1秒)结束时,计数器会重置。

优点

  • 实现简单,非常容易理解。

  • 适用于请求速率相对稳定的场景。

缺点

  • 在短时间流量突发时,将会有大量失败,无法平滑流量。

  • 有窗口边际效应:在窗口切换时,可能会出现短时间内请求激增的情况,导致系统过载。

02 滑动窗口算法(Sliding Window Algorithm)

图片

滑动窗口算法是对固定窗口算法的改进,它将时间窗口划分为多个小桶,并为每个小桶维护一个独立的请求计数器。当请求到达时,算法会根据请求的时间戳将其放入相应的小桶中,并检查整个滑动窗口内的请求总数是否超过限制。随着时间的推移,滑动窗口会不断向右滑动,丢弃最旧的小桶并添加新的小桶。

class SlidingWindowRateLimiter {
public:
    SlidingWindowRateLimiter(int max_requests_per_win, std::chrono::seconds bucket_size, int num_buckets)
        : max_requests_per_win_(max_requests_per_win), bucket_size_(bucket_size), num_buckets_(num_buckets) {
        request_counts_.resize(num_buckets, 0);
        last_bucket_time_ = std::chrono::steady_clock::now();
    }

    bool TryAcquire(int request_size) {
        std::lock_guard<std::mutex> lock(mtx_);
        auto now = std::chrono::steady_clock::now();
        UpdateBuckets_(now);

        int total_requests = 0;
        for (int count : request_counts_) {
            total_requests += count;
        }

        if (total_requests + request_size <= max_requests_per_win_) {
            request_counts_[current_bucket_index_] += request_size; // 增加当前桶的请求计数
            return true; // 允许请求
        } else {
            return false; // 超过最大请求数
        }
    }

private:
    void UpdateBuckets_(std::chrono::steady_clock::time_point now) {
        auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - last_bucket_time_);
        int buckets_to_update = static_cast<int>(elapsed / bucket_size_);

        if (buckets_to_update > 0) {
            for (int i = 0; i < std::min(num_buckets_, buckets_to_update); ++i) {
                auto next_bucket_index = (current_bucket_index_ + 1) % num_buckets_;
                request_counts_[next_bucket_index] = 0; // 移除最旧的桶
                current_bucket_index_ = next_bucket_index; // 移动到下一个桶
            }
            last_bucket_time_ += buckets_to_update * bucket_size_; // 更新最后桶的时间
        }
    }

    int max_requests_per_win_; // 最大请求数
    std::chrono::seconds bucket_size_; // 每个小桶的大小
    int num_buckets_; // 小桶的数量
    std::vector<int> request_counts_; // 每个小桶的请求计数
    std::mutex mtx_; // 互斥锁
    std::chrono::steady_clock::time_point last_bucket_time_; // 上一个桶的时间
    int current_bucket_index_ = 0; // 当前桶的索引
};

  • 算法简介:滑动窗口计数器算法是对固定窗口计数器算法的改进。它将时间窗口滑动分段,每个小段都进行计数,精确度更高。相比固定窗口,它更加平滑,不会出现突如其来的拒绝。
  • 适用场景:适用于对请求的实时性和稳定性要求较高的场景。
  • 案例:某社交平台在API访问上使用了滑动窗口算法,可以避免短时间内请求量突然增大时的系统压力,使请求处理更加平稳。

优点

  • 相比固定窗口算法可以更细粒度地控制流量。

  • 减缓了固定窗口算法中的窗口边际效应。

缺点

  • 在短时间流量突发时,将会有大量失败,无法平滑流量。

03 滑动日志算法(Sliding Log Algorithm)

图片

滑动日志算法通过记录每个请求的时间戳来控制请求速率。当一个请求到达时,系统会检查最近一段时间内的请求记录,计算请求速率是否超过限制。如果超过,则拒绝请求;否则,处理请求并记录当前请求的时间戳。

class SlidingLogRateLimiter {
public:
    SlidingLogRateLimiter(int max_requests_per_win, std::chrono::seconds window_size)
        : max_requests_per_win_(max_requests_per_win), window_size_(window_size) {}

    bool TryAcquire(int request_size) {
        std::lock_guard<std::mutex> lock(mtx_);
        auto now = std::chrono::steady_clock::now();
        CleanUp_(now);

        int total_requests = 0;
        for (const auto& timestamp : request_log_) {
            total_requests += timestamp.second;
        }

        if (total_requests + request_size <= max_requests_per_win_) {
            request_log_.emplace_back(now, request_size); // 记录当前请求
            return true; // 允许请求
        } else {
            return false; // 超过最大请求数
        }
    }

private:
    void CleanUp_(std::chrono::steady_clock::time_point now) {
        auto expiration_time = now - window_size_;
        while (!request_log_.empty() && request_log_.front().first < expiration_time) {
            request_log_.pop_front(); // 移除过期的请求记录
        }
    }

    int max_requests_per_win_; // 最大请求数
    std::chrono::seconds window_size_; // 窗口大小
    std::deque<std::pair<std::chrono::steady_clock::time_point, int>> request_log_; // 请求日志
    std::mutex mtx_; // 互斥锁
};

优点

  • 可以非常精确地控制请求速率。

缺点

  • 在短时间流量突发时,将会有大量失败,无法平滑流量。

  • 由于每一次成功请求都要被记录,所以会有较大额外的内存开销。

04 漏桶算法(Leaky Bucket Algorithm)

图片

漏桶算法将请求看作水滴,将请求处理过程看作水从漏桶底部中流出。系统以恒定速率处理请求(即漏桶的漏水速率),当一个请求到达时,如果漏桶未满,则请求被放入漏桶中等待处理;如果漏桶已满,则请求被拒绝。

class Task {
public:
    virtual int GetLoad() = 0;
    virtual void Run() = 0;
}

class LeakyBucketRateLimiter {
public:
    LeakyBucketRateLimiter(int capacity, int leak_rate)
        : capacity_(capacity), leak_rate_(leak_rate), current_water_(0) {
        last_leak_time_ = std::chrono::steady_clock::now();
    }

    bool TryRun(const TaskPtr& task) {
        std::lock_guard<std::mutex> lock(mtx_);
        Leak();

        if (current_water_ + task.GetLoad() <= capacity_) {
            current_water_ += task.GetLoad(); // 将请求放入漏桶
            heap_timer_.Schedule(task, current_water_ / leak_rate); // 定时器在该任务的水漏完后将会执行task的Run方法
            return true; // 允许请求
        } else {
            return false; // 漏桶已满,请求被拒绝
        }
    }

private:
    void Leak() {
        auto now = std::chrono::steady_clock::now();
        auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - last_leak_time_);
        int leaked_water = static_cast<int>(elapsed.count()) * leak_rate_;

        if (leaked_water > 0) {
            current_water_ = std::max(0, current_water_ - leaked_water); // 减少水量
            last_leak_time_ = now; // 更新最后漏水时间
        }
    }

    int capacity_; // 漏桶的容量
    int leak_rate_; // 漏水速率(每秒漏掉的水量)
    int current_water_; // 当前水量
    HeapTimer heap_timer_; // 一个任务执行的定时器
    std::mutex mtx_; // 互斥锁
    std::chrono::steady_clock::time_point last_leak_time_; // 上次漏水的时间
};

​​​​​​​

  • 算法简介:漏桶算法和令牌桶算法类似,但它更加严格。漏桶算法通过固定的出水速率来处理请求,桶满后会丢弃新请求。适合用来处理请求的平滑速率控制。
  • 适用场景:适用于有稳定速率处理的场景,比如实时数据处理,确保处理流量的一致性。
  • 案例:一个短视频平台可以使用漏桶算法来平稳处理视频上传流量,确保不会因为突发的高并发导致服务器崩溃。

优点

  • 能提供非常平稳的流量。

  • 削峰填谷,有一定的应对流量突发能力(桶的大小)。

缺点

  • 控制比较刻板,弹性能力较弱。

  • 在并发时候会产生额外的延迟等待开销(比如限制流量为1qps,两个请求同时到达,必然有其中一个请求需要等1s后才能服务)。

05 令牌桶算法(Token Bucket Algorithm)

图片

令牌桶算法使用一个桶来存储令牌,以固定速率向桶中添加令牌。每当请求到达时,会先检查桶中是否有令牌,如果有,则允许请求并消耗相应令牌;如果没有,则拒绝请求。

class TokenBucketRateLimiter {
public:
    TokenBucketRateLimiter(int capacity, int refill_rate)
        : capacity_(capacity), refill_rate_(refill_rate), current_tokens_(0) {
        last_refill_time_ = std::chrono::steady_clock::now();
    }

    bool TryAcquire(int request_size) {
        std::lock_guard<std::mutex> lock(mtx_);
        Refill_();

        if (current_tokens_ >= request_size) {
            current_tokens_ -= request_size; // 消耗令牌
            return true; // 允许请求
        } else {
            return false; // 令牌不足,请求被拒绝
        }
    }

private:
    void Refill_() {
        auto now = std::chrono::steady_clock::now();
        auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - last_refill_time_);
        current_tokens_ = std::min(capacity_, current_tokens_ + static_cast<int>(elapsed.count()) * refill_rate_); // 更新令牌数量
        last_refill_time_ = now; // 更新最后补充时间
    }

    int capacity_; // 令牌桶的容量
    int refill_rate_; // 令牌补充速率(每秒补充的令牌数量)
    int current_tokens_; // 当前令牌数量
    std::mutex mtx_; // 互斥锁
    std::chrono::steady_clock::time_point last_refill_time_; // 上次补充令牌的时间
};

​​​​​​​

  • 算法简介:令牌桶算法是一种典型的限流算法,通过控制令牌的生成速率和桶的容量来限制请求数量。当令牌不足时,请求会被延迟或拒绝。
  • 适用场景:适合流量突发性较强的场景。能够平滑处理高峰流量,避免瞬时请求量过高导致系统崩溃。
  • 案例:例如,一家电商平台的促销活动中,用户请求流量很难控制,使用令牌桶算法,能够保证每秒有一定数量的请求被允许进入系统,避免流量瞬间激增时导致系统崩溃。

优点

  • 能够处理突发流量,避免系统瞬间过载。

  • 灵活性高,可以通过调整令牌生成速率和桶容量来控制流量。

缺点

  • 实现相对复杂。

总结

想要在高并发系统中使用这些算法来优化系统性能?使用一些优秀的云平台服务可以帮助你实现灵活的限流与调度,如阿里云的云数据库API Gateway,为你的系统提供强有力的支撑

每种限流算法都有其适用的场景和优缺点。在选择限流算法时,需要根据具体的业务需求和系统特性进行权衡。通过合理选择和组合这些算法,可以有效地保护系统免受过载的影响

在面对高并发压力时,选择合适的限流算法至关重要。不同的业务场景需要根据流量特点选择最佳的限流策略,以确保系统高效、稳定地运行。希望本文的分析能帮助你在面对复杂的流量控制时做出明智的选择。

“高并发的世界里,限流算法不仅是保障系统稳定的利器,更是确保用户体验不被破坏的守护者。”

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

高等数学学习笔记 ☞ 无穷小比较与等价无穷小替换

1. 无穷小比较 1. 本质&#xff1a;就是函数的极限趋于0时的速度&#xff0c;谁快谁慢的问题。 2. 定义&#xff1a;若是在同一自变量的变化过程中的无穷小&#xff0c;且&#xff0c;则&#xff1a; ①&#xff1a;若&#xff0c;则称是比的高阶无穷小&#xff0c;记作&…

配置嵌入式服务器

一、如何定制和修改Servlet容器的相关配置 修改和server有关的配置&#xff08;ServerProperties&#xff09; server.port8081 server.context‐path/tx server.tomcat.uri-encodingUTF-8二、注册servlet三个组件【Servlet、Filter、Listener】 由于SpringBoot默认是以jar包…

大模型系列18-AI Agents

什么是AI Agents Al Agent智能体&#xff0c;是指一种能够模拟人类思考和行为来自动执行任务&#xff0c;以解决复杂问题的程序或系统 架构图 思考->行动->观测 思考依赖记忆以及规划决策&#xff0c;行动依赖工具&#xff0c;观测依赖感知 举例 长沙今天白天和晚上的…

springCloud 脚手架项目功能模块:Java分布式锁

文章目录 引言分布式锁产生的原因:集群常用的分布式锁分布式锁的三种实现方式I ZooKeeper 简介zookeeper本质上是一个分布式的小文件存储系zookeeper特性:全局数据一致性ZooKeeper的应用场景分布式锁(临时节点)II 基于ZooKeeper 实现一个排他锁创建锁获取锁释放锁Apache Zo…

Appium(二)--- ADB命令操作

一、ADB概述 什么是ADB?ADB全称Android Debug Bridge&#xff0c;起到调试桥的作用&#xff0c;是一个客户端-服务器端程序。其中客户端是用来操作的操作&#xff0c;服务端是Android设备。ADB也是Android SDK的一个工具&#xff0c;可以直接操作管理Android模拟器或者真实的…

基于SpringBoot在线竞拍平台系统功能实现十五

一、前言介绍&#xff1a; 1.1 项目摘要 随着网络技术的飞速发展和电子商务的普及&#xff0c;竞拍系统作为一种新型的在线交易方式&#xff0c;已经逐渐深入到人们的日常生活中。传统的拍卖活动需要耗费大量的人力、物力和时间&#xff0c;从组织拍卖、宣传、报名、竞拍到成…

Android GameActivity(NativeActivity)读写文件

最近研究native android相关内容&#xff0c;其中最棘手的就是文件读写问题&#xff0c;最主要的是相关的文档很少。这里写下我所知道的方法。 由于本人使用的是Android14[arm64-v8a]版本的设备,能访问的路径相当有限&#xff0c;如果想要访问更多的路径&#xff0c;就不得不申…

conda指定路径安装虚拟python环境

DataBall 助力快速掌握数据集的信息和使用方式&#xff0c;会员享有 百种数据集&#xff0c;持续增加中。 需要更多数据资源和技术解决方案&#xff0c;知识星球&#xff1a; “DataBall - X 数据球(free)” -------------------------------------------------------------…

计算机网络练习题

学习这么多啦&#xff0c;那就简单写几个选择题巩固一下吧&#xff01; 1. 在IPv4分组各字段中&#xff0c;以下最适合携带隐藏信息的是&#xff08;D&#xff09; A、源IP地址 B、版本 C、TTL D、标识 2. OSI 参考模型中&#xff0c;数据链路层的主要功能是&#xff08;…

【算法不挂科】算法期末考试【选择题专项练习】<多单元汇总>

前言 大家好吖&#xff0c;欢迎来到 YY 滴算法不挂科系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C的老铁 主要内容含&#xff1a; 一.选择题 【1】算法绪论 1.算法与程序的区别是( ) A.输出 B.输入 C.确定性 D.有穷性 D 2.算法复杂度分析的两种基本方法…

MIPI_DPU 综合(DPU+MIPI+Demosaic+VDMA 通路)

目录 1. 简介 2. 创建 Platform 2.1 Block Design 2.1.1 DPU PFM Lite 2.1.2 DPU prj 2.1.3 DPU MIPI Platform 2.2 pin 约束 2.2.1 GPIO 约束 2.2.2 IIC 约束 2.1.3 DPHY 约束 3. 报错总结 3.1 AXI_M 必须顺序引用 3.2 DPU 地址分配错误 4. Design Example 4.…

李宏毅机器学习课程笔记01 | 1.Introduction of Machine/Deep Learning

笔记是在语雀上面做的&#xff0c;粘贴在CSND上可能存在格式错误 机器学习的本质就是借助机器寻找一个转换函数 根据函数的输出类型&#xff0c;可以将机器学习进行分类 regression 回归任务&#xff1a;函数输出时一个数值classification 分类任务&#xff1a;人类设定好选项…

《Rust权威指南》学习笔记(五)

高级特性 1.在Rust中&#xff0c;unsafe是一种允许绕过Rust的安全性保证的机制&#xff0c;用于执行一些Rust默认情况下不允许的操作。unsafe存在的原因是&#xff1a;unsafe 允许执行某些可能被 Rust 的安全性检查阻止的操作&#xff0c;从而可以进行性能优化&#xff0c;如手…

云备份项目--客户端编写

文章目录 10. 客户端工具类10.1 整体的类10.2 测试 11 客户端数据管理类11.1 整体的类11.2 测试 12. 客户端业务处理12.1 整体的类 完整的代码–gitee链接 10. 客户端工具类 10.1 整体的类 在windows平台下进行开发&#xff0c;Util.hpp实际上是客户端FileUtil.hpp和JsonUtil…

开发培训-慧集通(iPaaS)集成平台脚本开发Groovy基础培训视频

‌Groovy‌是一种基于Java虚拟机&#xff08;JVM&#xff09;的敏捷开发语言&#xff0c;结合了Python、Ruby和Smalltalk的许多强大特性。它旨在提高开发者的生产力&#xff0c;通过简洁、熟悉且易于学习的语法&#xff0c;Groovy能够与Java代码无缝集成&#xff0c;并提供强大…

蓝桥杯(Java)(ing)

Java前置知识 输入流&#xff1a; &#xff08;在Java面向对象编程-CSDN博客里面有提过相关知识------IO流&#xff09; // 快读快写 static BufferedReader in new BufferedReader(new InputStreamReader(System.in)); static BufferedWriter out new BufferedWriter(new…

【2025最新计算机毕业设计】基于SpringBoot+Vue智慧养老医护系统(高质量源码,提供文档,免费部署到本地)【提供源码+答辩PPT+文档+项目部署】

作者简介&#xff1a;✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流。✌ 主要内容&#xff1a;&#x1f31f;Java项目、Python项目、前端项目、PHP、ASP.NET、人工智能…

文献分享集:跨模态的最邻近查询RoarGraph

文章目录 1. \textbf{1. } 1. 导论 1.1. \textbf{1.1. } 1.1. 研究背景 1.2. \textbf{1.2. } 1.2. 本文的研究 1.3. \textbf{1.3. } 1.3. 有关工作 2. \textbf{2. } 2. 对 OOD \textbf{OOD} OOD负载的分析与验证 2.1. \textbf{2.1. } 2.1. 初步的背景及其验证 2.1.1. \textbf{2…

【读书笔记·VLSI电路设计方法解密】问题35:ASIC设计流程的两个主要方面是什么

毫无疑问,ASIC设计流程是一个复杂的系统,包含了许多商业CAD工具以及许多内部开发的工具或脚本。然而,无论流程中集成了多少工具或脚本,ASIC设计流程的核心目标始终可以归结为两个关键点:创建和检查。 创建过程指的是生成硬件的活动,例如RTL编码、逻辑综合以及布局布线。…

域上的多项式环,整除,相通,互质

例1.已知 (R,,x)为域&#xff0c;请选出正确的说法:(A)(R,,x)也是整区; ABCD (B)R中无零因子; C)R在x运算上满足第一、二、三指数律; (D)R只有平凡理想; (E)R只有平凡子环。 域的特征&#xff1a; 域中&#xff0c;非0元素的加法周期 思考、在模7整数环R,中&#xff0c;…