熔断调试失败引发的sentinel降级熔断原理探究

sentinel降级熔断原理探究

背景

最近依赖的系统偶尔会出现超时的情况,由于该依赖属于弱依赖,所以笔者决定在依赖系统超时的情况下对该依赖进行熔断降级。

目前使用较多的限流与降低较多的框架是Hystrix与Sentinel。两者的具体对比可以参考sentinel与Hystrix对比

Sentinel支持响应平均RT等多种方式降级,支持公司目前使用的duboo框架,并且引入依赖简单配置即可快速使用,所以本次使用sentinel作为降级工具。

由于之前对sentinel的熔断降级具体原理不甚了解,导致本地调试时没有达到想要的熔断目的。当时满脸问号,源码面前无秘密,决定对它的原理一探究竟。

熔断降级应用

Sentinel通过定义的资源保护具体的业务代码或其他后方服务,用户只需要为受保护的代码或服务定义一个资源,然后定义规则就可以了。常用的SphU.entry("resourceName")会对业务代码造成入侵,一般用注解@SentinelResource的方式。

下面通过一个简单的栗子介绍Sentinel降级的使用方法。
假设我们有个获取用户信息的接口,如下

@RestController
@RequestMapping("/api/user")
public class SentinelTestController {
   

    @Autowired
    private UserService userService;

    @GetMapping("/detail")
    public BaseResponse<User> info(@RequestParam("id") Long userId) {
   
        User user = userService.getDetails(userId);
        return BaseResponse.success(user);
    }
}

获取用户信息的方法userService.getDetails(useId)方法如下,当触发降级时,自动调用blockHandlerMethod方法。

在spring应用中,注意@SentinelResource不能用于内部调用的方法;原因是类的内部方法调用是进入不了aop的。

@Service
public class UserServicesImpl implements UserService {
   

    @Override
    @SentinelResource(value = "user.test", blockHandler = "blockHandlerMethod")
    public User getDetails(Long id) {
   
        try {
   
            //睡眠模拟执行时长
            TimeUnit.MILLISECONDS.sleep(10);
        } catch (InterruptedException e) {
   
            e.printStackTrace();
        }
        return new User(id);
    }
    // 熔断降级对应的处理方法
    public User blockHandlerMethod(Long id, BlockException e) {
   
        System.out.println("blockHandlerMethod invoke");
        return null;
    }
}

这里我们用TimeUnit.MILLISECONDS.sleep(10)来模拟调用依赖方法所需的时间,触发熔断条件。用@SentinelResource定义资源,并对该资源配置相应的降级策略。降级配置如下

{
    "resource": "user.test", //资源名,与@SentinelResource中的value保持一致
    "count": 5, //阈值,当策略为RT时表示5ms
    "grade": 0, //熔断降级策略,支持秒级 RT(0)/秒级异常比例(1)/分钟级异常数(2)
    "timeWindow": 10 //降级的时间,单位为s
}

由于本文使用的sentinel版本小于1.7.0,所以没有rtSlowRequestAmount配置,该配置为RT模式下1 秒内连续多少个请求的平均RT超出阈值方可触发熔断,默认为5。

最后由于本应用是基于Springboot,需要将SentinelResourceAspect注册为一个bean,代码如下

@Configuration
public class SentinelAspectConfiguration {
   

    @Bean
    public SentinelResourceAspect sentinelResourceAspect() {
   
        return new SentinelResourceAspect();
    }
}

以上就完成了对一个方法进行熔断降级的初步栗子。

接下来尝试熔断降级的效果。当连续多次访问接口后,成功触发熔断机制,如下图
达到熔断方法示意图

在这里要说下自己为何最开始在测试环境没有模拟出熔断!由于Sentinel的平均rt超时熔断是基于秒级的,也就是说它会统计滑动窗口1秒内请求的平均耗时,当平均耗时大于设定阈值时,不会马上熔断,而是会将超时通过的passCount加1。当该秒内无请求或平均耗时小于阈值时,passCount会重置为0。只有当passCount大于等于5时,才会触发熔断机制。当时访问的速度和次数不够导致无法熔断。

接下来详细解析。

熔断降级原理

通过一个简单的示例程序,我们了解了sentinel可以对请求进行熔断降价。现在我们就拨开云雾,深入源码内部去一窥sentinel熔断降级的实现原理吧。

@SentinelResource注解解析

首选我们看下@SentinelResource注解,它是Sentinel用于定义资源的注解,并提供了可选的异常处理和 fallback 配置项。该注解源码与解释如下,具体可见官网

@Target({
   ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface SentinelResource {
   
    // Sentinel资源的名称
    String value() default "";
    //资源调用的流量类型,是入口流量EntryType.IN,还是出口流量EntryType.OUT。默认为 EntryType.OUT
    EntryType entryType() default EntryType.OUT;
    //blockHandler对应处理 BlockException 的函数名称,可选项。
    //blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。
    //blockHandler 函数默认需要和原方法在同一个类中
    String blockHandler() default "";
    //当blockHandler函数在其他类中时,需要指明函数对应的类的 Class 对象,并且对应的函数必需为 static 函数。
    Class<?>[] blockHandlerClass() default {
   };
    //fallback 函数名称,用于在抛出异常的时候提供 fallback 处理逻辑。
    //函数返回类型与参数列表需与原函数一致,方法入参可以额外多一个 Throwable 类型的参数用于接收对应的异常。
    String fallback() default "";
    //用于通用的 fallback 逻辑(即可以用于很多服务或方法)
    //若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。
    String defaultFallback() default "";
    //当fallback函数与原方法不在同一个类中,通过fallbackClass指定class
    Class<?>[] fallbackClass() default {
   };
    //用于指定哪些异常被统计
    Class<? extends Throwable>[] exceptionsToTrace() default {
   Throwable.class};
    //用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。
    Class<? extends Throwable>[] exceptionsToIgnore() default {
   };
}

注意当blockHandlerfallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑,之后在源码中可以看出。若未配置 blockHandlerfallbackdefaultFallback,则被限流降级时会将 BlockException直接抛出。

@SentinelResource注解执行流程

在执行@SentinelResource注解的方法前,该注解对应的SentinelResourceAspect切面进行拦截,判定是否触发限流或降级。我们来看下这个切面的实现:

@Aspect
public class SentinelResourceAspect extends AbstractSentinelAspectSupport {
   
    //将@SentinelResource注解定义为切点
    @Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)")
    public void sentinelResourceAnnotationPointcut() {
   
    }

    @Around("sentinelResourceAnnotationPointcut()")
    public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable {
   
        Method originMethod = resolveMethod(pjp);
        //获取注解
        SentinelResource annotation = originMethod.getAnnotation(SentinelResource.class);
        if (annotation == null) {
   
            throw new IllegalStateException("Wrong state for SentinelResource annotation");
        }
        //1. 获取资源名称,@SentinelResource注解上带有value则直接取,否则解析方法名作为资源名
        String resourceName = getResourceName(annotation.value(), originMethod);
        //获取流量类型
        EntryType entryType = annotation.entryType();
        Entry entry = null;
        try {
   
            //2. 申请entry进入资源,如果申请成功,则表明没有限流或降级
            entry = SphU.entry(resourceName, entryType, 1, pjp.getArgs());
            //执行原方法
            Object result = pjp.proceed();
            //返回结果
            return result;
        } catch (BlockException ex) {
   
           //3. entry资源申请不成功,抛出BlockException,根据注解上定义的异常处理函数处理该异常
            return handleBlockException(pjp, annotation, ex);
        //4. 处理非BlockException异常    
        } catch (Throwable ex) {
   
            //获取注解上定义的exceptionsToIgnore
            Class<? extends Throwable>[] exceptionsToIgnore = annotation.exceptionsToIgnore();
            //如果在列表中,则直接抛出,不会计入异常统计中,也不会进入 fallback 逻辑中
            if (exceptionsToIgnore.length > 0 && exceptionBelongsTo(ex, exceptionsToIgnore)) {
   
                throw ex;
            }
            //如果属于注解上的exceptionsToTrace标记的异常,则计入异常统计,并执行fallback 逻辑中
            if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) {
   
                traceException(ex, annotation);
                return handleFallback(pjp, annotation, ex);
            }
            //否则直接抛出
            throw ex;
        } finally {
   
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值