背景说明:
sentinel具体能干哪些事情请移步官方文档:https://sentinelguard.io/zh-cn/docs/introduction.html
本文章只对使用sentinel过程中可能会碰到问题的点进行源码解读。
1、@SentinelResource 注解解析
这里需要注意点的:
1、fallback和blockHandler的区别,前者是方法异常就会执行的方法,后者是熔断后才会执行的方法。
2、只指明fallbackClass这个不指明fallback 异常后方法不会执行,同理,只指明blockHandlerClass不指明blockHandler ,熔断后方法也不会执行
3、指明了fallbackClass 类,fallback指定的方法必须是静态的,且参数保持一致或者末尾加Throws异常类型变量(非必需)(必需是Throws类型,其他Exception异常都不行),不然也不会执行。
4、指明了blockHandlerClass 类,blockHandler指定的方法必须是静态的,且参数保持一致或者末尾加BlockException异常类型变量(必需)(必需是BlockException类型,其他Exception异常都不行),如果找不到会去找fallback指定的方法,还找不到就找默认的方法,否则不会执行降级
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.annotation;
import com.alibaba.csp.sentinel.EntryType;
import java.lang.annotation.*;
/**
* The annotation indicates a definition of Sentinel resource.
*
* @author Eric Zhao
* @author zhaoyuguang
* @since 0.1.1
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface SentinelResource {
/**
理解为该熔断降级点的主键key,后续配置熔断规则都是使用该名称来查询和收集,不填默认用熔断方法的全名称作为主键key
* @return name of the Sentinel resource
*/
String value() default "";
/**
标记流量类型是入,还是出?
这个没太明白,默认out就好了,暂时没用上,后续用上再看
* @return the entry type (inbound or outbound), outbound by default
*/
EntryType entryType() default EntryType.OUT;
/**
* @return the classification (type) of the resource
这个就是用来标记熔断的是什么类型的方法。web、rpc,还是mq,不重要
* @since 1.7.0
*/
int resourceType() default 0;
/**
注意!!!这个才是熔断后执行的方法,fallback 这个只是方法异常时执行的方法,非熔断
熔断的后执行的方法名称
* @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 {};
/**
设置熔断的方法执行异常后会执行的方法名,该方法要与设置熔断的方法参数保持一致,或者末尾多个Throws 参数也可以。
* @return name of the fallback function, empty by default
*/
String fallback() default "";
/**
当fallback或者blockHandler找不到的时候会默认执行设置的该方法
* 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 "";
/**
设置熔断的方法异常后执行的fallback方法所在的类(跟blockHander不相关)
* 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 {};
/**
这个好,这个属性可以控制哪些熔断方法的异常才会进入到fallback设置的方式去执行
* @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 {};
}
2、SentinelResourceAspect 解析
这个世sentinel的入口,依着这个入口往下看能看到整个sentinel的实际运行流程
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.annotation.aspectj;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import java.lang.reflect.Method;
/**
* Aspect for methods with {@link SentinelResource} annotation.
*
* @author Eric Zhao
*/
@Aspect
public class SentinelResourceAspect extends AbstractSentinelAspectSupport {
//基于SentinelResource注解实现的切面方法@Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)")
public void sentinelResourceAnnotationPointcut() {
}
//基于SentinelResource注解实现的切面方法
@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");
}
//获取annotation的value值或者方法全名,作为熔断资源的主键key
String resourceName = getResourceName(annotation.value(), originMethod);
EntryType entryType = annotation.entryType();
int resourceType = annotation.resourceType();
Entry entry = null;
try {
//是否熔断判断,里面涉及到多个熔断slot熔断规则的执行,一个entry可以理解为一个熔断资源对象
entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());
//这里是实际执行的切面方法,如果上一步命中熔断规则,则直接进入BlockException异常处理,不会再执行该方法
Object result = pjp.proceed();
return result;
} catch (BlockException ex) {
//处理熔断降级,并执行设置的熔断降级方法
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())) {
//标记entry为errorEntry,其实就是把throw对象给保存到entry里面去了
traceException(ex);
//执行fallback设置的方法
return handleFallback(pjp, annotation, ex);
}
// No fallback function can handle the exception, so throw it out.
throw ex;
} finally {
//这个很重要,一次SphU.entry就必需对应一次entry.exit,
//改方法主要是清楚本地缓存,上抛本次方法执行的结果给到setinel服务端
if (entry != null) {
entry.exit(1, pjp.getArgs());
}
}
}
}
3、Entry解析
Entry 使用主要看这个方法:com.alibaba.csp.sentinel.CtSph#entryWithPriority(com.alibaba.csp.sentinel.slotchain.ResourceWrapper, int, boolean, java.lang.Object...)
private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
throws BlockException {
Context context = ContextUtil.getContext();
if (context instanceof NullContext) {
// The {@link NullContext} indicates that the amount of context has exceeded the threshold,
// so here init the entry only. No rule checking will be done.
return new CtEntry(resourceWrapper, null, context);
}
if (context == null) {
// Using default context.
context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);
}
// Global switch is close, no rule checking will do.
if (!Constants.ON) {
return new CtEntry(resourceWrapper, null, context);
}
ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);
/*
* Means amount of resources (slot chain) exceeds {@link Constants.MAX_SLOT_CHAIN_SIZE},
* so no rule checking will be done.
*/
if (chain == null) {
return new CtEntry(resourceWrapper, null, context);
}
Entry e = new CtEntry(resourceWrapper, chain, context);
try {
//这里就是指责链模式去一个个执行每个slot
chain.entry(context, resourceWrapper, null, count, prioritized, args);
} catch (BlockException e1) {
e.exit(count, args);
throw e1;
} catch (Throwable e1) {
// This should not happen, unless there are errors existing in Sentinel internal.
RecordLog.info("Sentinel unexpected exception", e1);
}
return e;
}
4、总结
使用Sentinel 最重要需要注意的一个点:
如果使用了@SentinelResource,则一定要配置BlockHandler,不然就可能会导致命中了熔断,但是服务未降级,表现出来的异常现象就是:该熔断方法不调用了,但是也没有去调用对应的降级方法,如果你在熔断规则里面设置了熔断时间10分钟,则在10分钟内这个方法都会是不可用状态(排除探针探测到服务又可用的状态)
网友评论