微服务架构 | 5.4 Sentinel 流控、统计和熔断的源码分析
目录
- 前言
- 1. Sentinel 的自动装配
- 1.2 依赖引入
- 1.3 SentinelWebAutoConfiguration 配置类
- 1.4 CommonFilter 过滤器
- 1.5 小结
- 2. 获取 ProcessorSlot 链
- 2.1 Sentinel 源码包结构
- 2.2 获取 ProcessorSlot 链与操作 Slot 槽的入口 CtSph.entryWithPriority()
- 2.2.1 构造 ProcessorSlot 链 CtSph.lookProcessChain()
- 2.2.2 操作 Slot 槽的入口
- 3. 流控槽实施流控逻辑 FlowSlot.entry()
- 3.1 获取流控规则 FlowSlot.ruleProvider.apply()
- 3.2 校验每条规则 FlowRuleChecker.canPassCheck()
- 3.2.1 获取 Node FlowRuleChecker.selectNodeByRequesterAndStrategy()
- 3.2.2 获取流控的处理策略 `FlowRule.getRater().canPass()
- 4. 统计槽实施指标数据统计 StatisticSlot.entry()
- 4.1 统计“增加线程数”和“请求通过数”
- 4.2 数据统计的数据结构
- 4.2.1 ArrayMetric 指标数组
- 4.2.2 LeapArray 环形数组
- 4.2.3 WindowWrap 窗口包装类
- 4.2.4 MetricBucket 指标桶
- 4.2.5 各数据结构的依赖关系
- 4.2.6 LeapArray 统计数据的大致思路
- 5. 熔断槽实施服务熔断 DegradeSlot.entry()
- 5.1 继续或取消熔断功能
- 5.2 请求失败,启动熔断
- 6. Sentinel 源码结构图小结
- 最后
前言
参考资料:
《Spring Microservices in Action》
《Spring Cloud Alibaba 微服务原理与实战》
《B站 尚硅谷 SpringCloud 框架开发教程 周阳》
《Sentinel GitHub 官网》
《Sentinel 官网》
调用链路是 Sentinel 的工作主流程,由各个 Slot 槽组成,将不同的 Slot 槽按照顺序串在一起,从而将不同的功能(限流、降级、系统保护)组合在一起;
本篇《2. 获取 ProcessorSlot 链》将从源码级讲解如何获取调用链路,接着会以遍历链表的方式处理每一个 Slot 槽,其中就有:FlowSlot、StatisticSlot、DegradeSlot 等。分别对应本篇《3. 流控槽实施流控逻辑》、《4. 统计槽实施指标数据统计》和《5. 熔断槽实施服务熔断》;
1. Sentinel 的自动装配
1.2 依赖引入
- 我们引入 Sentinel 的 starter 依赖文件,不需要太多额外操作,即可使用 Sentinel 默认自带的限流功能,原因是这些配置和功能都给我们自动装配了;
- 在 Spring-Cloud-Alibaba-Sentinel 包下的 META-INF/spring.factories 文件里定义了会自动装配哪些类;
- SentinelWebAutoConfiguration:对 Web Servlet 环境的支持;
- SentinelWebFluxAutoConfiguration:对 Spring WebFlux 的支持;
- SentinelEndpointAutoConfiguration:暴露 Endpoint 信息;
- SentinelFeignAutoConfiguration:用于适应 Feign 组件;
- SentinelAutoConfiguration:支持对 RestTemplate 的服务调用使用 Sentinel 进行保护;
1.3 SentinelWebAutoConfiguration 配置类
- 在 SentinelWebAutoConfiguration 配置类中自动装配了一个 FilterRegistrationBean,其主要作用是注册一个 CommonFilter,并且默认情况下通过
/*
规则拦截所有的请求;
@Configuration
@EnableConfigurationProperties(SentinelProperties.class)
public class SentinelWebAutoConfiguration {
//省略其他代码
@Bean
@ConditionalOnProperty(name = "spring.cloud.sentinel.filter.enabled", matchIfMissing = true)
public FilterRegistrationBean sentinelFilter() {
FilterRegistrationBean registration = new FilterRegistrationBean<>();
SentinelProperties.Filter filterConfig = properties.getFilter();
if (filterConfig.getUrlPatterns() == null || filterConfig.getUrlPatterns().isEmpty()) {
List defaultPatterns = new ArrayList<>();
//默认情况下通过 /* 规则拦截所有的请求
defaultPatterns.add("/*");
filterConfig.setUrlPatterns(defaultPatterns);
}
registration.addUrlPatterns(filterConfig.getUrlPatterns().toArray(new String[0]));
//【点进去】注册 CommonFilter
Filter filter = new CommonFilter();
registration.setFilter(filter);
registration.setOrder(filterConfig.getOrder());
registration.addInitParameter("HTTP_METHOD_SPECIFY", String.valueOf(properties.getHttpMethodSpecify()));
log.info("[Sentinel Starter] register Sentinel CommonFilter with urlPatterns: {}.", filterConfig.getUrlPatterns());
return registration;
}
}
1.4 CommonFilter 过滤器
- CommonFilter 过滤器的作用与源码如下:
- 从请求中获取目标 URL;
- 获取 Urlcleaner;
- 对当前 URL 添加限流埋点;
public class CommonFilter implements Filter {
//省略部分代码
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) {
//如果存在,则说明配置过 URL 清洗策略,替换配置的 targer
target = urlCleaner.clean(target);
}
if (!StringUtil.isEmpty(target)) {
String origin = this.parseOrigin(sRequest);
ContextUtil.enter("sentinel_web_servlet_context", origin);
if (this.httpMethodSpecify) {
String pathWithHttpMethod = sRequest.getMethod().toUpperCase() + ":" + target;
//使用 SphU.entry() 方法对 URL 添加限流埋点
urlEntry = SphU.entry(pathWithHttpMethod, 1, EntryType.IN);
} else {
urlEntry = SphU.entry(target, 1, EntryType.IN);
}
}
//执行过滤
chain.doFilter(request, response);
} catch (BlockException var14) {
HttpServletResponse sResponse = (HttpServletResponse)response;
WebCallbackManager.getUrlBlockHandler().blocked(sRequest, sResponse, var14);
} catch (ServletException | RuntimeException | IOException var15) {
Tracer.traceEntry(var15, urlEntry);
throw var15;
} finally {
if (urlEntry != null) {
urlEntry.exit();
}
ContextUtil.exit();
}
}
}
1.5 小结
- 对于 Web Servlet 环境,只是通过 Filter 的方式将所有请求自动设置为 Sentinel 的资源,从而达到限流的目的;
2. 获取 ProcessorSlot 链
- Sentinel 的工作原理主要依靠 ProcessorSlot 链,遍历链中的每一个 Slot 槽,执行相应逻辑;
2.1 Sentinel 源码包结构
- 在 DeBug 之前,我们需要对 Sentinel 的源码包结构做个分析,以找到方法的入口;
模块名 | 说明 |
---|---|
sentinel-adapter | 负责针对主流开源框架进行限流适配,如:Dubbo、gRPC、Zuul 等; |
sentinel-core | Sentinel 核心库,提供限流、熔断等实现; |
sentinel-dashboard | 控制台模块,提供可视化监控和管理; |
sentinel-demo | 官方案例; |
sentinel-extension | 实现不同组件的数据源扩展,如:Nacos、ZooKeeper、Apollo 等; |
sentinel-transport | 通信协议处理模块; |
- Slot 槽是 Sentinel 的核心,因此方法的入口在 sentinel-core 核心库,里面有好多个
SphU.entry()
方法,我们给方法打上断点,DeBug 进入,然后登录 Sentinel 控制台;
2.2 获取 ProcessorSlot 链与操作 Slot 槽的入口 CtSph.entryWithPriority()
- 一直进入最终方法的实现在
CtSph.entryWithPriority()
方法里,其主要逻辑与源码如下:- 校验全局上下文 context;
- 构造 ProcessorSlot 链;
- 遍历 ProcessorSlot 链操作 Slot 槽(遍历链表);
private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args) throws BlockException {
Context context = ContextUtil.getContext();
if (context instanceof NullContext) {
//上下文量已经超过阈值 -> 只初始化条目,不进行规则检查
return new CtEntry(resourceWrapper, null, context);
}
if (context == null) {
//没有指定上下文 -> 使用默认上下文 context
context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);
}
if (!Constants.ON) {
//全局开关关闭 -> 没有规则检查
return new CtEntry(resourceWrapper, null, context);
}
//【断点步入 2.2.1】通过 lookProcessChain 方法获取 ProcessorSlot 链
ProcessorSlot