跳到主要内容

热点参数限流

热点参数限流是一种更精细化的流量控制方式,它可以根据请求中的参数值进行针对性的限流。比如,对热门商品 ID 进行限流,而对普通商品不限流;对频繁访问的用户 ID 进行限流,而对普通用户不限流。

什么是热点?

热点即经常访问的数据。在很多业务场景中,我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。

典型应用场景:

  • 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
  • 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制
  • IP 地址为参数,限制异常高频访问的 IP

热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。

工作原理

Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。

LRU(Least Recently Used)策略会维护一个固定大小的缓存,记录最近访问的参数值及其访问频率。当新的请求到来时,会更新参数的访问计数,并淘汰最久未访问的参数。

基本使用

引入依赖

要使用热点参数限流功能,需要引入额外的依赖:

<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-parameter-flow-control</artifactId>
<version>1.8.8</version>
</dependency>

定义资源并传入参数

热点参数限流需要在调用 SphU.entry() 时传入参数:

Entry entry = null;
try {
// 传入参数 userId,索引为 0
entry = SphU.entry("getUser", EntryType.IN, 1, userId);
// 业务逻辑
} catch (BlockException e) {
// 被限流
} finally {
if (entry != null) {
// exit 时也要传入相同的参数
entry.exit(1, userId);
}
}

重要说明:

  • 参数通过 SphU.entry(name, type, count, args...) 传入
  • exit() 时必须传入相同的参数,否则会导致统计错误
  • 参数索引从 0 开始

配置热点参数规则

ParamFlowRule rule = new ParamFlowRule();
rule.setResource("getUser"); // 资源名
rule.setParamIdx(0); // 参数索引,对应第 1 个参数
rule.setCount(10); // 每秒最多 10 个请求
rule.setGrade(RuleConstant.FLOW_GRADE_QPS); // QPS 模式

ParamFlowRuleManager.loadRules(Collections.singletonList(rule));

参数例外项

热点参数限流支持针对特定的参数值设置不同的限流阈值。比如,对 VIP 用户不限流,对普通用户限流。

ParamFlowRule rule = new ParamFlowRule();
rule.setResource("getUser");
rule.setParamIdx(0);
rule.setCount(10); // 默认每秒 10 个请求

// 对特定参数值设置例外阈值
ParamFlowItem vipItem = new ParamFlowItem();
vipItem.setObject("vip_user_123"); // 参数值
vipItem.setClassType(String.class.getName()); // 参数类型
vipItem.setCount(100); // VIP 用户每秒 100 个请求

ParamFlowItem adminItem = new ParamFlowItem();
adminItem.setObject("admin");
adminItem.setClassType(String.class.getName());
adminItem.setCount(1000); // 管理员每秒 1000 个请求

rule.setParamFlowItemList(Arrays.asList(vipItem, adminItem));

ParamFlowRuleManager.loadRules(Collections.singletonList(rule));

参数例外项说明:

  • object:参数值,字符串形式
  • classType:参数的完整类名
  • count:该参数值的限流阈值

使用注解方式

使用 @SentinelResource 注解时,方法参数会自动作为热点参数传入:

@SentinelResource(value = "getUser", blockHandler = "handleBlock")
public User getUser(String userId, int type) {
// userId 索引为 0,type 索引为 1
return userService.findById(userId);
}

public User handleBlock(String userId, int type, BlockException e) {
return User.defaultUser();
}

配置规则时,paramIdx 对应方法参数的位置:

// 对第一个参数 userId 进行限流
ParamFlowRule rule = new ParamFlowRule();
rule.setResource("getUser");
rule.setParamIdx(0); // userId
rule.setCount(10);

热点参数规则属性

ParamFlowRule 包含以下属性:

属性说明默认值
resource资源名必填
count限流阈值必填
grade限流模式QPS
paramIdx热点参数的索引必填
paramFlowItemList参数例外项列表-
durationInSec统计窗口时间长度(秒)1
controlBehavior流控效果快速失败
maxQueueingTimeMs最大排队等待时长0
clusterMode是否集群模式false
clusterConfig集群流控配置-

流控效果

热点参数限流支持两种流控效果:

快速失败(默认)

当请求超过阈值时,直接抛出 ParamFlowException

ParamFlowRule rule = new ParamFlowRule();
rule.setResource("getUser");
rule.setParamIdx(0);
rule.setCount(10);
rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT); // 快速失败

匀速排队

请求以均匀的速度通过,适用于削峰填谷场景:

ParamFlowRule rule = new ParamFlowRule();
rule.setResource("getUser");
rule.setParamIdx(0);
rule.setCount(10);
rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER); // 匀速排队
rule.setMaxQueueingTimeMs(500); // 最大排队时间 500ms

代码示例

完整的热点参数限流示例

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.slots.block.BlockException;
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.Arrays;
import java.util.Collections;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class HotParamFlowDemo {

public static void main(String[] args) throws InterruptedException {
initParamFlowRules();

ExecutorService executor = Executors.newFixedThreadPool(10);

String[] users = {"user1", "user2", "vip_user", "admin", "user1", "user1"};

for (int i = 0; i < 100; i++) {
final String userId = users[i % users.length];

executor.submit(() -> {
Entry entry = null;
try {
entry = SphU.entry("getUser", EntryType.IN, 1, userId);
System.out.println("用户 " + userId + " 访问成功");
} catch (BlockException e) {
System.out.println("用户 " + userId + " 被限流");
} finally {
if (entry != null) {
entry.exit(1, userId);
}
}
});

TimeUnit.MILLISECONDS.sleep(50);
}

executor.shutdown();
}

private static void initParamFlowRules() {
ParamFlowRule rule = new ParamFlowRule();
rule.setResource("getUser");
rule.setParamIdx(0);
rule.setCount(5);
rule.setDurationInSec(1);

ParamFlowItem vipItem = new ParamFlowItem();
vipItem.setObject("vip_user");
vipItem.setClassType(String.class.getName());
vipItem.setCount(20);

ParamFlowItem adminItem = new ParamFlowItem();
adminItem.setObject("admin");
adminItem.setClassType(String.class.getName());
adminItem.setCount(Integer.MAX_VALUE);

rule.setParamFlowItemList(Arrays.asList(vipItem, adminItem));

ParamFlowRuleManager.loadRules(Collections.singletonList(rule));
}
}

商品热点限流示例

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

@RestController
@RequestMapping("/api/products")
public class ProductController {

@GetMapping("/{productId}")
@SentinelResource(value = "getProduct", blockHandler = "getProductBlock")
public Product getProduct(@PathVariable String productId) {
return productService.findById(productId);
}

public Product getProductBlock(String productId, BlockException e) {
return Product.defaultProduct();
}
}

注意事项

1. 参数类型限制

若需要配置参数例外项或使用集群维度流控,传入的参数只支持基本类型(int、long、float、double、boolean)和字符串类型。

2. 参数一致性

entry()exit() 传入的参数必须一致,否则会导致统计错误:

// 正确示例
Entry entry = SphU.entry("resource", EntryType.IN, 1, paramA, paramB);
try {
// 业务逻辑
} finally {
entry.exit(1, paramA, paramB); // 相同的参数
}

// 错误示例
entry.exit(1); // 缺少参数,统计会出错

3. 框架适配

目前 Sentinel 自带的适配模块中,只有 Dubbo 方法埋点会自动携带热点参数。其他适配模块(如 Web)默认不支持热点规则,需要自定义埋点。

4. 性能考虑

热点参数限流会维护参数的统计数据,参数值过多会占用较多内存。建议:

  • 控制热点参数的基数(不同值的数量)
  • 合理设置统计窗口时间

最佳实践

1. 识别真正的热点

通过监控数据分析,找出真正的热点参数值,而不是盲目配置。

2. 合理设置例外项

  • VIP 用户可以设置更高的阈值
  • 系统内部调用可以设置例外
  • 测试账号可以设置例外

3. 结合业务场景

  • 秒杀场景:对热门商品 ID 设置更高的阈值
  • 防刷场景:对频繁访问的用户 ID 进行限流
  • API 保护:对高频调用的 API Key 进行限流

下一步