5大常见高并发限流算法选型浅析
👉目录
1 固定窗口算法(Fixed Window Algorithm)
2 滑动窗口算法(Sliding Window Algorithm)
3 滑动日志算法(Sliding Log Algorithm)
4 漏桶算法(Leaky Bucket Algorithm)
5 令牌桶算法(Token Bucket Algorithm)
6 总结
本文将深入剖析几种常见的限流算法,探讨它们的原理、优缺点并给出代码实例,帮助读者更好地理解和应用这些算法,从而在实际项目中构建更加高效、稳定的系统。
01
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_; // 互斥锁};
优点
实现简单,非常容易理解。
适用于请求速率相对稳定的场景。
缺点
在短时间流量突发时,将会有大量失败,无法平滑流量。
有窗口边际效应:在窗口切换时,可能会出现短时间内请求激增的情况,导致系统过载。
02
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; // 当前桶的索引};
优点
相比固定窗口算法可以更细粒度地控制流量。
减缓了固定窗口算法中的窗口边际效应。
缺点
在短时间流量突发时,将会有大量失败,无法平滑流量。
03
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
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
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_; // 上次补充令牌的时间};
优点
能够处理突发流量,避免系统瞬间过载。
灵活性高,可以通过调整令牌生成速率和桶容量来控制流量。
缺点
实现相对复杂。
06
📢📢欢迎加入腾讯云开发者社群,享前沿资讯、大咖干货,找兴趣搭子,交同城好友,更有鹅厂招聘机会、限量周边好礼等你来~
(长按图片立即扫码)