Sentinel源码解析


入口

在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:降级规则

源码图

processon;


文章作者: dm
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 dm !
评论
 上一篇
Sentinel持久化改造 Sentinel持久化改造
前言使用过Sentinel的都知道,只要服务一重启,在sentinel dashboard上编写的规则就会失效。这种现象在生产上肯定是不允许存在的。那么我们有什么办法规避掉这种情况呢。 首先我们有2种思路, 在sentinel dashb
2023-02-05
下一篇 
Sentinel常用算法及简易实现 Sentinel常用算法及简易实现
这篇博文主要是针对Sentinel中间件中常用算法进行源码解析和进行一些简易实现代码 计数器限流算法假设限流1s 1000个请求。定义起始点,每来一个请求计数器加一,到了1000限流,到了1s计数器清空。 优点:实现简单,基本用redis
2022-12-10
  目录