Sentinel之针对复杂请求参数中的成员变量进行热点Key限流

热点参数限流

Overview

我们看看官方对于热点Key限流的描述

何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:

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

热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。

热点参数规则

热点参数规则(ParamFlowRule)类似于流量控制规则(FlowRule):

属性说明默认值
resource资源名,必填
count限流阈值,必填
grade限流模式QPS 模式
durationInSec统计窗口时间长度(单位为秒),1.6.0 版本开始支持1s
controlBehavior流控效果(支持快速失败和匀速排队模式),1.6.0 版本开始支持快速失败
maxQueueingTimeMs最大排队等待时长(仅在匀速排队模式生效),1.6.0 版本开始支持0ms
paramIdx热点参数的索引,必填,对应 SphU.entry(xxx, args) 中的参数索引位置
paramFlowItemList参数例外项,可以针对指定的参数值单独设置限流阈值,不受前面 count 阈值的限制。仅支持基本类型和字符串类型
clusterMode是否是集群参数流控规则false
clusterConfig集群流控相关配置

关键点

我们可以看到在官方的ParamFlowRule中我们只能设置我们需要限流的参数索引

@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey")
public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
                         @RequestParam(value = "p2",required = false) String p2) {
    return "success";
}

也就是以上代码中的p1或者p2参数索引,从0开始

**但是!!!我们在实际开发中通常传参是传的一个复杂类型的对象。**例如下面的接口

/**
 * 同步发送
 *
 * @param dto
 * @return
 */
@PostMapping("/sync/send")
@SentinelResource(value = "sendSyncMsg", blockHandlerClass = SentinelBlockHandlerImpl.class, blockHandler = "limitFlow")
public R sendSyncMsg(@RequestBody UnifySendReqDto dto) {
    log.info("应用调用消息中心接口传参:{}", JSON.toJSONString(dto));
    dto.setSyncIdentify(true);
    if (dto.getDeliveryTime() != null && dto.getDeliveryTime() >= 1) {
        throw new BusinessException("同步发送不支持定时发送功能,请调用异步发送接口");
    }
    return msgPushAppService.sendMsg(dto);
}

这是在实际开发中的调用信息发送接口。可以看到UnifySendReqDto对象,是一个复杂对象,而我们设置热点Key限流只能设置其索引为0

@Data
public class UnifySendReqDto {
/**
     * 应用唯一标识
     */
    private String appCode;

    /**
     * 密钥
     */
    private String secret;
    
    /**
     * 消息内容
     */
    private String content;
          
    /**
     * 接收人集合
     */
    private List<RecipientInfoDto> recipientInfoDtos;
    
    // 省略其他的成员变量......
}

针对RecipientInfoDto中的userId进行限流

@Data
public class RecipientInfoDto {
    /**
     * 接收人飞书id(飞书需要、必传),接收aunid
     */
    private String userId;
    
    // 省略其他的成员变量......
}

如何解决

解决方案很简单,我们可以思考,他是热点key限流。如果是我们,我们要怎么做?

简单结构:

  1. 使用者设置QPS为n
  2. 拿到该接口请求参数
  3. 在1秒内比较参数是否相同
  4. 若相同则出现次数+1,若次数超过了n,则限流

重点是比较

sentinel首先是通过hashCode()去判断这个key是否相同,若相同则继续判断其equals(Object o)是否为true

  • 若hashCode()相同,equals()为true,则相同
  • 若hashCode()相同,equals()为false,则不相同
  • 若hashCode()不同,则不相同

因此针对上述问题,我们只需要在UnifySendReqDto对象中重写equals()和hashCode()方法了

@Override
public boolean equals(Object o) {
    AtomicBoolean flag = new AtomicBoolean(false);
    if (o instanceof UnifySendReqDto) {
        Map<String, RecipientInfoDto> collect = this.recipientInfoDtos.stream().collect(Collectors.toMap(RecipientInfoDto::getUserId, Function.identity(), (o1, o2) -> o1));
        ((UnifySendReqDto) o).getRecipientInfoDtos().forEach(e -> {
            flag.set(!Objects.isNull(collect.get(e.getUserId())));
        });
    }
    return flag.get();
}

@Override
public int hashCode() {
    return 1;
}

因为我们是对集合中的RecipientInfoDto对象中的userId限流,因此hashCode不可能相同,则我们先满足hashCode都相同,根据equals(Object o)来判断对象是否相同。

这样我们就可以对其复杂类型的热点Key限流了。

总结

对于请求参数是一个复杂对象中的某个成员变量限流,我们就可以重新这个类的hashCode()和equals(Object o)方法来判断对象中的热点key是否相同,让sentinel对其限流。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值