限流算法
4. 限流的底层原理解析
01 限流的目的
限流主要有两个目的:
- 防止系统过载:确保系统在高负载情况下仍能保持稳定运行。
- 保证服务质量:为所有用户提供公平的服务,避免某些用户占用过多资源。
02 限流算法的实现
2.1 固定窗口计数器算法
固定窗口计数器算法是一种基本的限流方法,它通过在固定时间窗口内跟踪请求的数量来实现限流。
// 这是一个简单的 C++ 实现案例
#include <iostream>
#include <mutex>
#include <chrono>
// FixedWindowCounter 类实现固定窗口计数器限流算法。
class FixedWindowCounter {
private:
std::mutex mu; // 用于同步访问,保证并发安全
int count; // 记录当前时间窗口内的请求数量
int limit; // 时间窗口内允许的最大请求数量
std::chrono::steady_clock::time_point window; // 记录当前时间窗口的开始时间
std::chrono::steady_clock::duration duration; // 时间窗口的持续时间
public:
FixedWindowCounter(int limit, std::chrono::steady_clock::duration duration)
: count(0), limit(limit), window(std::chrono::steady_clock::now()), duration(duration) {}
// allow 方法用于判断当前请求是否被允许。
bool allow() {
std::lock_guard<std::mutex> lock(mu); // 自动管理锁,保证原子性
auto now = std::chrono::steady_clock::now();
// 如果当前时间超过了窗口的结束时间,重置计数器和窗口开始时间。
if (now - window > duration) {
count = 0;
window = now;
}
// 如果当前计数小于限制,则增加计数并允许请求。
if (count < limit) {
count++;
return true;
}
return false; // 达到限制,拒绝请求。
}
};
int main() {
// 设置每秒只允许10个请求
FixedWindowCounter limiter(10, std::chrono::seconds(1));
for (int i = 0; i < 15; i++) {
if (limiter.allow()) {
std::cout << "Request " << i + 1 << " allowed\n";
} else {
std::cout << "Request " << i + 1 << " rejected\n";
}
}
return 0;
}
- 实现原理:通过设置固定的时间窗口和请求限制数量,在每个窗口开始时清零计数器,请求到来时递增。达到阈值后拒绝后续请求,直至窗口重置。
- 优点:实现简单直观,能保证固定窗口内的绝对限制。
- 缺点:在窗口切换的瞬间可能有请求高峰(临界点问题);无法平滑处理突发流量。 固定窗口适用于请求分布相对均匀的场景。
2.2 滑动窗口算法
滑动窗口通过覆盖多个小时间段来平滑请求流量,避免瞬时高峰。
// 滑动窗口 C++ 演示代码
#include <iostream>
#include <vector>
#include <mutex>
#include <chrono>
class SlidingWindowLimiter {
std::mutex mtx;
std::vector<int> counters;
int limit;
std::chrono::steady_clock::time_point windowStart;
std::chrono::milliseconds windowDuration;
std::chrono::milliseconds interval;
public:
SlidingWindowLimiter(int limit, std::chrono::milliseconds windowDuration, std::chrono::milliseconds interval)
: limit(limit), windowStart(std::chrono::steady_clock::now()), windowDuration(windowDuration), interval(interval) {
counters.resize(windowDuration.count() / interval.count(), 0);
}
bool allow() {
std::lock_guard<std::mutex> lock(mtx);
auto now = std::chrono::steady_clock::now();
if (now - windowStart > windowDuration) slideWindow(now);
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - windowStart);
int index = (elapsed.count() / interval.count()) % counters.size();
if (counters[index] < limit) {
counters[index]++;
return true;
}
return false;
}
private:
void slideWindow(std::chrono::steady_clock::time_point now) {
// 滑动窗口,忽略最旧的时间段
for (size_t i = 0; i < counters.size() - 1; ++i) {
counters[i] = counters[i + 1];
}
counters.back() = 0; // 重置最后一个时间段
windowStart = now;
}
};
int main() {
SlidingWindowLimiter limiter(1, std::chrono::seconds(1), std::chrono::milliseconds(10));
for (int i = 0; i < 100; i++) {
std::cout << "Request " << i+1 << (limiter.allow() ? " allowed\n" : " rejected\n");
}
return 0;
}
- 实现原理:将大时间窗口划分为多个小格子,每个请求落在对应的小格子内。随时间推移,废弃旧格子并引入新格子,统计总格子内的请求数。
- 优点:相比固定窗口,能更平滑地处理请求,避免瞬时高峰。
- 缺点:实现复杂,需要维护多个计数器,内存和计算开销更高。
2.3 漏桶算法
漏桶算法适合平滑突发流量,确保数据以均匀的速率被处理。
// 漏桶算法 C++ 演示代码
#include <iostream>
#include <queue>
#include <mutex>
#include <thread>
#include <chrono>
class LeakyBucket {
std::queue<int> q; // 请求队列
int capacity;
std::mutex mtx;
public:
LeakyBucket(int cap) : capacity(cap) {}
bool push() {
std::lock_guard<std::mutex> lock(mtx);
if (q.size() < capacity) {
q.push(1); // 模拟放入请求
return true;
}
return false; // 桶满,丢弃请求
}
void process() {
while (true) {
bool hasRequest = false;
{
std::lock_guard<std::mutex> lock(mtx);
if (!q.empty()) {
q.pop();
hasRequest = true;
}
}
if (hasRequest) {
std::cout << "Request processed\n";
}
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 恒定速率处理
}
}
};
int main() {
LeakyBucket lb(5);
std::thread processor(&LeakyBucket::process, &lb); // 启动后台处理线程
for (int i = 0; i < 10; i++) {
if (lb.push()) std::cout << "Request " << i+1 << " accepted\n";
else std::cout << "Request " << i+1 << " rejected\n";
}
std::this_thread::sleep_for(std::chrono::seconds(2));
processor.detach();
return 0;
}
- 实现原理:用固定容量的队列模拟桶,以恒定速率从桶中取出请求处理。请求到达时入桶,桶满则溢出(拒绝)。
- 优点:强制固定速率处理,流量绝对平滑。
- 缺点:面对突发流量不够灵活,突发请求会被直接堆积或丢弃。
2.4 令牌桶算法
令牌桶允许一定程度的突发流量,同时保持长期的平均速率。
// 令牌桶算法 C++ 演示代码
#include <iostream>
#include <mutex>
#include <chrono>
#include <algorithm>
class TokenBucket {
std::mutex mtx;
int capacity;
double tokens;
double refillRate; // 每秒填充速率
std::chrono::steady_clock::time_point lastRefill;
public:
TokenBucket(int cap, double rate)
: capacity(cap), tokens(cap), refillRate(rate), lastRefill(std::chrono::steady_clock::now()) {}
bool allow() {
std::lock_guard<std::mutex> lock(mtx);
auto now = std::chrono::steady_clock::now();
std::chrono::duration<double> elapsed = now - lastRefill;
// 按速率计算新增令牌
double tokensToAdd = elapsed.count() * refillRate;
if (tokensToAdd > 0) {
tokens = std::min((double)capacity, tokens + tokensToAdd);
lastRefill = now;
}
// 判断是否有充足令牌
if (tokens >= 1.0) {
tokens -= 1.0;
return true;
}
return false;
}
};
int main() {
TokenBucket limiter(10, 2.0); // 容量10,每秒加2个
for (int i = 0; i < 15; i++) {
std::cout << "Request " << i+1 << (limiter.allow() ? " allowed\n" : " rejected\n");
}
return 0;
}
- 实现原理:以固定速率向桶中添加令牌,直到达到容量上限。请求到来时需消耗令牌,令牌不足则拒绝。由于桶内能预存令牌,因此能处理瞬间突发流量。
- 优点:允许突发流量,灵活度高。
- 缺点:实现需维护时间戳和浮点计算,有一定同步开销。
03 限流的实现方式
3.1 应用层限流
在应用代码中通过中间件实现限流。拦截请求,判断是否放行。
// 应用层限流伪代码 (结合上述 TokenBucket)
#include <iostream>
#include <string>
// 模拟 HTTP 请求上下文
struct Context {
bool aborted = false;
void JSON(int code, const std::string& msg) { std::cout << "HTTP " << code << " : " << msg << "\n"; }
void Abort() { aborted = true; }
void Next() { if (!aborted) std::cout << "Processing handler logic...\n"; }
};
// 模拟限流中间件
auto Middleware(TokenBucket* tb) {
return [tb](Context& c) {
if (!tb->allow()) {
c.JSON(429, "too many requests");
c.Abort();
return;
}
c.Next();
};
}
int main() {
TokenBucket tb(10, 1.0);
auto limitMiddleware = Middleware(&tb);
// 模拟请求流转
Context ctx;
limitMiddleware(ctx);
return 0;
}
- 优点:易于集成,细粒度控制(按路由/用户限制)。
- 缺点:增加应用侧同步开销,可能影响并发吞吐。
3.2 代理层限流
在 Nginx/HAProxy 等代理服务器层拦截,保护后端。 Nginx 配置示例:
http {
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=1r/s;
server {
listen 80;
location /api/ {
limit_req zone=mylimit burst=5 nodelay;
proxy_pass http://backend/;
}
}
}
- 优点:全局保护,减轻后端应用负担。
- 缺点:依赖中间件功能,分布式下需额外状态同步机制。
3.3 硬件层限流
在物理或云厂商的负载均衡器(如 F5、云 SLB)网关节点进行拦截。
04 限流策略
4.1 阈值设置
决定单位时间内的最大承载量。
// 带有明确阈值配置的扩展
class RateLimiterV2 {
std::mutex mtx;
int tokens;
int capacity;
double refillRate;
int limit; // 强制熔断阈值
public:
RateLimiterV2(int cap, double rate, int lim) : capacity(cap), tokens(cap), refillRate(rate), limit(lim) {}
bool allow() {
std::lock_guard<std::mutex> lock(mtx);
if (tokens >= limit) return false; // 触发阈值熔断
// ... 继续令牌计算 ...
return true;
}
};
4.2 请求分类
针对不同 API 赋予不同限流器。
#include <unordered_map>
#include <string>
std::unordered_map<std::string, RateLimiterV2*> RouteLimiterMap;
void SetRateLimiterForRoute(const std::string& route, int cap, double rate, int limit) {
RouteLimiterMap[route] = new RateLimiterV2(cap, rate, limit);
}
// 模拟带路由的中间件
auto MiddlewareWithRoute(const std::string& route) {
return [route](Context& c) {
if (RouteLimiterMap.count(route) && !RouteLimiterMap[route]->allow()) {
c.JSON(429, "too many requests");
c.Abort();
return;
}
c.Next();
};
}
4.3 反馈机制
返回明确原因给调用方。
#include <tuple>
class FeedbackLimiter {
public:
std::tuple<bool, std::string> allowWithFeedback() {
// ... 业务判断逻辑 ...
bool isAllowed = false; // 模拟被拒
if (!isAllowed) {
return {false, "Too many requests. Please try again later."};
}
return {true, ""};
}
};
05 限流的考虑因素
5.1 公平性
依据 UserID 或 IP 分发限流器。
class FairLimiter {
std::mutex mtx;
std::unordered_map<std::string, RateLimiterV2*> limits;
public:
std::tuple<bool, std::string> allow(const std::string& userID) {
std::lock_guard<std::mutex> lock(mtx);
if (limits.find(userID) == limits.end()) {
limits[userID] = new RateLimiterV2(100, 10.0, 100);
}
return {true, "Allowed"}; // 调内部器 allowWithFeedback
}
};
5.2 灵活性
支持运行时热更新参数。
class FlexibleLimiter {
std::mutex mtx;
int capacity;
double refillRate;
int limit;
public:
void setParams(int cap, double rate, int lim) {
std::lock_guard<std::mutex> lock(mtx);
capacity = cap;
refillRate = rate;
limit = lim;
}
// 动态应用新参数去执行判断...
};
5.3 透明性
响应头注入当前剩余额度,供客户端削峰。
class TransparentLimiter {
int currentTokens = 50; // 模拟额度
public:
std::tuple<bool, std::string, int> allowWithStatus() {
return {false, "Limit exceeded", currentTokens};
}
};
// 中间件设置 Header (伪代码)
void MiddlewareWithTransparency(Context& w) {
TransparentLimiter tl;
auto [allowed, msg, tokens] = tl.allowWithStatus();
if (!allowed) {
// w.SetHeader("X-RateLimit-Remaining", std::to_string(tokens));
w.JSON(429, msg);
w.Abort();
return;
}
w.Next();
}

浙公网安备 33010602011771号