初始化工作原理
在 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的默认实现类
核心方法:
- 校验全局上下文Context
- 通过lookProcessChain 方法获取一个 ProcessorSlot 链
- 执行 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);
}
单机主要有两个逻辑:
- 根据来源和策略获取Node,从而拿到统计的runtime信息
- 使用流量控制器检查是否让流量通过
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() 获得流控行为,实现不同的处理策略。
- 直接拒绝
- 匀速排队
- 冷启动
- 匀速+冷启动