一、自定义限流效果
如果我们不对Sentinel的异常提示做自定义,那么此时的提示是非常不详细的。
正常情况下的:
如果做了自定义,就可以看到下面自定义的异常提示:
相关指标数据信息:
二、限流aop增强实现方式
因此我们可以看到Sentinel里面是有对Sentinel的增强的。Sentinel基于aop的增强:
看到下面的代码中,带有@SentinelResource这个注解。
1.带SentinelResource注解,写好对应的fallback方法进行自定义处理
@Override
@SentinelResource(value="hello", fallback="helloFallback")
publicStringhello(longs) {
if (s<0) {
thrownewIllegalArgumentException("invalid arg");
}
returnString.format("Hello at %d", s);
}
//自定义处理的方法
publicStringhelloFallback(longs, Throwableex) {
ex.printStackTrace();
return"Oops, error occurred at "+s;
}
可以看到自定义的处理方法helloFallback。
2.采用blockHandler为异常的处理方法,blockHandlerClass为异常处理的类
@Override
@SentinelResource(value="test", blockHandler="handleException", blockHandlerClass= {ExceptionUtil.class})
publicvoidtest() {
System.out.println("Test");
}
3.采用自定义的处理方法,同时忽略异常的类
@Override
@SentinelResource(value="helloAnother", defaultFallback="defaultFallback",
exceptionsToIgnore= {IllegalStateException.class})
publicStringhelloAnother(Stringname) {
if (name==null||"bad".equals(name)) {
thrownewIllegalArgumentException("oops");
}
if ("foo".equals(name)) {
thrownewIllegalStateException("oops");
}
return"Hello, "+name;
}
三、执行aop增强的实现
此时我们停下来想一想,如果想要实现对对应的方法进行增强,需要做哪些操作?
- 1.首先基于注解做切面
- 2.在切点中拿到originMethod.getAnnotation(SentinelResource.class)拿到带SentinelResource注解的class的请求
- 3.根据拿到的注解信息去拿当前请求的资源名称和entry类型
- 4.执行entry请求 entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());处理完成
- 5.如果当前的处理出现了异常,会根据对应的注解的方法执行处理,匹配异常,如果是block异常,处理异常handleBlockException;如果是Throwable异常,忽略对应的异常,然后执行handleFallback
- 6.完成处理之后,执行exit操作 entry.exit(1, pjp.getArgs());
可以看到其是基于切面实现的
@Around("sentinelResourceAnnotationPointcut()")
publicObjectinvokeResourceWithSentinel(ProceedingJoinPointpjp) throwsThrowable {
MethodoriginMethod=resolveMethod(pjp);
SentinelResourceannotation=originMethod.getAnnotation(SentinelResource.class);
// 获取资源信息
StringresourceName=getResourceName(annotation.value(), originMethod);
EntryTypeentryType=annotation.entryType();
intresourceType=annotation.resourceType();
Entryentry=null;
try {
// 执行entry
entry=SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());
returnpjp.proceed();
} catch (BlockExceptionex) {
returnhandleBlockException(pjp, annotation, ex);
} catch (Throwableex) {
Class<?extendsThrowable>[] exceptionsToIgnore=annotation.exceptionsToIgnore();
// The ignore list will be checked first.
if (exceptionsToIgnore.length>0&&exceptionBelongsTo(ex, exceptionsToIgnore)) {
throwex;
}
if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) {
traceException(ex);
returnhandleFallback(pjp, annotation, ex);
}
// No fallback function can handle the exception, so throw it out.
throwex;
} finally {
if (entry!=null) {
entry.exit(1, pjp.getArgs());
}
}
}
debug进去,可以看到切面中相关注解信息annotation:
@com.alibaba.csp.sentinel.annotation.SentinelResource(blockHandler=handleException, entryType=OUT, fallbackClass=[], exceptionsToIgnore=[], exceptionsToTrace=[classjava.lang.Throwable], defaultFallback=, value=test, fallback=, blockHandlerClass=[classcom.alibaba.csp.sentinel.demo.annotation.aop.service.ExceptionUtil], resourceType=0)
我们可以看到里面有我们熟悉的两个重要方法:
SphU.entry
entry.exit
entry=SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());
entry.exit(1, pjp.getArgs())
可以看到entry操作首先是拿上下文的信息,执行处理链操作,这个过程中会涉及到spi的加载,加载对应的Slot,形成sentinel的责任链骨架。有了这个骨架就可执行对应的责任链的操作了,也即执行entry操作。
Contextcontext=ContextUtil.getContext();
ProcessorSlot<Object>chain=lookProcessChain(resourceWrapper);
Entrye=newCtEntry(resourceWrapper, chain, context);
chain.entry(context, resourceWrapper, null, count, prioritized, args);
可以看到对应的责任链操作:
spi形成对应的Slot之后,放入到map中,执行entry操作,形成责任链模式。这个流程的操作是按照下面这个顺序执行的:
我们之所以能够看到界面上的数据,那么需要统计之后,才会在界面上显示,因此StatisticSlot的子类DefaultNode里面进行了统计数据操作。
com.alibaba.csp.sentinel.slots.statistic.StatisticSlot#entry
统计的数据信息在这里可以看到会添加到node中,最终node的数据会设置到上下文context中。
四、相关校验规则check
1.白名单认证check
com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot#entry
checkBlackWhiteAuthority(resourceWrapper, context);
2.系统自适应check
com.alibaba.csp.sentinel.slots.system.SystemSlot#entry
SystemRuleManager.checkSystem(resourceWrapper, count);
3.限流check
com.alibaba.csp.sentinel.slots.block.flow.FlowSlot#entry
checkFlow(resourceWrapper, context, node, count, prioritized);
4.降级隔离check
com.alibaba.csp.sentinel.slots.block.degrade.DegradeSlot#entry
performChecking(context, resourceWrapper);
在限流中,check的过程中,会首先会根据对应的规则和策略拿到rule=>Node selectedNode = selectNodeByRequesterAndStrategy(rule, context, node);,通过ruleManager拿到。拿到之后,和通过当前拿到的数据信息和内存中的数据对比,从而判断是是否pass。
限流的接口:
com.alibaba.csp.sentinel.slots.block.flow.TrafficShapingController#canPass(com.alibaba.csp.sentinel.node.Node, int, boolean)
可以看到限流的方式有四种:
Default 默认限流方式,快速失败限流
RateLimiter 匀速限流
WarmUp 冷启动限流
WarmUpRateLimiter 冷启动匀速限流
限流涉及到滑动窗口的知识和相关限流的算法。
执行完成之后,就可执行exit操作了。