接口被刷百萬QPS,怎么防?
前言
今天我們不聊風花雪月,只講這個讓無數開發者夜不能寐的終極命題:當惡意流量如海嘯般撲來,如何守住你的系統防線?
有些小伙伴在工作中可能經歷過接口被刷的噩夢,但百萬QPS量級的攻擊完全是另一個維度的戰爭。
今天這篇文章跟大家一起聊聊接口被刷百萬QPS,如何防御,希望對你會有所幫助。
為什么百萬QPS如此致命?
用一張圖給解釋一下百萬QPS的危害:
圖片
攻擊者三大核心武器:
- IP海洋戰術:10萬+代理IP池動態輪轉,傳統IP限流失效。
- 設備克隆技術:偽造瀏覽器指紋,模擬真實設備行為。
- 協議級精準攻擊:精心構造的HTTP請求,繞過基礎WAF規則。
系統崩潰的致命鏈反應:
- 線程池100%占用 → 新請求排隊超時
- 數據庫連接耗盡 → SQL執行阻塞
- Redis響應飆升 → 緩存穿透雪崩
- 微服務連環熔斷 → 服務不可用
那么,我們該如何防御呢?
第一道防線:基礎限流與熔斷
1. 網關層限流
我們需要在網關層做限流,目前主流的解決方案是:Nginx + Lua。
下面是Nginx的限流配置:
location /api/payment {
access_by_lua_block {
local limiter = require"resty.limit.req"
-- 令牌桶配置:1000QPS + 2000突發容量
local lim, err = limiter.new("payment_limit", 1000, 2000)
ifnot lim then
ngx.log(ngx.ERR, "限流器初始化失敗: ", err)
return ngx.exit(500)
end
-- 基于客戶端IP限流
local key = ngx.var.remote_addr
local delay, err = lim:incoming(key, true)
ifnot delay then
if err == "rejected"then
-- 返回429狀態碼+JSON錯誤信息
ngx.header.content_type = "application/json"
ngx.status = 429
ngx.say([[{"code":429,"msg":"請求過于頻繁"}]])
return ngx.exit(429)
end
ngx.log(ngx.ERR, "限流錯誤: ", err)
return ngx.exit(500)
end
}
}
代碼解析:
- 使用OpenResty的
lua-resty-limit-req
模塊 - 令牌桶算法:1000QPS常規流量 + 2000突發流量緩沖
- 基于客戶端IP維度限流
- 超出限制返回429狀態碼和JSON格式錯誤
2. 分布式熔斷
面對大流量時,我們需要增加分布式熔斷機制,比如使用Sentinel集群流控。
下面是Sentinel集群的流控配置:
public class SentinelConfig {
@PostConstruct
public void initFlowRules() {
// 創建集群流控規則
ClusterFlowRule rule = new ClusterFlowRule();
rule.setResource("createOrder"); // 受保護資源
rule.setGrade(RuleConstant.FLOW_GRADE_QPS); // QPS限流
rule.setCount(50000); // 集群閾值5萬QPS
rule.setClusterMode(true); // 開啟集群模式
rule.setClusterConfig(new ClusterRuleConfig()
.setFlowId(123) // 全局唯一ID
.setThresholdType(1) // 全局閾值
);
// 注冊規則
ClusterFlowRuleManager.loadRules(Collections.singletonList(rule));
}
}
流程圖如下:
圖片
實現原理:
- Token Server集中管理全集群流量配額
- 網關節點實時向Token Server申請令牌
- 當集群總QPS超過閾值時,按比例限制各節點流量
- 避免單節點限流導致的集群流量不均衡問題
第二道防線:設備指紋與行為分析
1. 瀏覽器指紋生成
前端可以在瀏覽器上生成指紋,即使客戶端IP換了,但相同設備的指紋還是一樣的。
前端設備指紋生成方案,這里使用了Canvas+WebGL。
// 前端設備指紋生成方案
function generateDeviceFingerprint() {
// 1. 獲取基礎設備信息
const baseInfo = [
navigator.userAgent,
navigator.platform,
screen.width + 'x' + screen.height,
navigator.language
].join('|');
// 2. 生成Canvas指紋
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#f60';
ctx.fillRect(0, 0, 100, 30);
ctx.fillStyle = '#069';
ctx.font = '16px Arial';
ctx.fillText('防御即藝術', 10, 20);
const canvasData = canvas.toDataURL();
// 3. 生成WebGL指紋
const gl = canvas.getContext('webgl');
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
// 4. 組合生成最終指紋
const fingerprint = md5(baseInfo + canvasData + renderer);
return fingerprint;
}
指紋特性分析:
- 穩定性:相同設備多次生成一致性 > 98%
- 唯一性:不同設備碰撞概率 < 0.1%
- 隱蔽性:用戶無感知,無法簡單清除
2. 行為分析模型
我們還可以分析用戶的行為。
使用下面的鼠標行為分析引擎:
import numpy as np
def analyze_mouse_behavior(move_events):
"""
分析鼠標移動行為特征
:param move_events: 鼠標移動事件列表 [{'x':100, 'y':200, 't':1680000000}, ...]
:return: 異常概率(0-1)
"""
# 1. 計算移動速度序列
speeds = []
for i in range(1, len(move_events)):
prev = move_events[i-1]
curr = move_events[i]
dx = curr['x'] - prev['x']
dy = curr['y'] - prev['y']
distance = (dx**2 + dy**2) ** 0.5
time_diff = curr['t'] - prev['t']
# 防止除零
speed = distance / max(0.001, time_diff)
speeds.append(speed)
# 2. 計算加速度變化
accelerations = []
for i in range(1, len(speeds)):
acc = speeds[i] - speeds[i-1]
accelerations.append(acc)
# 3. 提取關鍵特征
features = {
'speed_mean': np.mean(speeds),
'speed_std': np.std(speeds),
'acc_max': max(accelerations),
'acc_std': np.std(accelerations),
'linearity': calc_linearity(move_events)
}
# 4. 使用預訓練模型預測
return risk_model.predict([features])
行為特征維度:
- 移動速度:機器人速度恒定,真人波動大
- 加速度:機器人加速度變化呈鋸齒狀
- 移動軌跡線性度:機器人多為直線運動
- 操作間隔:機器人操作間隔高度一致
第三道防線:動態規則引擎
1. 實時規則配置
我們還可以使用動態規則引擎(比如:Drools引擎),可以配置風控規則。
Drools風控規則示例:
rule "高頻訪問敏感接口"
// 規則元數據
salience 100// 優先級
no-loop true// 防止規則循環觸發
// 條件部分
when
$req : Request(
path == "/api/coupon/acquire", // 敏感接口
$uid : userId != null, // 登錄用戶
$ip : clientIp
)
// 統計同一用戶10秒內請求次數
accumulate(
Request(
userId == $uid,
path == "/api/coupon/acquire",
this != $req, // 排除當前請求
$ts : timestamp
);
$count : count($ts),
$minTime : min($ts),
$maxTime : max($ts)
)
// 判斷條件:10秒內超過30次請求
eval($count > 30 && ($maxTime - $minTime) < 10000)
then
// 執行動作:阻斷并記錄
insert(new BlockEvent($uid, $ip, "高頻領券"));
$req.setBlock(true);
end
規則引擎優勢:
- 實時生效:新規則秒級推送
- 復雜條件:支持多維度聯合判斷
- 動態更新:無需重啟服務
2. 多維關聯分析模型
我們需要建立一套多維關聯分析模型:
圖片
使用風險評分機制。
評分模型公式:
風險分 =
IP風險權重 × IP評分 +
設備風險權重 × 設備評分 +
行為異常權重 × 行為異常度 +
歷史畫像權重 × 歷史風險值
終極防御架構
下面用用一張圖總結一下百萬QPS防御的架構體系:
圖片
核心組件解析:
1.流量清洗層(CDN)
- 過濾靜態資源請求
- 吸收70%以上流量沖擊
2.安全防護層(網關集群)
- 設備指紋生成:標記每個請求源
- 分布式限流:集群級QPS控制
- 規則引擎:實時判斷風險
3.實時風控層(Flink計算)
// Flink實時風控處理
riskStream
.keyBy(req => req.getDeviceId()) // 按設備ID分組
.timeWindow(Time.seconds(10)) // 10秒滾動窗口
.aggregate(new RiskAggregator) // 聚合風險指標
.map(riskData => {
val score = riskModel.predict(riskData)
if(score > RISK_THRESHOLD) {
// 高風險請求阻斷
blockRequest(riskData.getRequestId())
}
})
4.數據支撐層
- Redis:存儲實時風險畫像
- Flink:計算行為特征指標
- 規則管理臺:動態調整策略
血淚教訓
1. IP白名單的陷阱
場景:將合作方IP加入白名單災難:攻擊者入侵合作方服務器發起攻擊解決方案:
使用設備指紋校驗和行為分析。
2. 限流閾值靜態設置的災難
場景:設置固定5000QPS閾值問題:大促時正常流量超閾值被誤殺優化方案:
// 動態閾值調整算法
public class DynamicThreshold {
// 基于歷史流量自動調整
public static int calculateThreshold(String api) {
// 1. 獲取上周同時段流量
double base = getHistoricalQps(api);
// 2. 考慮當日增長系數
double growth = getGrowthFactor();
// 3. 保留20%安全余量
return (int)(base * growth * 0.8);
}
}
3. 忽略帶寬成本
教訓:10Gbps流量攻擊導致月度預算超支200%應對策略:
- 前置CDN吸收靜態流量
- 配置云廠商DDoS防護服務
- 設置帶寬自動熔斷機制
真正的防御不是讓攻擊無法發生,而是讓攻擊者付出十倍代價卻一無所獲。當你的防御成本低于對手的攻擊成本時,戰爭就結束了。