C++实现基于时间戳的限流算法 _ 令牌桶与漏桶原理实现【源码】
C++实现基于时间戳的限流算法:令牌桶与漏桶原理实现【源码】

免费影视、动漫、音乐、游戏、小说资源长期稳定更新! 👉 点此立即查看 👈
开门见山,先说结论:在C++服务端开发中,利用std::chrono配合原子变量,完全可以构建出线程安全且开销极低的令牌桶限流器。至于漏桶算法,在纯内存的服务端限流场景里,其实很少有必要去实现——它的核心是“恒定速率输出”,而服务端限流真正要防范的,往往是突发流量冲击。相比之下,令牌桶“允许突发+平滑限流”的特性,显然更贴合实际需求。
令牌桶:用 std::atomic 和 std::chrono::steady_clock 管理剩余令牌
实现高性能令牌桶,关键在于思路的转变:核心不是每秒去重置令牌数量,而是根据当前时刻,动态计算出“此刻应该有多少令牌”。这样一来,就彻底摆脱了对定时器或后台线程的依赖。具体怎么操作?有几个关键点需要把握:
capacity(桶容量)和rate_per_sec(填充速率)是基础配置,例如可以设为每秒填充100个令牌,桶最大容量为200个。- 存储的核心不是令牌数量本身,而是
std::atomic类型的上一次填充时间戳(建议使用纳秒精度)。这个设计能大幅减少竞态条件。 - 每次请求到来时,先计算出从上次填充到现在,理论上应增加的令牌数。公式是:(当前时间 - 上次填充时间) * 填充速率 / 10^9。然后,将这个数值与当前剩余令牌数取最小值,得到实际可用令牌。
- 最后判断是否放行:如果
可用令牌数 >= 本次请求消耗数,则通过原子操作更新剩余令牌值,并返回true。
来看一段示例代码的关键片段:
bool tryConsume(int tokens = 1) {
auto now = std::chrono::steady_clock::now().time_since_epoch().count();
auto& last = last_fill_time_;
auto prev = last.load(std::memory_order_relaxed);
long long a vail = 0;
do {
auto elapsed_ns = now - prev;
double added = elapsed_ns * rate_per_sec_ / 1e9;
a vail = std::min(static_cast(added), capacity_);
if (a vail < tokens) return false;
} while (!last.compare_exchange_weak(prev, now, std::memory_order_relaxed));
// 此处需用 CAS 更新剩余令牌数(略去具体实现,推荐用带版本号的双原子变量或 mutex)
return true;
}
想深入掌握?可以立即学习“C++免费学习笔记(深入)”。
漏桶不适合做接入层限流:它不解决“突发允许通过”的问题
为什么说漏桶在接入层限流中常常水土不服?根源在于它的设计哲学:强制以恒定速率输出请求。这意味着,即便桶是空的,新来的请求也只能排队等待。在HTTP接入层,这会直接放大请求延迟。更关键的是,它无法应对那些合理的、短时间的流量突增,比如秒杀活动的预热阶段。
实践中,漏桶常被误用,有几个典型的坑:
- 用
std::queue加一个定时器来模拟“漏水”,结果往往因为定时器精度差、线程调度抖动,导致实际漏速完全不可控。 - 把漏桶简单当成“请求队列长度限制器”,但这本质上只是削峰填谷,并非严格意义上的限流。真正的限流,在超出能力时必须果断拒绝,而非无限制排队。
- 试图混合使用令牌桶和漏桶,比如“令牌桶准入,漏桶排队”,这种设计不仅增加了系统复杂度,还可能带来额外的延迟,收益却微乎其微。
话说回来,漏桶就一无是处吗?当然不是。它真正的用武之地,在于那些对速率稳定性有硬性要求的场景,比如底层IO调度或硬件限速。在这些地方使用,也必须配合高精度时钟(例如CLOCK_MONOTONIC_RAW)甚至内核旁路技术,才有实际意义。
线程安全与性能陷阱:别用 std::mutex 锁整个桶
高并发场景下,如果用std::mutex把整个桶锁住,那性能基本就归零了,所有请求都会串行化。正确的做法是:
- 所有读操作,比如时间计算、令牌估算,全部走无锁路径。只在最后真正扣减令牌的那一步,做最小粒度的CAS(比较并交换)操作。
- 坚决避免使用
std::time(nullptr)或gettimeofday()这类可能发生时间回跳且精度不高的函数。必须使用std::chrono::steady_clock。 - 桶容量
capacity不要设置得过大(比如10万)。过大的数值会导致浮点误差累积,建议控制在1000以内,并尽量使用整数运算来替代浮点运算。 - 如果需要做分布式限流,那么本地的令牌桶只能作为最后一道防线。核心的限流逻辑必须下沉,采用Redis+Lua脚本(例如
redis-cell模块)或者专用的限流服务来实现。
还有一个极易被忽略的设计要点:令牌桶对突发流量的容忍能力,其实取决于capacity和rate_per_sec的比值。把配置设为100/100和1000/100,看上去平均速率都是100 QPS,但后者允许在某一秒内突发处理1000个请求——这个细微的设计权衡,文档里往往不提,可一旦线上流量出现波动,全靠它来兜底。
结论:C++中用std::chrono和原子变量可实现线程安全、低开销的令牌桶限流,而漏桶在纯内存服务端限流中基本不适用,因其无法应对突发流量,仅适合底层IO等对恒定速率有硬性要求的场景。
游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。
同类文章
PHP如何防止点击劫持攻击_PHP防止点击劫持攻击方法【安全】
PHP如何防止点击劫持攻击:五种协同防护策略详解 如果你的PHP应用页面被发现可以被随意嵌入到第三方网站的iframe中,甚至可能诱导用户进行非本意的操作,那么这很可能就是点击劫持攻击在“敲门”了。这种安全漏洞的危害不容小觑,但好在,我们可以通过一套组合拳来有效防御。下面要介绍的,正是五种经过验证、
Laravel如何部署到生产环境_Laravel部署到生产环境方法【运维】
Lara vel生产环境部署需六步:一、安装PHP 8 1+、Nginx、MySQL、Composer及必要扩展;二、Git克隆代码并运行composer install --no-dev --optimize-autoloader;三、设APP_ENV=production、APP_DEBUG=f
C++ move_if_noexcept用法 _ 异常安全与移动语义结合【详解】
std::move_if_noexcept:一个你几乎不该直接调用的“内部开关” 首先需要明确一个核心观点:std::move_if_noexcept 并不是一个设计给业务逻辑手动调用的“选择器”。它的真实定位,是 C++ 标准库为了实现强异常安全保证而内置的自动化决策机制。简单来说,它是一个“幕后
PHP函数如何利用非统一内存访问优化_PHP适配NUMA硬件架构【方法】
PHP函数如何利用非统一内存访问优化_PHP适配NUMA硬件架构【方法】 先说一个核心结论:PHP函数本身,无法直接利用非统一内存访问(NUMA)架构来优化性能。 这听起来可能有点反直觉,但原因在于PHP的运行机制。它运行在Zend虚拟机之上,所有的内存分配,无论是通过glibc的malloc还是P
C++如何实现函数超时处理 _ std::future_status与wait_for【实战】
C++如何实现函数超时处理:std::future_status与wait_for实战解析 std::future_status 是什么,为什么不能直接用它判断超时 先来澄清一个常见的误区。std::future_status本身只是一个简单的枚举类型,它包含三个可能的值:ready、timeout
- 日榜
- 周榜
- 月榜
1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
相关攻略
2015-03-10 11:25
2015-03-10 11:05
2021-08-04 13:30
2015-03-10 11:22
2015-03-10 12:39
2022-05-16 18:57
2025-05-23 13:43
2025-05-23 14:01
热门教程
- 游戏攻略
- 安卓教程
- 苹果教程
- 电脑教程
热门话题

