Sentenel对Spring框架支持

我们经常使用@SentinelResource来标记一个方法,可以将这个被@SentinelResource标记的方法看成是一个Sentinel资源

因此,我们以@ SentinelResource为入口,找到其切面,看看切面拦截后所做的工作,来明确Sentinel的工作原理。直接看注解@SentinelResource的切面代码(SentinelResourceAspect)。

(1) Spring中Sentinel使用

<!-- sentinel核心模块-->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
    <version>1.8.6</version>
</dependency>

<!-- sentinel注解 spring里会用到 -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-annotation-aspectj</artifactId>
    <version>1.8.6</version>
</dependency>     

配置对应Bean

@Configuration
public class SentinelAspectConfiguration {

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

}

(1.1) 限流


/**
 * 下单接口
 */
@PostMapping(value = "/order/create", consumes = MediaType.APPLICATION_JSON_VALUE)
@SentinelResource(value = "orderCreate", blockHandler = "blockHandlerForOrderCreate")
public OrderResponse<OrderCreateVo> orderCreate(@RequestBody OrderCreateRequest req) {
    log.info("下单请求开始||req={}", req);
    OrderResponse<OrderCreateVo> res = orderCreateService.createOrder(req);
    log.info("下单请求结束||res={}", res);
    return res;
}

/**
 * 限流
 *
 * @param req OrderCreateRequest
 * @param e BlockException
 * @return
 */
public OrderResponse<OrderCreateVo> blockHandlerForOrderCreate(@RequestBody OrderCreateRequest req, BlockException e) {
    log.info("下单达到阈值被限流 || ");
    return OrderResponse.fail(BizErrorCodeEnum.SYSTEM_BUSY.getCode(), BizErrorCodeEnum.SYSTEM_BUSY.getDescription());
}

(1.2) 熔断


/**
 * @param goodsBizReq
 * @return
 */
@Override
@SentinelResource(value = "order_getGoodsInfo", fallback = "fallbackForGetGoodsInfo")
public List<GoodsBizResEntity> getGoodsInfo(GoodsRequestBizEntity goodsBizReq) {

    // 省略部分代码
    
    return goodsBizList;
}

/**
 * 熔断
 *
 * @param goodsBizReq
 * @param ex
 * @return
 */
public List<GoodsBizResEntity> fallbackForGetGoodsInfo(GoodsRequestBizEntity goodsBizReq, BlockException ex) {
    log.warn("查询商品触发熔断||");
    // 走熔断逻辑 调getGoodsInfoV2
    return getGoodsInfoV2(goodsBizReq);
}

(2) 源码解析

(2.1) SentinelResourceAspect

源码: https://github.com/alibaba/Sentinel/blob/1.8.6/sentinel-extension/sentinel-annotation-aspectj/src/main/java/com/alibaba/csp/sentinel/annotation/aspectj/SentinelResourceAspect.java

package com.alibaba.csp.sentinel.annotation.aspectj;

/**
 * SentinelResource注解切面
 */
@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) {
            // Should not go through here.
            throw new IllegalStateException("Wrong state for SentinelResource annotation");
        }
        // 资源名 使用注解里配置的资源名或者方法名
        String resourceName = getResourceName(annotation.value(), originMethod);
        // 注解里没配置 默认是OUT
        EntryType entryType = annotation.entryType();
        // 默认为0
        int resourceType = annotation.resourceType();
        Entry entry = null;
        try {
            // 进行流控
            entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());
            // 通过流控检查后,才会调用目标方法
            return pjp.proceed();
        } catch (BlockException ex) {
            // 对BlockException进行回调
            return handleBlockException(pjp, annotation, ex);
        } catch (Throwable ex) {
            Class<? extends Throwable>[] exceptionsToIgnore = annotation.exceptionsToIgnore();
            // The ignore list will be checked first.
            if (exceptionsToIgnore.length > 0 && exceptionBelongsTo(ex, exceptionsToIgnore)) {
                throw ex;
            }
            if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) {
                traceException(ex);
                // 异常回调
                return handleFallback(pjp, annotation, ex);
            }

            // No fallback function can handle the exception, so throw it out.
            throw ex;
        } finally {
            if (entry != null) {
                entry.exit(1, pjp.getArgs());
            }
        }
    }

}

进入SentinelResource切面后,会执行SphU.entry()方法,在这个方法中会对被拦截方法做限流和熔断的逻辑处理。
如果触发限流或熔断,会抛出BlockException,我们可以指定blockHandler方法来处理BlockException。而对于业务上的异常,我们也可以配置fallback方法来处理被拦截方法调用产生的异常。

public abstract class AbstractSentinelAspectSupport {

    /** 获取资源名 */
    protected String getResourceName(String resourceName, /*@NonNull*/ Method method) {
        // 注解里配置了资源名,使用此值
        if (StringUtil.isNotBlank(resourceName)) {
            return resourceName;
        }
        // 解析方法名 使用方法名作为资源名
        return MethodUtil.resolveMethodName(method);
    }

}

(2.2) SentinelResource

package com.alibaba.csp.sentinel.annotation;


/**
 * 注释表示Sentinel资源的定义。
 *
 * @since 0.1.1
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface SentinelResource {

    /**
     * 返回Sentinel资源的名称
     */
    String value() default "";

    /**
     * @return the entry type (inbound or outbound), outbound by default
     */
    EntryType entryType() default EntryType.OUT;

    /**
     * @return the classification (type) of the resource
     * @since 1.7.0
     */
    int resourceType() default 0;

    /**
     * @return name of the block exception function, empty by default
     */
    String blockHandler() default "";

    /**
     * The {@code blockHandler} is located in the same class with the original method by default.
     * However, if some methods share the same signature and intend to set the same block handler,
     * then users can set the class where the block handler exists. Note that the block handler method
     * must be static.
     *
     * @return the class where the block handler exists, should not provide more than one classes
     */
    Class<?>[] blockHandlerClass() default {};

    /**
     * @return name of the fallback function, empty by default
     */
    String fallback() default "";

    /**
     * The {@code defaultFallback} is used as the default universal fallback method.
     * It should not accept any parameters, and the return type should be compatible
     * with the original method.
     *
     * @return name of the default fallback method, empty by default
     * @since 1.6.0
     */
    String defaultFallback() default "";

    /**
     * The {@code fallback} is located in the same class with the original method by default.
     * However, if some methods share the same signature and intend to set the same fallback,
     * then users can set the class where the fallback function exists. Note that the shared fallback method
     * must be static.
     *
     * @return the class where the fallback method is located (only single class)
     * @since 1.6.0
     */
    Class<?>[] fallbackClass() default {};

    /**
     * @return the list of exception classes to trace, {@link Throwable} by default
     * @since 1.5.1
     */
    Class<? extends Throwable>[] exceptionsToTrace() default {Throwable.class};
    
    /**
     * Indicates the exceptions to be ignored. Note that {@code exceptionsToTrace} should
     * not appear with {@code exceptionsToIgnore} at the same time, or {@code exceptionsToIgnore}
     * will be of higher precedence.
     *
     * @return the list of exception classes to ignore, empty by default
     * @since 1.6.0
     */
    Class<? extends Throwable>[] exceptionsToIgnore() default {};
}

参考资料

[1] 主流框架的适配