基于调用关系的流量控制。调用关系包括调用方、被调用方;一个方法可能会调用其它方法,形成一个调用链路的层次关系。

直接

超过阈值直接失败

image.png

关联

当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。比如对数据库同一个字段的读操作和写操作存在争抢,读的速度过高会影响写得速度,写的速度过高会影响读的速度。如果放任读写操作争抢资源,则争抢本身带来的开销会降低整体的吞吐量。可使用关联限流来避免具有关联关系的资源之间过度的争抢,举例来说,read_db 和 write_db 这两个资源分别代表数据库读写,我们可以给 read_db 设置限流规则来达到写优先的目的:设置 strategy 为 RuleConstant.STRATEGY_RELATE 同时设置 refResource 为 write_db。这样当写库操作过于频繁时,读数据的请求会被限流。

image.png
说简单点就是 我关联资源/user/info 达到单机阈值2之后,对上面配置的资源名/user/findOrderByUserId 进行限流

链路

根据调用链路入口限流。
NodeSelectorSlot 中记录了资源之间的调用链路,这些资源通过调用关系,相互之间构成一棵调用树。这棵树的根节点是一个名字为 machine-root 的虚拟节点,调用链的入口都是这个虚节点的子节点。
一棵典型的调用树如下图所示:

  1. machine-root
  2. / \
  3. / \
  4. Entrance1 Entrance2
  5. / \
  6. / \
  7. DefaultNode(nodeA) DefaultNode(nodeA)

上图中来自入口 Entrance1 和 Entrance2 的请求都调用到了资源 NodeA,Sentinel 允许只根据某个入口的统计信息对资源限流。

image.png测试会发现链路规则不生效
注意,高版本此功能直接使用不生效,如何解决?

从1.6.3版本开始,Sentinel Web filter默认收敛所有URL的入口context,导致链路限流不生效。 从1.7.0版本开始,官方在CommonFilter引入了WEB_CONTEXT_UNIFY参数,用于控制是否收敛context,将其配置为false即可根据不同的URL进行链路限流。

1.8.0 需要引入sentinel-web-servlet依赖

  1. <!--- 解决流控链路不生效的问题-->
  2. <dependency>
  3. <groupId>com.alibaba.csp</groupId>
  4. <artifactId>sentinel-web-servlet</artifactId>
  5. </dependency>

添加配置类,配置CommonFilter过滤器,指定WEB_CONTEXT_UNIFY=false,禁止收敛URL的入口context

@Configuration
public class SentinelConfig {
    @Bean
    public FilterRegistrationBean sentinelFilterRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new CommonFilter());
        registration.addUrlPatterns("/*");
        // 入口资源关闭聚合   解决流控链路不生效的问题
        registration.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY, "false");
        registration.setName("sentinelFilter");
        registration.setOrder(1);
        return registration;
    }
}

再次测试链路规则,链路规则生效,但是出现异常
流控模式 - 图4
控制台打印FlowException异常
流控模式 - 图5

原因分析:
1. Sentinel流控规则的处理核心是 FlowSlot, 对getUser资源进行了限流保护,当请求QPS超过阈值2的时候,就会触发流控规则抛出FlowException异常
2. 对getUser资源保护的方式是@SentinelResource注解模式,会在对应的SentinelResourceAspect切面逻辑中处理BlockException类型的FlowException异常
(解决方案: 在@SentinelResource注解中指定blockHandler处理BlockException

// UserServiceImpl.java

@Override
@SentinelResource(value = "getUser",blockHandler = "handleException")
public UserEntity getUser(int id){
    UserEntity user = baseMapper.selectById(id);
    return user;
}

public UserEntity handleException(int id, BlockException ex) {
    UserEntity userEntity = new UserEntity();
    userEntity.setUsername("===被限流降级啦===");
    return userEntity;
}

如果此过程没有处理FlowException, AOP就会对异常进行处理,核心代码在CglibAopProxy.CglibMethodInvocation#proceed中,抛出UndeclaredThrowableException异常,属于RuntimeException
流控模式 - 图6
3.异常继续向上抛出,引入CommonFilter后,CommonFilter添加了对异常的处理机制,所以会在CommonFilter中进行处理。
(注意此处对BlockException异常的处理是UrlBlaockHandler的实现类,而在AbstractSentinelInterceptor拦截器中是使用BlockExceptionHandler的实现类处理)
流控模式 - 图7
会抛出一个RuntimeException类型的UndeclaredThrowableException异常,然后打印到控制台显示
流控模式 - 图8
此处又是有坑: FlowException不会被BlockException异常机制处理,因为FlowException已经被封装为RuntimeException类型的UndeclaredThrowableException异常

测试:
自定义CommonFilter对BlockException异常处理逻辑,用于处理经过CommonFilter处理的spring webmvc接口的BlockException

// SentinelConfig.java
@Bean
public FilterRegistrationBean sentinelFilterRegistration() {
    FilterRegistrationBean registration = new FilterRegistrationBean();
    registration.setFilter(new CommonFilter());
    registration.addUrlPatterns("/*");
    // 入口资源关闭聚合  解决流控链路不生效的问题
    registration.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY, "false");
    registration.setName("sentinelFilter");
    registration.setOrder(1);

    //CommonFilter的BlockException自定义处理逻辑
    WebCallbackManager.setUrlBlockHandler(new MyUrlBlockHandler());

    return registration;
}

// UrlBlockHandler的实现类
@Slf4j
public class MyUrlBlockHandler implements UrlBlockHandler {
    @Override
    public void blocked(HttpServletRequest request, HttpServletResponse response, BlockException e) throws IOException {
        log.info("UrlBlockHandler BlockException================"+e.getRule());

        R r = null;

        if (e instanceof FlowException) {
            r = R.error(100,"接口限流了");

        } else if (e instanceof DegradeException) {
            r = R.error(101,"服务降级了");

        } else if (e instanceof ParamFlowException) {
            r = R.error(102,"热点参数限流了");

        } else if (e instanceof SystemBlockException) {
            r = R.error(103,"触发系统保护规则了");

        } else if (e instanceof AuthorityException) {
            r = R.error(104,"授权规则不通过");
        }

        //返回json数据
        response.setStatus(500);
        response.setCharacterEncoding("utf-8");
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        new ObjectMapper().writeValue(response.getWriter(), r);
    }
}

测试,此场景拦截不到BlockException,对应@SentinelResource指定的资源必须在@SentinelResource注解中指定blockHandler处理BlockException

总结: 为了解决链路规则引入ComonFilter的方式,除了此处问题,还会导致更多的问题,不建议使用ComonFilter的方式。 流控链路模式的问题等待官方后续修复,或者使用AHAS。