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,为你的系统提供强有力的支撑

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【重庆】《政务数字化应用费用测算规范》(T/CDCIDA 001—2023)-省市费用标准解读系列36

《政务数字化应用费用测算规范&#xff08;报批稿&#xff09;》于2023年11月18日实施&#xff0c;本文件按照GB/T 1.1-2020给出的规则起草&#xff0c;主要适用于重庆政务数字化应用项目的费用测算。我司基于专业第三方信息化项目造价机构角度&#xff0c;从标准创新点、定制软…

力扣【SQL连续问题】

180. 连续出现的数字 SELECT DISTINCT if(a.num b.num AND b.num c.num,a.num,null) AS ConsecutiveNums FROM Logs a LEFT OUTER JOIN Logs b ON a.id1 b.id LEFT OUTER JOIN Logs c ON a.id2 c.id WHERE if(a.num b.num AND b.num c.num,a.num,null) IS NOT NULL603. 连…

qml MouseArea详解

1. 概述 MouseArea 是 QML 中用于处理鼠标事件的一个非常重要的项&#xff08;Item&#xff09;。它允许开发者响应鼠标的点击、拖拽、悬停等操作。MouseArea 可以与任何 QML 项目&#xff08;如 Rectangle, Image, Text 等&#xff09;结合使用&#xff0c;用于实现用户交互。…

Git快速入门(三)·远程仓库GitHub以及Gitee的使用

目录 1. 远程仓库GitHub 1.1 登录 1.2 创建库 1.3 创建文件 1.4 修改文件 1.5 创建分支 1.6 删除库 1.7 将远程仓库下载到本地 1.7.1 关联登录 1.7.2 克隆 1.7.3 通过GitHub Desktop更改远程库 2. 远程仓库Gitee 2.1 登录 2.2 创建文件 2.3 关联…

Uncaught ReferenceError: __VUE_HMR_RUNTIME__ is not defined

Syntax Error: Error: vitejs/plugin-vue requires vue (>3.2.13) or vue/compiler-sfc to be present in the dependency tree. 第一步 npm install vue/compiler-sfc npm run dev 运行成功&#xff0c;本地打开页面是空白&#xff0c;控制台报错 重新下载了vue-loa…

Rockect基于Dledger的Broker主从同步原理

1.前言 此文章是在儒猿课程中的学习笔记&#xff0c;感兴趣的想看原来的课程可以去咨询儒猿课堂 这篇文章紧挨着上一篇博客来进行编写&#xff0c;有些不清楚的可以看下上一篇博客&#xff1a; RocketMQ原理简述&#xff08;二&#xff09;-CSDN博客 2.Broker的高可用 如果…

企业为何需要小型语言模型:AI 应用的新趋势与策略

在人工智能蓬勃发展的当下&#xff0c;语言模型作为其中的关键技术&#xff08;LLM的擅长与不擅长&#xff1a;深入剖析大语言模型的能力边界&#xff09;&#xff0c;深刻影响着各个行业的发展和企业的运营模式。长期以来&#xff0c;“越大越好” 的理念在人工智能领域根深蒂…

组会 | DenseNet

目录 1 研究背景1.1 提出的动机1.2 同期的模型 2 网络模型2.1 模型架构2.2 模块与参数2.3 瓶颈层和压缩率2.4 小结 3 实验结果4 优点与缺点4.1 DenseNet 的优点4.2 DenseNet 的缺点 前言&#xff1a;本博客仅为组会总结&#xff0c;如有谬误&#xff0c;请不吝指出…

BGP基础配置实验

一、实验拓补 二、实验要求及分析 实验要求&#xff1a; 1&#xff0c;R1为AS 100区域&#xff1b;R2、R3、R4为AS 200区域且属于OSPF协议&#xff1b;R5为AS 300区域&#xff1b; 2&#xff0c;每个设备上都有环回&#xff0c;且通过环回可以使设备互通&#xff1b; 实验分…

智慧工地解决方案 1

建设背景与挑战 工地施工现场环境复杂&#xff0c;人员管理难度大&#xff0c;多工种交叉作业导致管理混乱&#xff0c;事故频发。传统管理方式难以实现科学、有效、集中式的管理&#xff0c;特别是在环境复杂、地点分散的情况下&#xff0c;监管困难&#xff0c;取证复杂。施…

框架模块说明 #09 日志模块_01

背景 日志模块是系统的重要组成部分&#xff0c;主要负责记录系统运行状态和定位错误问题的功能。通常&#xff0c;日志分为系统日志、操作日志和安全日志三类。虽然分布式数据平台是当前微服务架构中的重要部分&#xff0c;但本文的重点并不在此&#xff0c;而是聚焦于自定义…

通义千问API KEY操作指南

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl 注册阿里云账号 在使用通义千问前&#xff0c;请注册阿里云账号。 开通阿里云百炼模型服务 阿里云百炼官方地址&#xff1a;https://bailian.console.aliyun.com/&#x…

java实验4 反射机制

要求&#xff1a; 1&#xff09;严禁上网抄袭、互相抄袭和各种形式的抄袭&#xff08;如代码抄袭&#xff0c;运行截图一图多用&#xff09;&#xff0c;一旦发现单次作业按零分处理&#xff01; 2&#xff09;课程报告正文内容基本格式为&#xff1a;宋体&#xff0c;小五号…

简易Type-C拉取5V/3A电流电路分享

今天介绍一种在Type-C 5V电压下获取3A电流的简易办法 我们都知道&#xff0c;USB里面的D D-用来传输数据&#xff0c;其实Type-C接口里面还有一组CC引脚&#xff0c;先科普一些概念 DFP&#xff0c;下行端口&#xff0c;可以理解为Host&#xff0c;数据下行以及对外提供电源&…

基于Spring Boot的IT技术交流和分享平台的设计与实现源码

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的IT技术交流和分享平台的设计与实现。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 基于S…

海南省大数据发展中心:数据资产场景化评估案例手册(第二期)

2025年1月3日&#xff0c;海南省数据产品超市印发《数据资产场景化评估案例手册&#xff08;第二期&#xff09;》&#xff08;以下简称《手册》&#xff09;&#xff0c;该手册是基于真实数据要素典型应用场景进行数据资产评估操作的指导性手册&#xff0c;为企业在数据资产入…

​​​​​​​CDP集群安全指南系列文章导读

[一]大数据安全综述 1-认证 身份验证是任何计算环境的基本安全要求。简单来说&#xff0c;用户和服务必须在使用系统功能并获得授权之前&#xff0c;向系统证明其身份&#xff08;进行身份验证&#xff09;。身份验证与授权紧密配合&#xff0c;共同保护系统资源。大多数 CDH …

Chapter4.2:Normalizing activations with layer normalization

文章目录 4 Implementing a GPT model from Scratch To Generate Text4.2 Normalizing activations with layer normalization 4 Implementing a GPT model from Scratch To Generate Text 4.2 Normalizing activations with layer normalization 通过层归一化&#xff08;La…

MyBatis-plus sql拦截器

因为业务需求&#xff0c;重新写了一套数据权限。项目中用的是mybtis-plus&#xff0c;正好MyBatis-Plus提供了插件数据权限插件 | MyBatis-Plus&#xff0c;那就根据文档来实现这个需求。 实现&#xff1a; 实现MultiDataPermissionHandler 首先创建MultiDataPermissionHan…

数据挖掘——关联规则挖掘

数据挖掘——关联数据挖掘 关联数据挖掘关联规则关联规则挖掘问题&#xff1a;具体挖掘过程Apriori 产生关联规则 关联数据挖掘 关联分析用于发现隐藏在大型数据集中的令人感兴趣的联系&#xff0c;所发现的模式通常用关联规则或频繁项集的形式表示。 关联规则反映一个事物与…