1.注释形式
在sentinel中有个标签 SentinelResource,一般该标签是定义在方法上,demo
public class TestServiceImpl {
@SentinelResource(value = "test", blockHandler = "handleException", blockHandlerClass = {ExceptionUtil.class})
public void test() {
System.out.println("Test hello");
}
}
其中value值是指命名资源的名称,当然,他也做了相应的降级处理,例如Block异常或者其他异常,捕获时,通过标签定义的方法进行执行,相应的降级处理了。
标签属性解读:
value:资源名称
entryType:入口类型,是输入还是输出的
blockHandler:block异常时,调用的方法名称,与blockHandlerClass一起配套使用
blockHandlerClass:block异常时,调用的处理类
fallback:降级处理的方法
defaultFallback:默认的降级处理方法,不带有参数
fallbackClass:降级的处理类,并且该类中的方法都是静态的
如何实现的?
该注解方式通过spring的aop实现的,通过环绕通知的模式进行代理。
SentinelResourceAspect类是实现功能的主要类
@Aspect
public class SentinelResourceAspect extends AbstractSentinelAspectSupport {
@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);
EntryType entryType = annotation.entryType();
Entry entry = null;
try {
entry = SphU.entry(resourceName, entryType, 1, pjp.getArgs());
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())) {
traceException(ex, annotation);
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 进行代理,然后对sentinelResourceAnnotationPointcut方法进行环绕通知,为什么采用环绕通知方式?
因为这与sentinel资源开启和关闭的工作模式有关,当Entry entry = SphU.entry(resourceName, entryType, 1, pjp.getArgs());进行声明资源时,最后都会进行entry.exit()关闭动作。
首先通过切入点ProceedingJoinPoint,得到具体对象中的方法,然后通过该方法得到了SentinelResource注解信息。其中resourceName是由注解的value决定,如果value为空,则会通过方法名,类名,和参数类型名组成一个资源。然后之后就是经典的try{} catch(){} finally{}模式。如果出现BlockException异常,则处理handleBlockException的方法,如果是其他异常,要么进行降级处理,要么直接抛出异常,最后结束时entry.exit.
2.Dubbo中的使用
考虑一下,如何让sentinel功能侵入少,又容易维护管理的嵌入到常用的框架中,例如web-servlet,zuul,dubbo他们本身都有filter过滤链的概念,所以最好在filter过滤逻辑中,进行嵌入,这样代码侵入少,利于维护等。在dubbo中有针对生产者,和消费者进行filter过滤。如果处理这些filter过滤呢?消费者,是请求资源,主要是往外发送请求的,那么在入口资源判定时,需要用EntryType.OUT标识,那么生产者往往是进入的请求资源,需要EntryType.IN标识。
@Activate(group = "provider")
public class SentinelDubboProviderFilter implements Filter {
public SentinelDubboProviderFilter() {
RecordLog.info("Sentinel Apache Dubbo provider filter initialized");
}
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// Get origin caller.
String application = DubboUtils.getApplication(invocation, "");
Entry interfaceEntry = null;
Entry methodEntry = null;
try {
String resourceName = DubboUtils.getResourceName(invoker, invocation);
String interfaceName = invoker.getInterface().getName();
// Only need to create entrance context at provider side, as context will take effect
// at entrance of invocation chain only (for inbound traffic).
ContextUtil.enter(resourceName, application);
interfaceEntry = SphU.entry(interfaceName, EntryType.IN);
methodEntry = SphU.entry(resourceName, EntryType.IN, 1, invocation.getArguments());
Result result = invoker.invoke(invocation);
if (result.hasException()) {
Throwable e = result.getException();
// Record common exception.
Tracer.traceEntry(e, interfaceEntry);
Tracer.traceEntry(e, methodEntry);
}
return result;
} catch (BlockException e) {
return DubboFallbackRegistry.getProviderFallback().handle(invoker, invocation, e);
} catch (RpcException e) {
Tracer.traceEntry(e, interfaceEntry);
Tracer.traceEntry(e, methodEntry);
throw e;
} finally {
if (methodEntry != null) {
methodEntry.exit(1, invocation.getArguments());
}
if (interfaceEntry != null) {
interfaceEntry.exit();
}
ContextUtil.exit();
}
}
}
在dubbo中有个显著的区别,他分别申请了2中支援,一种是针对接口资源限定的,一种是针对具体方法资源限定的。还有一个是确定了Context上下文,context一般是默认创建的,但是当ContextUtil.enter(resourceName, application);进行声明时,就会在ThreadLocal中创建了Context,并且有个origin属性是 application,该application即为请求方的唯一标识,所以可以怎么办?可以针对某一个具体服务器请求进行限制限流。
普通Servlet过滤
通常在开发web时,都会添加过滤器,例如有字符过滤,有权限过滤,他们都需要实现javax.servlet.Filter接口,所以也和其他的拓展功能类似。
CommonFilter实现类中doFilter方法
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest sRequest = (HttpServletRequest) request;
Entry entry = null;
Entry methodEntry = null;
try {
String target = FilterUtil.filterTarget(sRequest);
// Clean and unify the URL.
// For REST APIs, you have to clean the URL (e.g. `/foo/1` and `/foo/2` -> `/foo/:id`), or
// the amount of context and resources will exceed the threshold.
UrlCleaner urlCleaner = WebCallbackManager.getUrlCleaner();
if (urlCleaner != null) {
target = urlCleaner.clean(target);
}
// Parse the request origin using registered origin parser.
String origin = parseOrigin(sRequest);
ContextUtil.enter(target, origin);
entry = SphU.entry(target, EntryType.IN);
// Add method specification if necessary
if (httpMethodSpecify) {
methodEntry = SphU.entry(sRequest.getMethod().toUpperCase() + COLON + target,
EntryType.IN);
}
chain.doFilter(request, response);
} catch (BlockException e) {
HttpServletResponse sResponse = (HttpServletResponse) response;
// Return the block page, or redirect to another URL.
WebCallbackManager.getUrlBlockHandler().blocked(sRequest, sResponse, e);
} catch (IOException e2) {
Tracer.trace(e2);
throw e2;
} catch (ServletException e3) {
Tracer.trace(e3);
throw e3;
} catch (RuntimeException e4) {
Tracer.trace(e4);
throw e4;
} finally {
if (methodEntry != null) {
methodEntry.exit();
}
if (entry != null) {
entry.exit();
}
ContextUtil.exit();
}
}
web中的filter过滤,可以获取到的是请求路径path,以path为资源入口,并且origin可以通过在http请求header中添加约定参数,作为origin来源。其他工作逻辑方式也差不多