秒杀被黑产刷崩了下单:Sentinel 限流、熔断、热点防护实战

限时秒杀开始三分钟下单服务就挂了,黑产脚本把 QPS 刷到 21 万。一周接入 Sentinel 流量治理:QPS 流控(直接拒绝/Warm Up/排队)、关联与链路限流、慢调用比例熔断降级、热点参数限流精准防刷、系统自适应保护 + 网关流控。后面几场秒杀零宕机。

2024 年我们做了一场限时秒杀,活动开始三分钟,下单服务就挂了。复盘发现:除了正常用户,还有一批黑产用脚本疯狂刷下单接口,峰值 QPS 冲到 21 万,远超系统设计容量,服务线程被瞬间打满,连正常用户都下不了单。活动被迫中止。事后投了一周接入并打磨 Sentinel 流量治理,从限流、熔断、热点防护到系统自适应保护全部配齐,后面几场秒杀再没崩过。本文复盘 Sentinel 流量防护的完整实战。

问题背景

业务:电商下单服务(Spring Cloud),秒杀活动
正常容量:单实例约 3000 QPS,8 实例
事故现象:
- 活动开始 3 分钟,下单接口 QPS 冲到 21 万
- 远超 8 实例 2.4 万的设计容量
- 线程池打满,Tomcat 拒绝新请求
- 下游库存服务被连带拖慢,链路雪崩
- 正常用户大量下单失败

抓包分析流量来源:
- 正常用户:约 1.8 万 QPS
- 黑产脚本:约 19 万 QPS(同一批 IP/设备,疯狂重试)

根因:
1. 接口完全没有限流,来多少收多少
2. 下游(库存/风控)变慢时,上游无熔断,线程全卡在等待
3. 没有针对"单用户/单商品"的细粒度防刷
4. 系统过载时没有自我保护,直到彻底崩溃
5. 没有降级预案,挂了就是白屏

修复 1:QPS 流控

// 接入 Sentinel,给下单接口定义资源并配流控规则

// === 用注解定义资源 ===
@Service
public class OrderService {

    @SentinelResource(value = "createOrder",
                      blockHandler = "createOrderBlocked")
    public OrderResult createOrder(OrderRequest req) {
        return doCreateOrder(req);
    }

    // 被限流/降级时走这里(方法签名要和原方法一致 + BlockException)
    public OrderResult createOrderBlocked(OrderRequest req, BlockException ex) {
        // 不是报错,而是友好提示:让用户重试,而不是看到 500
        return OrderResult.fail("当前下单人数过多,请稍后再试");
    }
}

// === 流控规则:限制 createOrder 的 QPS ===
public void initFlowRules() {
    FlowRule rule = new FlowRule();
    rule.setResource("createOrder");
    rule.setGrade(RuleConstant.FLOW_GRADE_QPS);   // 按 QPS 限流
    rule.setCount(2500);                          // 单实例阈值 2500 QPS
    // 控制效果:超过阈值后怎么办
    rule.setControlBehavior(
        RuleConstant.CONTROL_BEHAVIOR_DEFAULT);   // 直接拒绝(快速失败)
    FlowRuleManager.loadRules(Collections.singletonList(rule));
}

// === 三种控制效果,按场景选 ===
// 1. 直接拒绝(CONTROL_BEHAVIOR_DEFAULT)
//    超阈值立即拒绝,适合明确知道容量上限的接口
// 2. Warm Up(CONTROL_BEHAVIOR_WARM_UP)
//    阈值从低缓慢爬升到设定值,给系统(缓存/连接池)预热时间
//    适合"冷启动"场景,防止瞬间满负荷把冷系统打挂
//    rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_WARM_UP);
//    rule.setWarmUpPeriodSec(10);
// 3. 排队等待(CONTROL_BEHAVIOR_RATE_LIMITER)
//    匀速放行,多余请求排队(超时则拒绝),削峰填谷
//    适合突发流量、又不想丢请求的写场景(如消息处理)
//    rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER);
//    rule.setMaxQueueingTimeMs(500);

修复 2:关联限流与链路限流

// QPS 直接限流只看自己,Sentinel 还能基于"关系"限流

// === 关联限流:当关联资源压力大时,限流本资源 ===
// 场景:读接口和写接口共用一个 DB
// 写库存(扣减)很重要,读库存(查询)可以让步
// 配置:当"写库存"QPS 高时,限流"读库存",把 DB 资源让给写
public void initRelateRule() {
    FlowRule rule = new FlowRule();
    rule.setResource("queryStock");               // 限流对象:读库存
    rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
    rule.setCount(1000);
    rule.setStrategy(RuleConstant.STRATEGY_RELATE);
    rule.setRefResource("deductStock");           // 关联资源:写库存
    // 当 deductStock QPS 达到压力时,queryStock 被限到 1000
    FlowRuleManager.loadRules(Collections.singletonList(rule));
}

// === 链路限流:只对来自特定调用链的流量限流 ===
// 场景:同一个"扣库存"方法,被"秒杀下单"和"普通下单"都调用
// 只想限制秒杀链路打过来的,普通下单不限
public void initChainRule() {
    FlowRule rule = new FlowRule();
    rule.setResource("deductStock");
    rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
    rule.setCount(500);
    rule.setStrategy(RuleConstant.STRATEGY_CHAIN);
    rule.setRefResource("seckillEntry");          // 只限来自秒杀入口的链路
    FlowRuleManager.loadRules(Collections.singletonList(rule));
}
// 链路限流要配合 web-context-unify=false,让不同入口各成一条链路

修复 3:熔断降级

// 下游(风控/库存)变慢或大量报错时,熔断它,避免线程全卡在等待

// === 熔断规则:三种触发策略 ===
public void initDegradeRules() {
    List<DegradeRule> rules = new ArrayList<>();

    // 策略 1:慢调用比例 —— 最常用
    DegradeRule slow = new DegradeRule("riskCheck");
    slow.setGrade(RuleConstant.DEGRADE_GRADE_RT);
    slow.setCount(200);             // RT 阈值 200ms,超过算"慢调用"
    slow.setSlowRatioThreshold(0.5);// 慢调用比例超 50% → 熔断
    slow.setStatIntervalMs(10000);  // 统计窗口 10s
    slow.setMinRequestAmount(20);   // 窗口内至少 20 个请求才触发(防误判)
    slow.setTimeWindow(10);         // 熔断 10s,之后进入半开探测
    rules.add(slow);

    // 策略 2:异常比例
    DegradeRule errRatio = new DegradeRule("riskCheck");
    errRatio.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO);
    errRatio.setCount(0.5);         // 异常比例超 50% → 熔断
    errRatio.setStatIntervalMs(10000);
    errRatio.setMinRequestAmount(20);
    errRatio.setTimeWindow(10);
    rules.add(errRatio);

    // 策略 3:异常数
    DegradeRule errCount = new DegradeRule("riskCheck");
    errCount.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
    errCount.setCount(50);          // 1 分钟异常数超 50 → 熔断
    errCount.setTimeWindow(30);
    rules.add(errCount);

    DegradeRuleManager.loadRules(rules);
}

// === 熔断后的降级处理 ===
@SentinelResource(value = "riskCheck", fallback = "riskCheckFallback")
public RiskResult riskCheck(OrderRequest req) {
    return riskClient.check(req);   // 调第三方风控
}

// fallback 处理"业务异常 + 熔断";blockHandler 只处理"限流/熔断 Block"
public RiskResult riskCheckFallback(OrderRequest req, Throwable t) {
    // 风控熔断时的降级策略:小额订单放行,大额订单转人工审核
    if (req.getAmount() < 100) {
        return RiskResult.pass("风控降级:小额自动放行");
    }
    return RiskResult.review("风控降级:转人工审核");
}
// 熔断状态机:Closed(正常)→ 触发阈值 → Open(熔断,直接走 fallback)
//           → timeWindow 后 → Half-Open(放一个请求探测)
//           → 成功则 Closed,失败则继续 Open

修复 4:热点参数限流

// 普通限流是"接口级",但黑产刷的是"同一个用户/同一个商品"
// 热点参数限流:对参数的某些具体值单独限流

// === 给资源标注热点参数 ===
@SentinelResource("seckillOrder")
public OrderResult seckillOrder(
        @SentinelHotParam Long userId,    // 第 0 个参数:用户 id
        @SentinelHotParam Long itemId) {  // 第 1 个参数:商品 id
    return doSeckill(userId, itemId);
}

// === 热点规则:对单个用户限流 ===
public void initHotParamRule() {
    ParamFlowRule rule = new ParamFlowRule("seckillOrder");
    rule.setParamIdx(0);            // 对第 0 个参数(userId)限流
    rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
    rule.setCount(5);               // 默认:每个 userId 最多 5 QPS
    rule.setDurationInSec(1);

    // 例外:某些 VIP 用户或内部账号放宽
    ParamFlowItem vip = new ParamFlowItem();
    vip.setObject(String.valueOf(10001L));   // userId=10001
    vip.setClassType(long.class.getName());
    vip.setCount(100);              // 这个用户单独放到 100 QPS
    rule.setParamFlowItemList(Collections.singletonList(vip));

    ParamFlowRuleManager.loadRules(Collections.singletonList(rule));
}
// 效果:黑产单个账号狂刷 → 被限到 5 QPS,正常用户(点几下)完全无感
// 这是防刷最有效的一招:接口级限流挡不住"分散到大量请求"的刷子,
// 但热点限流精准锁定"单个值刷得太猛"

// === 也可对商品维度限流 ===
// rule.setParamIdx(1) → 对 itemId 限流,防单个爆品把系统打偏

修复 5:系统自适应保护与网关流控

// === 系统自适应保护:最后一道兜底,基于机器整体负载 ===
// 前面的规则都要"预先设阈值",但容量会变(GC、其他服务抢资源)
// 系统规则:不管具体接口,当机器整体扛不住时,自动拒绝入口流量
public void initSystemRule() {
    List<SystemRule> rules = new ArrayList<>();

    SystemRule load = new SystemRule();
    load.setHighestSystemLoad(8.0);   // 系统 load1 阈值(参考核数)
    rules.add(load);

    SystemRule cpu = new SystemRule();
    cpu.setHighestCpuUsage(0.8);      // CPU 使用率超 80% → 开始保护
    rules.add(cpu);

    SystemRule qps = new SystemRule();
    qps.setQps(20000);               // 入口总 QPS 上限
    rules.add(qps);

    SystemRule rt = new SystemRule();
    rt.setAvgRt(500);                // 平均 RT 超 500ms → 保护
    rules.add(rt);

    SystemRuleManager.loadRules(rules);
}
// 系统保护是"自适应"的:它不需要你算准容量,而是看机器实际状态
// 真过载了就拦,是规则没配全时的最后保险
# === 网关层流控:在 Spring Cloud Gateway 用 Sentinel,把限流前移 ===
# 流量在网关就被挡掉,根本不进到下游业务服务,保护更彻底
spring:
  cloud:
    sentinel:
      transport:
        dashboard: sentinel-dashboard:8080   # 控制台地址
      # 网关流控规则(也可在 Dashboard 动态配)
      scg:
        fallback:
          mode: response
          response-status: 429
          response-body: '{"code":429,"msg":"请求过于频繁,请稍后再试"}'
---
# 网关 API 分组 + 限流:对 /api/seckill/** 这组路由限流
# 在 Dashboard 配:resource=seckill_route, grade=QPS, count=15000
# 配合 routeId 或自定义 API 分组,粒度灵活

修复 6:监控告警

# Sentinel 指标接 Prometheus(sentinel + micrometer 暴露)
groups:
- name: sentinel-flow
  rules:
  # 1. 限流触发量(被 block 的请求数)
  - alert: FlowBlockedHigh
    expr: rate(sentinel_block_qps{resource="createOrder"}[1m]) > 1000
    for: 2m
    annotations:
      summary: "{{ $labels.resource }} 限流 block > 1000/s,可能遭刷或容量不足"

  # 2. 熔断触发(资源进入熔断状态)
  - alert: CircuitBreakerOpen
    expr: sentinel_circuit_breaker_state{state="open"} == 1
    for: 1m
    annotations:
      summary: "{{ $labels.resource }} 已熔断,排查下游慢/异常"

  # 3. 资源 RT 飙高(熔断前兆)
  - alert: ResourceRtHigh
    expr: sentinel_resource_rt{resource="riskCheck"} > 300
    for: 3m
    annotations:
      summary: "{{ $labels.resource }} RT > 300ms,接近慢调用熔断阈值"

  # 4. 系统自适应保护触发(整体过载)
  - alert: SystemProtectionTriggered
    expr: rate(sentinel_system_block_qps[1m]) > 0
    for: 1m
    annotations:
      summary: "{{ $labels.instance }} 系统保护已触发,机器整体过载"

  # 5. 热点参数限流命中(防刷生效,也可能误伤)
  - alert: HotParamBlockedHigh
    expr: rate(sentinel_param_block_qps[1m]) > 500
    for: 2m
    annotations:
      summary: "{{ $labels.resource }} 热点限流 > 500/s,确认是防刷还是误伤"

优化效果

指标                      治理前          治理后
=============================================================
峰值承接 QPS              21 万(崩溃)    网关挡掉,业务侧 2 万
黑产脚本流量              畅通无阻         热点限流拦掉 99%
下游风控变慢时            线程全卡死       熔断 + 降级,链路不雪崩
正常用户下单成功率        37%             99.2%
服务过载保护              无               系统规则自适应兜底
活动期间宕机              整体崩溃         零宕机
降级预案                  无               风控降级 + 友好提示

压测(模拟 25 万 QPS,含 80% 刷子流量):
- 治理前:3 分钟服务崩溃
- 治理后:刷子被网关 + 热点限流逐层削掉,业务侧平稳运行
         正常用户 P99 80ms,无失败

接入与改造:
- 接入 Sentinel + Dashboard:1 天
- 流控 + 熔断规则梳理与配置:2 天
- 热点参数限流 + 防刷:1 天
- 网关流控 + 系统保护:1 天
- 全链路压测(故障 + 刷子注入):2 天

避坑清单

  1. 核心接口必须配 QPS 流控,阈值由压测得出,绝不"来多少收多少"
  2. 三种控制效果分清:稳定容量用直接拒绝,冷启动用 Warm Up,削峰用排队
  3. blockHandler 处理限流/熔断 Block,fallback 处理业务异常,别混用
  4. 熔断优先用"慢调用比例",配 minRequestAmount 防低流量误判
  5. 防刷靠热点参数限流,对单 userId/itemId 限流,接口级限流挡不住分散刷
  6. 热点规则给 VIP/内部账号配例外,避免误伤正常大客户
  7. 系统自适应保护是兜底,不需算准容量,过载自动拦,务必开启
  8. 限流尽量前移到网关,流量不进业务服务,保护更彻底
  9. 每个降级 fallback 都要有真实的兜底逻辑,不能只是抛异常
  10. 限流量、熔断状态、系统保护触发都要上监控,区分防刷生效与误伤

总结

这次秒杀被刷崩的事故,让我对"限流"这件事的认识彻底变了。过去总觉得限流是个可有可无的"锦上添花",真正想明白后才发现:它是高并发系统的底线防御,没有它,系统就是不设防的城池,一波超出容量的流量——无论来自真实用户还是黑产——都能轻易把它推倒。最大的认知改变是理解了限流的层次性:它不是配一条 QPS 规则就完事,而是一套从粗到细、从外到内的纵深防御。网关层做总量管控,把洪峰挡在业务系统之外;接口层做 QPS 流控,守住单个资源的容量红线;热点参数限流做精准防刷,锁定那些"单个用户、单个商品刷得太猛"的异常值——这一招最关键,因为接口级限流对"分散成海量请求的刷子"无能为力,而热点限流能精准把单个账号摁到 5 QPS,正常用户却毫无感知;最后系统自适应保护兜底,不用你算准容量,机器真扛不住时自动拦。第二个深刻的体会是熔断和降级是一体两面:熔断负责"快速失败",当下游变慢时果断切断,避免线程全部堆积在等待上引发雪崩;降级负责"优雅失败",熔断之后不能让用户看到白屏或 500,而要返回有意义的兜底——小额订单自动放行、大额转人工审核,这才是对业务负责。归根结底,流量治理的哲学是承认"系统容量有限"这个事实,与其在过载时整体崩溃、所有人都用不了,不如主动拒绝一部分、保住核心链路让大多数人可用。能限流的系统才是能在大促活下来的系统。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
技术教程

Redis 缓存雪崩把数据库打挂:穿透、击穿、雪崩与一致性治理实录

2026-5-20 12:23:30

技术教程

Elasticsearch 查询从 3 秒到 50 毫秒:分片、mapping 与深分页治理实录

2026-5-20 12:28:24

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索