引言:当80%的流量集中在20%的数据上

你是否遇到过这样的场景?

场景1:秒杀系统

双11,iPhone 15 Pro Max限量秒杀,1000台库存。

虽然你的商品详情接口做了限流(QPS 10000),但这1万个请求全部集中在这一个商品ID上

其他普通商品无人问津,但这一个热点商品把数据库、缓存、库存服务全部打爆了。

场景2:直播平台

某顶流明星开始直播,500万人涌入直播间。

虽然你的直播接口做了限流(QPS 100000),但这10万个请求全部集中在这一个直播间ID上

其他直播间正常,但这一个热点直播间导致整个直播服务崩溃。

这就是热点数据问题:少数数据承载了绝大部分流量,普通的限流无法精准保护。

这就需要Sentinel的热点参数限流(Hot Param Flow Control)。


什么是热点数据?

热点数据的定义

热点数据是指在一段时间内,访问频率远高于其他数据的数据

典型案例

场景热点数据流量分布
电商秒杀iPhone 15 秒杀商品ID80%流量集中在1个商品
视频平台热门视频ID90%播放量集中在TOP 100视频
社交平台热门话题ID70%讨论集中在热门话题
新闻网站突发新闻ID突发事件发生时,流量暴涨
直播平台顶流主播直播间ID500万人涌入1个直播间

热点数据的特点

  1. 不可预测:不知道哪个数据会成为热点
  2. 时间敏感:热点会随时间变化(如突发新闻)
  3. 流量集中:少数数据占据大部分流量
  4. 影响范围大:一个热点可能拖垮整个系统

普通限流的局限性

问题1:无法区分参数

案例:商品详情接口

@GetMapping("/product/{id}")
public Product getProductDetail(@PathVariable Long id) {
    return productService.getById(id);
}

限流配置

FlowRule rule = new FlowRule();
rule.setResource("getProductDetail");
rule.setCount(10000); // QPS 10000

问题

  • 这个限流是针对整个接口
  • 无论查询哪个商品ID,都共享这个10000的QPS配额
  • 如果1个热点商品ID占据了9000 QPS,其他商品只剩1000 QPS

结果

商品ID实际QPS期望QPS影响
100(秒杀商品)90005000✅ 虽然流量大,但没超限
101(普通商品)5005000❌ 被热点商品挤占
102(普通商品)3005000❌ 被热点商品挤占
103(普通商品)2005000❌ 被热点商品挤占

问题2:无法精准保护

期望

  • 热点商品ID:限流5000 QPS
  • 普通商品ID:不限流(或限流更高)

现实

  • 普通限流只能对整个接口限流
  • 无法针对特定参数值限流

热点参数限流的原理

核心思想

带有特定参数值的请求进行限流,而不是对整个资源限流。

示例

// 对商品详情接口的第1个参数(商品ID)进行限流
// 如果商品ID = 100,限流5000 QPS
// 其他商品ID,不限流

工作流程

请求: GET /product/100
    ↓
提取参数: productId = 100
    ↓
查找热点规则: productId = 100 的限流规则
    ↓
发现规则: QPS 5000
    ↓
统计QPS: 当前QPS = 6000
    ↓
判断: 6000 > 5000,触发限流
    ↓
返回: 限流拒绝

参数索引

重要概念:参数索引(paramIdx)。

Sentinel通过参数索引来识别要限流的参数:

  • 索引从0开始
  • paramIdx = 0:第1个参数
  • paramIdx = 1:第2个参数

示例

@GetMapping("/product/detail")
public Product getDetail(Long productId, String region) {
    // paramIdx = 0:productId
    // paramIdx = 1:region
    return productService.getDetail(productId, region);
}

配置热点参数限流

基础配置

import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager;

import java.util.ArrayList;
import java.util.List;

public class HotParamFlowRuleConfig {

    public static void initHotParamRule() {
        List<ParamFlowRule> rules = new ArrayList<>();

        ParamFlowRule rule = new ParamFlowRule();
        rule.setResource("getProductDetail"); // 资源名
        rule.setParamIdx(0); // 第1个参数(productId)
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS); // QPS模式
        rule.setCount(1000); // 默认QPS阈值:1000

        rules.add(rule);
        ParamFlowRuleManager.loadRules(rules);
        System.out.println("✅ 热点参数限流规则已加载");
    }
}

配置说明

  • resource:资源名称,必须与@SentinelResource的value一致
  • paramIdx:参数索引,0表示第1个参数
  • grade:限流模式,通常使用QPS
  • count默认限流阈值,适用于所有参数值

使用@SentinelResource

重要:热点限流必须配合@SentinelResource使用。

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/product")
public class ProductController {

    /**
     * 商品详情接口
     */
    @GetMapping("/detail")
    @SentinelResource(
        value = "getProductDetail",
        blockHandler = "handleBlock"
    )
    public Product getDetail(@RequestParam Long productId) {
        // 查询商品详情
        return productService.getById(productId);
    }

    /**
     * 限流处理
     */
    public Product handleBlock(Long productId, BlockException ex) {
        Product product = new Product();
        product.setId(productId);
        product.setName("商品访问量过大,请稍后重试");
        return product;
    }
}

高级配置:参数例外项

为什么需要例外项?

场景

  • 大部分商品的限流阈值是1000 QPS
  • 但秒杀商品(ID = 100)预计有10000人抢购,需要更高的阈值5000 QPS
  • 普通商品(ID = 999)是滞销品,访问量很小,不需要限流

需求:针对特定参数值,设置不同的限流阈值

配置例外项

import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowItem;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class HotParamFlowRuleWithException {

    public static void initHotParamRule() {
        List<ParamFlowRule> rules = new ArrayList<>();

        ParamFlowRule rule = new ParamFlowRule();
        rule.setResource("getProductDetail");
        rule.setParamIdx(0); // 第1个参数:productId
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule.setCount(1000); // 默认阈值:1000 QPS

        // 配置例外项
        List<ParamFlowItem> paramFlowItems = new ArrayList<>();

        // 例外项1:商品ID = 100(秒杀商品),限流5000 QPS
        ParamFlowItem item1 = new ParamFlowItem();
        item1.setObject("100"); // 参数值(注意是字符串)
        item1.setClassType(Long.class.getName()); // 参数类型
        item1.setCount(5000); // 例外阈值:5000 QPS
        paramFlowItems.add(item1);

        // 例外项2:商品ID = 101(热门商品),限流3000 QPS
        ParamFlowItem item2 = new ParamFlowItem();
        item2.setObject("101");
        item2.setClassType(Long.class.getName());
        item2.setCount(3000);
        paramFlowItems.add(item2);

        rule.setParamFlowItemList(paramFlowItems);

        rules.add(rule);
        ParamFlowRuleManager.loadRules(rules);
        System.out.println("✅ 热点参数限流规则(带例外项)已加载");
    }
}

效果

商品ID限流阈值说明
1005000 QPS例外项1:秒杀商品
1013000 QPS例外项2:热门商品
其他1000 QPS默认阈值

实战案例:秒杀系统热点保护

场景描述

某电商平台双11秒杀活动:

  • 商品ID = 100:iPhone 15 Pro Max,预计10万人抢购
  • 商品ID = 101-110:其他秒杀商品,预计各5000人抢购
  • 其他商品:正常商品,流量正常

完整代码实现

1. 配置热点限流规则

import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowItem;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager;

import java.util.ArrayList;
import java.util.List;

@Configuration
public class SeckillHotParamConfig {

    @PostConstruct
    public void initSeckillHotParamRule() {
        List<ParamFlowRule> rules = new ArrayList<>();

        ParamFlowRule rule = new ParamFlowRule();
        rule.setResource("seckillProductDetail");
        rule.setParamIdx(0); // 第1个参数:productId
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule.setCount(1000); // 默认阈值:1000 QPS

        // 配置秒杀商品的例外项
        List<ParamFlowItem> items = new ArrayList<>();

        // iPhone 15 秒杀商品
        ParamFlowItem iphone = new ParamFlowItem();
        iphone.setObject("100");
        iphone.setClassType(Long.class.getName());
        iphone.setCount(5000); // 5000 QPS
        items.add(iphone);

        // 其他秒杀商品(101-110)
        for (long i = 101; i <= 110; i++) {
            ParamFlowItem item = new ParamFlowItem();
            item.setObject(String.valueOf(i));
            item.setClassType(Long.class.getName());
            item.setCount(3000); // 3000 QPS
            items.add(item);
        }

        rule.setParamFlowItemList(items);
        rules.add(rule);
        ParamFlowRuleManager.loadRules(rules);

        System.out.println("✅ 秒杀商品热点限流规则已加载");
    }
}

2. Controller层

@RestController
@RequestMapping("/seckill")
public class SeckillController {

    @Autowired
    private SeckillService seckillService;

    /**
     * 秒杀商品详情
     */
    @GetMapping("/product/{id}")
    @SentinelResource(
        value = "seckillProductDetail",
        blockHandler = "seckillBlockHandler"
    )
    public Result getProductDetail(@PathVariable Long id) {
        // 查询商品详情
        Product product = seckillService.getProduct(id);
        return Result.success(product);
    }

    /**
     * 热点限流处理
     */
    public Result seckillBlockHandler(Long id, BlockException ex) {
        return Result.error(429, "商品太火爆了,请稍后再试");
    }
}

3. 压测验证

使用JMeter或wrk压测:

# 压测商品ID = 100(限流5000 QPS)
wrk -t10 -c1000 -d60s "http://localhost:8080/seckill/product/100"

# 压测商品ID = 200(限流1000 QPS)
wrk -t10 -c1000 -d60s "http://localhost:8080/seckill/product/200"

预期结果

商品ID压测QPS限流阈值成功率
10010000500050%
1015000300060%
2002000100050%

实战案例2:直播平台热点保护

场景描述

某直播平台:

  • 顶流主播直播间(roomId = 1):500万人同时在线
  • 普通主播直播间:几千人在线
  • 需要保护热点直播间,防止拖垮整个直播服务

配置规则

@Configuration
public class LiveHotParamConfig {

    @PostConstruct
    public void initLiveHotParamRule() {
        List<ParamFlowRule> rules = new ArrayList<>();

        // 规则1:进入直播间接口
        ParamFlowRule enterRule = new ParamFlowRule();
        enterRule.setResource("enterLiveRoom");
        enterRule.setParamIdx(0); // 第1个参数:roomId
        enterRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        enterRule.setCount(1000); // 默认:1000 QPS

        // 顶流主播直播间
        ParamFlowItem topItem = new ParamFlowItem();
        topItem.setObject("1");
        topItem.setClassType(Long.class.getName());
        topItem.setCount(10000); // 10000 QPS
        enterRule.setParamFlowItemList(Collections.singletonList(topItem));

        rules.add(enterRule);

        // 规则2:发送弹幕接口
        ParamFlowRule sendMsgRule = new ParamFlowRule();
        sendMsgRule.setResource("sendMessage");
        sendMsgRule.setParamIdx(0);
        sendMsgRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        sendMsgRule.setCount(5000); // 默认:5000 QPS

        // 热点直播间的弹幕限流
        ParamFlowItem msgItem = new ParamFlowItem();
        msgItem.setObject("1");
        msgItem.setClassType(Long.class.getName());
        msgItem.setCount(50000); // 50000 QPS
        sendMsgRule.setParamFlowItemList(Collections.singletonList(msgItem));

        rules.add(sendMsgRule);

        ParamFlowRuleManager.loadRules(rules);
        System.out.println("✅ 直播间热点限流规则已加载");
    }
}

热点限流的最佳实践

1. 确定热点参数

步骤

  1. 分析业务场景,识别可能的热点
  2. 通过监控数据,观察参数值的访问分布
  3. 对访问量TOP的参数值配置例外项

工具

  • Sentinel Dashboard:查看实时QPS
  • 日志分析:统计参数访问频率
  • 业务预判:秒杀、突发事件等

2. 阈值设置策略

分层设置

层级阈值说明
默认阈值1000普通参数值
热门阈值3000预期会有较高流量的参数
超热阈值5000-10000秒杀、顶流等超级热点

留有余量

  • 阈值应该是系统承载能力的70-80%
  • 避免设置过低,导致误限流

3. 监控与动态调整

监控指标

# Prometheus指标
sentinel_param_pass_qps{resource="getProductDetail", param_value="100"}
sentinel_param_block_qps{resource="getProductDetail", param_value="100"}

动态调整

  • 通过Sentinel Dashboard动态调整阈值
  • 根据实时流量调整例外项
  • 秒杀结束后及时移除例外项

4. 与其他限流组合使用

第一层:系统保护(CPU、Load)
    ↓
第二层:接口限流(总QPS)
    ↓
第三层:热点参数限流(特定参数值QPS)
    ↓
第四层:依赖熔断

示例

// 第一层:接口总QPS限流
FlowRule flowRule = new FlowRule();
flowRule.setResource("getProductDetail");
flowRule.setCount(10000); // 总QPS 10000

// 第二层:热点参数限流
ParamFlowRule paramRule = new ParamFlowRule();
paramRule.setResource("getProductDetail");
paramRule.setParamIdx(0);
paramRule.setCount(1000); // 默认1000,秒杀商品5000

注意事项

1. 参数类型限制

支持的参数类型

  • 基本类型:int、long、double等
  • 包装类型:Integer、Long、String等

不支持的类型

  • 自定义对象(如Product、Order)
  • 集合类型(如List、Map)

解决方案

// ❌ 不支持
@SentinelResource(value = "xxx")
public void method(Product product) { }

// ✅ 支持
@SentinelResource(value = "xxx")
public void method(Long productId) { }

2. 参数值必须是字符串

在配置例外项时,参数值必须是字符串

// ✅ 正确
item.setObject("100"); // 字符串
item.setClassType(Long.class.getName());

// ❌ 错误
item.setObject(100L); // Long类型

3. 必须使用@SentinelResource

热点限流必须配合@SentinelResource使用,原因是Sentinel需要通过AOP拦截方法参数。

// ❌ 不会生效
@GetMapping("/product/{id}")
public Product getDetail(@PathVariable Long id) { }

// ✅ 会生效
@GetMapping("/product/{id}")
@SentinelResource(value = "getProductDetail")
public Product getDetail(@PathVariable Long id) { }

4. 性能影响

热点限流会有一定的性能开销:

  • 需要提取参数值
  • 需要统计每个参数值的QPS
  • 参数值种类越多,内存占用越大

建议

  • 只对必要的参数做热点限流
  • 定期清理不活跃的参数统计数据

总结

本文我们学习了Sentinel的热点参数限流:

  1. 热点数据问题:少数数据承载绝大部分流量,普通限流无法精准保护
  2. 热点限流原理:对带有特定参数值的请求进行限流
  3. 基础配置:ParamFlowRule、paramIdx、默认阈值
  4. 高级配置:参数例外项,为特定参数值设置不同阈值
  5. 实战案例:秒杀系统、直播平台的热点保护
  6. 最佳实践:确定热点参数、分层设置阈值、监控与调整、组合使用

核心要点

热点参数限流是Sentinel的"精确打击武器",它能针对特定参数值进行限流,精准保护热点数据,避免少数热点拖垮整个系统。

下一篇预告

我们将学习黑白名单与授权规则

  • 什么是黑白名单?
  • 如何实现IP黑名单?
  • 如何实现用户黑名单?
  • 授权规则的原理和配置

让我们一起探索更灵活的访问控制!