• 作为架构师如果遇到接口被刷百万QPS该如何防御?
  • 发布于 1天前
  • 27 热度
    0 评论
  • 吹南风
  • 0 粉丝 29 篇博客
  •   
一.前言
今天我们不聊风花雪月,只讲这个让无数开发者夜不能寐的终极命题:当恶意流量如海啸般扑来,如何守住你的系统防线?有些小伙伴在工作中可能经历过接口被刷的噩梦,但百万QPS量级的攻击完全是另一个维度的战争。今天这篇文章跟大家一起聊聊接口被刷百万QPS,如何防御,希望对你会有所帮助。

二.为什么百万QPS如此致命?
用一张图给解释一下百万QPS的危害:

攻击者三大核心武器:
1.IP海洋战术:10万+代理IP池动态轮转,传统IP限流失效。
2.设备克隆技术:伪造浏览器指纹,模拟真实设备行为。

3.协议级精准攻击:精心构造的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
        -- 堆代码 duidaima.com
        -- 基于客户端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())
    }
  })
数据支撑层
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防护服务

.设置带宽自动熔断机制

四.总结:

真正的防御不是让攻击无法发生,而是让攻击者付出十倍代价却一无所获。当你的防御成本低于对手的攻击成本时,战争就结束了。

用户评论