问题
有一个服务器,用于提供HTTP服务,但是需要限制每个用户在任意的100秒内只能请求60次,怎么实现这个功能
我的回答
嗯,这个问题其实挺经典的,就是限流嘛。任意100秒内最多60次请求,这是个滑动窗口的问题。
我觉得可以这样实现:
方案一:用队列记录时间戳
cpp
#include <unordered_map>
#include <queue>
#include <chrono>
class RateLimiter {
private:
std::unordered_map<std::string, std::queue<long long>> userRequests;
public:
bool isAllowed(const std::string& userId) {
auto now = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()).count();
auto& requests = userRequests[userId];
// 先把100秒前的请求都清掉
while (!requests.empty() && now - requests.front() > 100) {
requests.pop();
}
// 看看是不是已经到60次了
if (requests.size() >= 60) {
return false;
}
// 记录这次请求
requests.push(now);
return true;
}
};
这个方案比较直观,就是每个用户维护一个时间戳队列。但是吧,如果用户很多的话,内存消耗会比较大。
方案二:滑动窗口计数器
cpp
#include <unordered_map>
#include <chrono>
struct WindowData {
int prevCount = 0;
int currCount = 0;
long long currWindowStart = 0;
};
class RateLimiter {
private:
std::unordered_map<std::string, WindowData> windows;
static const int WINDOW_SIZE = 100;
static const int MAX_REQUESTS = 60;
public:
bool isAllowed(const std::string& userId) {
auto now = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()).count();
auto& data = windows[userId];
long long currentWindow = now / WINDOW_SIZE;
// 如果进入了新的窗口
if (data.currWindowStart != currentWindow) {
data.prevCount = data.currCount;
data.currCount = 0;
data.currWindowStart = currentWindow;
}
// 计算滑动窗口内的估算请求数
double prevWeight = (double)(WINDOW_SIZE - (now % WINDOW_SIZE)) / WINDOW_SIZE;
double estimatedCount = data.prevCount * prevWeight + data.currCount;
if (estimatedCount >= MAX_REQUESTS) {
return false;
}
data.currCount++;
return true;
}
};
这个方案内存占用比较小,每个用户就存几个数字,但是精度会差一点点。
实际使用的话
如果在实际项目中遇到这个问题,可能还会考虑:
- 线程安全:加个mutex或者用原子操作
cpp
#include <mutex>
class ThreadSafeRateLimiter {
private:
std::mutex mtx;
// ... 其他成员
public:
bool isAllowed(const std::string& userId) {
std::lock_guard<std::mutex> lock(mtx);
// ... 限流逻辑
}
};
-
内存清理:定期清理过期的用户数据,不然内存会一直涨
-
如果是分布式的:可能需要用Redis之类的外部存储
我个人比较倾向于第二种方案,因为内存效率高,而且对于大部分场景来说精度够用了。当然如果对精度要求特别高,那就用第一种。