防止用戶短時間內瘋狂刷新頁面(如 1 分鐘超 100 次),保護服務器資源,防惡意刷接口。
get($key);
if ($current !== false) {
// Key 存在:檢查閾值
if ($current >= $maxRequests) {
// 觸發限流:返回 429 狀態碼 + 友好提示
http_response_code(429);
header('Content-Type: text/html; charset=utf-8');
exit('請求過於頻繁,請稍後再試 (429 Too Many Requests)
');
}
// 未超限:原子遞增
$memcache->increment($key);
} else {
// Key 不存在:首次訪問,設置初始值 1,TTL 60s
// Memcache::set($key, $var, $flag, $expire)
$memcache->set($key, 1, 0, $windowSeconds);
}
}
/**
* 獲取客戶端真實 IP(支持代理)
*/
function getClientIp(): string {
$headers = ['HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'HTTP_CLIENT_IP'];
foreach ($headers as $header) {
if (!empty($_SERVER[$header])) {
$ips = explode(',', $_SERVER[$header]);
return trim($ips[0]); // 取第一個非代理 IP
}
}
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}
// 在項目公共入口文件調用
// requestRateLimit(100, 60);
`ip2long()` 返回有符號整數,負數無法作為 Key 一部分。`sprintf('%u')` 轉為無符號字符串,保證 Key 唯一且可讀。
Memcached 協議層面保證 `incr`/`decr` 原子操作,多進程並發下計數絕不會漏增或重複增。
當前方案是固定窗口:Key 創建後 60s 過期。邊界瞬間(如 59.9s 和 60.1s)可能允許 200 次請求。
嚴格滑動窗口需用 Redis Sorted Set(時間戳為分數)或 Lua 腳本實現。
Memcached 故障時 不應阻斷正常業務,代碼中 `return` 而非 `die()`,僅記錄錯誤日誌。
// Redis + Lua 腳本實現精確滑動窗口 $luaScript = <<= limit then return 0 end redis.call('ZADD', key, now, now .. '-' .. math.random()) redis.call('EXPIRE', key, window) return 1 LUA; $allowed = $redis->eval($luaScript, 1, $key, $maxRequests, $windowSeconds, time() * 1000); if (!$allowed) { /* 429 */ }
這套方案以極低延遲、極簡代碼解決了 90% 的頁面刷新濫用場景。高併發核心業務建議遷移到 Redis + Lua 精確滑動窗口,並接入 API 网關層統一治理。