入口
在Springcloud中引入sentinel我们发现只是引入了一个jar包就完事了,说明肯定是通过SpringBoot自动装配将sentinel引入进来,既然这样,那么我没找到spring.factories就能找到自动装配的类
spring-cloud-alibaba-sentinel.jar打开找到spring.factories(版本不一样位置可能不同)
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.cloud.sentinel.SentinelWebAutoConfiguration,\
com.alibaba.cloud.sentinel.SentinelWebFluxAutoConfiguration,\
com.alibaba.cloud.sentinel.endpoint.SentinelEndpointAutoConfiguration,\
com.alibaba.cloud.sentinel.custom.SentinelAutoConfiguration,\
com.alibaba.cloud.sentinel.feign.SentinelFeignAutoConfiguration
org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker=\
com.alibaba.cloud.sentinel.custom.SentinelCircuitBreakerConfiguration
在Springcloud gateway使用的是webflux,所以我们猜测很有可能使用的是SentinelWebFluxAutoConfiguration,如果是普通应用使用的是web servlet环境那么应该使用的是SentinelWebAutoConfiguration,由于我们对Web环境更加熟悉,我们这里直接看普通web环境所引入的SentinelWebAutoConfiguration
SentinelWebAutoConfiguration
这里自动装配代码很简单,就是注入了一个Bean,并将这个bean绑定到web容器的拦截器中。所以我们在普通Web环境中解决Sentinel的方法是通过拦截器解决的(通过注解形式@SentinelResources方法是通过AOP解决的,SentinelResourceAspect这里暂不做解释)
注意点:这里有个小问题,可能是我得sentinel版本问题。在设置拦截器路径匹配的时候他设置的路径规则是/*,而这便会导致某些路径不会被拦截器拦截,所以我们需要配置文件加上
spring.cloud.sentinel.filter.url-patterns=/**
@Autowired
private Optional<SentinelWebInterceptor> sentinelWebInterceptorOptional;
@Override
public void addInterceptors(InterceptorRegistry registry) {
if (!sentinelWebInterceptorOptional.isPresent()) {
return;
}
SentinelProperties.Filter filterConfig = properties.getFilter();
// 将下面注册的bean绑定在拦截器中
registry.addInterceptor(sentinelWebInterceptorOptional.get())
.order(filterConfig.getOrder())
// 默认路径匹配为/*存在问题,配置文件重新配置
.addPathPatterns(filterConfig.getUrlPatterns());
log.info(
"[Sentinel Starter] register SentinelWebInterceptor with urlPatterns: {}.",
filterConfig.getUrlPatterns());
}
// 这里引入了一个SentinelWebInterceptor的Bean,而这个bean是被sentinelWebInterceptorOptional所注入。
// 在addInterceptors中通过sentinelWebInterceptorOptional.get()方法将这个bean取出来
@Bean
@ConditionalOnProperty(name = "spring.cloud.sentinel.filter.enabled",
matchIfMissing = true)
public SentinelWebInterceptor sentinelWebInterceptor(
SentinelWebMvcConfig sentinelWebMvcConfig) {
return new SentinelWebInterceptor(sentinelWebMvcConfig);
}
SentinelWebInterceptor
我们知道拦截器通常是有一个preHandle方法,在SentinelWebInterceptor中这个方法在它的抽象父类中。
这里就是获取资源并对资源进行限流保护核心代码其实就一句 Entry entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.IN);申请一个entry 申请成功则成功失败既限流。
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
try {
String resourceName = getResourceName(request);
if (StringUtil.isNotEmpty(resourceName)) {
// Parse the request origin using registered origin parser.
String origin = parseOrigin(request);
ContextUtil.enter(SENTINEL_SPRING_WEB_CONTEXT_NAME, origin);
// 核心代码
// 申请一个entry资源,申请成功则通过请求,申请失败就进行限流熔断保护
Entry entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.IN);
setEntryInRequest(request, baseWebMvcConfig.getRequestAttributeName(), entry);
}
return true;
} catch (BlockException e) {
handleBlockException(request, response, e);
return false;
}
}
SphU#entry
从这里一直往下跟会到CtSph#entryWithPriority这个方法,这便是一切的开始。
private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
throws BlockException {
// =======省略代码=========
// 构造一个Slot链条,使用责任链的方式进行调用
ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);
// =======省略代码=========
Entry e = new CtEntry(resourceWrapper, chain, context);
try {
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;
}
lookProcessChain
这里主要是构建后续调用链
NodeSelectorSlot->ClusterBuilderSlot->LogSlot->StatisticSlot->ParamFlowSlot->SystemSlot->AuthoritySlot->FlowSlot->DegradeSlot
ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
// 先看这个资源有没有获取这个调用链
ProcessorSlotChain chain = chainMap.get(resourceWrapper);
// DCL 防并发
if (chain == null) {
synchronized (LOCK) {
chain = chainMap.get(resourceWrapper);
if (chain == null) {
// Entry size limit.
if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) {
return null;
}
// 构建链条,DefaultSlotChainBuilder,GatewaySlotChainBuilder,HotParamSlotChainBuilder三种构建器,通过SPI获取到HotParamSlotChainBuilder
// 链条 NodeSelectorSlot->ClusterBuilderSlot->LogSlot->StatisticSlot->ParamFlowSlot->SystemSlot->AuthoritySlot->FlowSlot->DegradeSlot
chain = SlotChainProvider.newSlotChain();
Map<ResourceWrapper, ProcessorSlotChain> newMap = new HashMap<ResourceWrapper, ProcessorSlotChain>(
chainMap.size() + 1);
// 将链条放打Map中,key-资源,value-链条
newMap.putAll(chainMap);
newMap.put(resourceWrapper, chain);
chainMap = newMap;
}
}
}
return chain;
}
chain.entry
通过上面构造的链条开始调用
entry-执行逻辑,fireEntry-执行下一个Slot,transformEntry-调用entry
大致逻辑就是entry->fireEntry->transformEntry->entry


执行完之后有会在拦截器中的afterCompletion方法中进行退出
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
Entry entry = getEntryInRequest(request, baseWebMvcConfig.getRequestAttributeName());
if (entry != null) {
// 调用chain中的exit方法,和上诉调用链基本一致
traceExceptionAndExit(entry, ex);
removeEntryInRequest(request);
}
ContextUtil.exit();
}
限流逻辑
限流逻辑就藏在每一个Slot中,代码逻辑不复杂,就不详细解释
NodeSelectorSlot:为资源不同的线程创建node
ClusterBuilderSlot:为资源创建集群node
LogSlot:没做什么,打印日志
StatisticSlot:统计数据,这里先调用了下一个链条,等调用完毕进行一些处理,包括并不限于对一些数据进行统计,比如异常数据,通过数据
ParamFlowSlot:热点参数规则
SystemSlot:系统规则
AuthoritySlot:授权规则
FlowSlot:流控规则
DegradeSlot:降级规则
源码图
