Sentinel 源码解析

初始化工作原理

在 spring-cloud-alibaba-sentinel 包中,starter组件会自动装备,所以直接 看 meta-inf/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
  • SentinelWebAutoConfiguration 是对 Web Servlet 环境的支持
  • SentinelWebFluxAutoConfiguration 是对 Spring WebFlux 的支持
  • SentinelEndpointAutoConfiguration 暴露 Endpoint 信息
  • SentinelAutoConfiguration 支持对 RestTemplate 的服务调用使用 Sentinel 进行保护
  •  SentinelFeignAutoConfiguration 用于适配 Feign 组件

SentinelWebAutoConfiguration 为例

@Configuration
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass({CommonFilter.class})
@ConditionalOnProperty(
    name = {"spring.cloud.sentinel.enabled"},
    matchIfMissing = true
)
@EnableConfigurationProperties({SentinelProperties.class})
public class SentinelWebAutoConfiguration {
    private static final Logger log = LoggerFactory.getLogger(SentinelWebAutoConfiguration.class);
    @Autowired
    private SentinelProperties properties;
    @Autowired
    private Optional<UrlCleaner> urlCleanerOptional;
    @Autowired
    private Optional<UrlBlockHandler> urlBlockHandlerOptional;
    @Autowired
    private Optional<RequestOriginParser> requestOriginParserOptional;

    public SentinelWebAutoConfiguration() {
    }

    @PostConstruct
    public void init() {
        this.urlBlockHandlerOptional.ifPresent(WebCallbackManager::setUrlBlockHandler);
        this.urlCleanerOptional.ifPresent(WebCallbackManager::setUrlCleaner);
        this.requestOriginParserOptional.ifPresent(WebCallbackManager::setRequestOriginParser);
    }

    @Bean
    @ConditionalOnProperty(
        name = {"spring.cloud.sentinel.filter.enabled"},
        matchIfMissing = true
    )
    public FilterRegistrationBean sentinelFilter() {
        FilterRegistrationBean<Filter> registration = new FilterRegistrationBean();
        com.alibaba.cloud.sentinel.SentinelProperties.Filter filterConfig = this.properties.getFilter();
        if (filterConfig.getUrlPatterns() == null || filterConfig.getUrlPatterns().isEmpty()) {
            List<String> defaultPatterns = new ArrayList();
            defaultPatterns.add("/*");
            filterConfig.setUrlPatterns(defaultPatterns);
        }

        registration.addUrlPatterns((String[])filterConfig.getUrlPatterns().toArray(new String[0]));
        Filter filter = new CommonFilter();
        registration.setFilter(filter);
        registration.setOrder(filterConfig.getOrder());
        registration.addInitParameter("HTTP_METHOD_SPECIFY", String.valueOf(this.properties.getHttpMethodSpecify()));
        log.info("[Sentinel Starter] register Sentinel CommonFilter with urlPatterns: {}.", filterConfig.getUrlPatterns());
        return registration;
    }
}

 FilterRegistrationBean 主要作用是注册一个 CommonFilter,并且默认情况下 通过 "/*" 规则拦截所有请求

CommonFilter 工作逻辑

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest sRequest = (HttpServletRequest)request;
    Entry urlEntry = null;

    try {
        //解析URL
        String target = FilterUtil.filterTarget(sRequest);
       //清洗URL
        UrlCleaner urlCleaner = WebCallbackManager.getUrlCleaner();
        if (urlCleaner != null) {
            target = urlCleaner.clean(target);
        }

        if (!StringUtil.isEmpty(target)) {
            String origin = this.parseOrigin(sRequest);
            String contextName = this.webContextUnify ? "sentinel_web_servlet_context" : target;
            ContextUtil.enter(contextName, origin);
            if (this.httpMethodSpecify) {
                //限流埋点
                String pathWithHttpMethod = sRequest.getMethod().toUpperCase() + ":" + target;
                urlEntry = SphU.entry(pathWithHttpMethod, 1, EntryType.IN);
            } else {
                urlEntry = SphU.entry(target, 1, EntryType.IN);
            }
        }

        chain.doFilter(request, response);
    } catch (BlockException var15) {
        HttpServletResponse sResponse = (HttpServletResponse)response;
        WebCallbackManager.getUrlBlockHandler().blocked(sRequest, sResponse, var15);
    } catch (ServletException | RuntimeException | IOException var16) {
        Tracer.traceEntry(var16, urlEntry);
        throw var16;
    } finally {
        if (urlEntry != null) {
            urlEntry.exit();
        }

        ContextUtil.exit();
    }

}

所以对于 Web Servlet 环境,是通过 filter 的方式将所有请求自动设置为 Sentinel 资源,从而达到限流目的。

Sentinel 源码分析

  • sentinel-adapter : 负责针对主流开源框架进行限流适配,比如 Dubbo,gRPC,Zuul等
  • sentinel-core:核心库,提供限流,降级等实现
  • sentinel-dashboard:控制台模块
  • sentinel-extension:实现不同组件的数据源扩展,比如:Nacos,Zookeeper,Apollo等
  • sentinel-transport:通信协议处理模块

限流源码实现

限流的判断逻辑是在 SphU.entry 方法中实现的

 

CtSph 是 Sph的默认实现类

核心方法:

  1. 校验全局上下文Context
  2. 通过lookProcessChain 方法获取一个 ProcessorSlot 链
  3. 执行 chain.entry ,如果没有被限流,返回 entry 对象 ,否则抛出 BlockException
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 {
        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 方法

这里的 chain 会构造一个 Slot 链,启动每一个 Slot 都有各自的责任

ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
    ProcessorSlotChain chain = chainMap.get(resourceWrapper);
    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;
                }

                chain = SlotChainProvider.newSlotChain();
                Map<ResourceWrapper, ProcessorSlotChain> newMap = new HashMap<ResourceWrapper, ProcessorSlotChain>(
                    chainMap.size() + 1);
                newMap.putAll(chainMap);
                newMap.put(resourceWrapper, chain);
                chainMap = newMap;
            }
        }
    }
    return chain;
}
public static ProcessorSlotChain newSlotChain() {
    if (slotChainBuilder != null) {
        return slotChainBuilder.build();
    }

    // Resolve the slot chain builder SPI.
    slotChainBuilder = SpiLoader.loadFirstInstanceOrDefault(SlotChainBuilder.class, DefaultSlotChainBuilder.class);

    if (slotChainBuilder == null) {
        // Should not go through here.
        RecordLog.warn("[SlotChainProvider] Wrong state when resolving slot chain builder, using default");
        slotChainBuilder = new DefaultSlotChainBuilder();
    } else {
        RecordLog.info("[SlotChainProvider] Global slot chain builder resolved: "
            + slotChainBuilder.getClass().getCanonicalName());
    }
    return slotChainBuilder.build();
}
public class DefaultSlotChainBuilder implements SlotChainBuilder {

    @Override
    public ProcessorSlotChain build() {
        ProcessorSlotChain chain = new DefaultProcessorSlotChain();

        // Note: the instances of ProcessorSlot should be different, since they are not stateless.
        List<ProcessorSlot> sortedSlotList = SpiLoader.loadPrototypeInstanceListSorted(ProcessorSlot.class);
        for (ProcessorSlot slot : sortedSlotList) {
            if (!(slot instanceof AbstractLinkedProcessorSlot)) {
                RecordLog.warn("The ProcessorSlot(" + slot.getClass().getCanonicalName() + ") is not an instance of AbstractLinkedProcessorSlot, can't be added into ProcessorSlotChain");
                continue;
            }

            chain.addLast((AbstractLinkedProcessorSlot<?>) slot);
        }

        return chain;
    }
}

 这里看到会 load ProcessorSlot 的实现类的 Slot  每个 Slot 都对应不同的职责:

 比如:

  • FlowSlot:根据限流规则和各个Node中的统计数据进行限流判断
  • LogSlot:在出现限流,熔断,系统保护时负责记录日志
  • DegradeSlot:根据熔断规则和各个Node中的统计数据进行服务降级。
  • 等等。。。

FlowSlot 分析

@Override
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
                  boolean prioritized, Object... args) throws Throwable {
    checkFlow(resourceWrapper, context, node, count, prioritized);

    fireEntry(context, resourceWrapper, node, count, prioritized, args);
}
//限流检测
void checkFlow(ResourceWrapper resource, Context context, DefaultNode node, int count, boolean prioritized)
    throws BlockException {
    checker.checkFlow(ruleProvider, resource, context, node, count, prioritized);
}
//获取限流规则
private final Function<String, Collection<FlowRule>> ruleProvider = new Function<String, Collection<FlowRule>>() {
    @Override
    public Collection<FlowRule> apply(String resource) {
        // Flow rule map should not be null.
        Map<String, List<FlowRule>> flowRules = FlowRuleManager.getFlowRuleMap();
        return flowRules.get(resource);
    }
};

上面的 chain.entry 会经过 FlowSlot 的 entry()方法,调用 checkFlow 进行流控规则判断

public void checkFlow(Function<String, Collection<FlowRule>> ruleProvider, ResourceWrapper resource,
                      Context context, DefaultNode node, int count, boolean prioritized) throws BlockException {
    if (ruleProvider == null || resource == null) {
        return;
    }
    Collection<FlowRule> rules = ruleProvider.apply(resource.getName());
    if (rules != null) {
        //遍历所有FlowRule
        for (FlowRule rule : rules) {
            //针对每个规则,调用canPassCheck进行校验
            if (!canPassCheck(rule, context, node, count, prioritized)) {
                throw new FlowException(rule.getLimitApp(), rule);
            }
        }
    }
}

 canPassCheck 提供了集群和单机两种限流模式。

public boolean canPassCheck(/*@NonNull*/ FlowRule rule, Context context, DefaultNode node, int acquireCount,
                                                boolean prioritized) {
    String limitApp = rule.getLimitApp();
    if (limitApp == null) {
        return true;
    }

    if (rule.isClusterMode()) {
        return passClusterCheck(rule, context, node, acquireCount, prioritized);
    }

    return passLocalCheck(rule, context, node, acquireCount, prioritized);
}

 单机主要有两个逻辑:

  1. 根据来源和策略获取Node,从而拿到统计的runtime信息
  2. 使用流量控制器检查是否让流量通过
private static boolean passLocalCheck(FlowRule rule, Context context, DefaultNode node, int acquireCount,
                                      boolean prioritized) {
    Node selectedNode = selectNodeByRequesterAndStrategy(rule, context, node);
    if (selectedNode == null) {
        return true;
    }

    return rule.getRater().canPass(selectedNode, acquireCount, prioritized);
}

selectNodeByRequesterAndStrategy 根据 FlowRule 中配置的 Strategy 和 limitApp 属性,返回不同处理策略的 Node

static Node selectNodeByRequesterAndStrategy(/*@NonNull*/ FlowRule rule, Context context, DefaultNode node) {
    // The limit app should not be empty.
    String limitApp = rule.getLimitApp();
    int strategy = rule.getStrategy();
    String origin = context.getOrigin();
 //第1种:限流规则设置了具体应用,如果当前流量就是通过该应用的,则命中场景1
    if (limitApp.equals(origin) && filterOrigin(origin)) {
        if (strategy == RuleConstant.STRATEGY_DIRECT) {
            // Matches limit origin, return origin statistic node.
            return context.getOriginNode();
        }

        return selectReferenceNode(rule, context, node);
//第2种:限流规则未设置应用,默认为 default,则命中场景2
    } else if (RuleConstant.LIMIT_APP_DEFAULT.equals(limitApp)) {
        if (strategy == RuleConstant.STRATEGY_DIRECT) {
            // Return the cluster node.
            return node.getClusterNode();
        }

        return selectReferenceNode(rule, context, node);
//第3种:限流规则设置的是 other ,当前流量未命中前两种场景
    } else if (RuleConstant.LIMIT_APP_OTHER.equals(limitApp)
        && FlowRuleManager.isOtherOrigin(origin, rule.getResource())) {
        if (strategy == RuleConstant.STRATEGY_DIRECT) {
            return context.getOriginNode();
        }

        return selectReferenceNode(rule, context, node);
    }

    return null;
}
  • 第一种:区分调用来源,如 A 500 QPS ,  B 200 QPS , 此时会产生两条规则
  • 第二种:不区分调用来源,所有入口调用 userService 共享一个规则,所有 client 加起来总流量只能 1000 QPS
  • 第三种:配合第一种使用,没有具体设置的应用都会命中

rule.getRater() 获得流控行为,实现不同的处理策略。

 

  1.  直接拒绝
  2. 匀速排队
  3. 冷启动
  4. 匀速+冷启动

 

 

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值