Sentinel 核心概念和工作流程详解

前言:

上一篇文章中,我们对 Sentinel 有了基本认知,知道其是 Alibaba 开源的一个服务稳定性组件,我们从 Sentinel 控制台认识了 Sentinel 的流控、降级、热点、授权规则,本篇我们将从核心概念和工作流程方面继续分析 Sentinel。

Sentinel 系列文章传送门:

Sentinel 初步认识及使用

Sentinel 两个核心概念

Sentinel 实现限流、隔离、降级、熔断等功能,其本质就是做了两件事情,统计数据和规则判断。

  • 统计数据:统计某个资源的访问数据,比如 QPS、RT、是否异常请求等。
  • 规则判断:判断限流规则、隔离规则、降级规则、熔断规则是否满足。

资源就是指被 Sentinel 保护的业务,例如项目中定义的 controlleer 方法就是默认被 Sentinel 保护的资源。

ProcessorSlotChain

Sentinel 实现试数据统计和规则判断的核心类是 ProcessorSlotChain,这个类基于责任链模式来设计,将不同的功能
封装为一个个的 Slot,请求进入后逐个执行即可,执行流程图如下:

在这里插入图片描述

图摘自 Github,Sentinel Github wiki 地址

Slot

数据统计 Slot(statistic)

  • NodeSelectorSlot:负责构建簇点链路中的节点(DefaultNode),将这些节点形成链路树。
  • ClusterBuilderSlot:负责构建某个资源的 ClusterNode,ClusterNoode 可以保存资源的运行信息(响应时间、QPS、block数目、线程数、异常数等)以及来源信息(origin名称)。
  • StatisticSlot:负责统计实时调用数据,包括运行信息、来源信息等。

规则判断 Slot(rulechecking)

  • AuthoritySlot:负责授权规则(来源控制)。
  • SystemSlot:负责系统保护规则。
  • ParamFlowSlot:负责热点参数限流规则。
  • FlowSlot:负责限流规则。
  • DegradeSlot:负责降级规则。

Context

Context 代表调用链路上下文,贯穿一次调用链路中的所有 Entry。Context 维持着入口节点(entranceNode)、本次调用链路的 curNode、调用来源(origin)等信息。Context 名称即为调用链路入口名称。Context 维持的方式:通过 ThreadLocal 传递,只有在入口 enter 的时候生效。由于 Context 是通过 ThreadLocal 传递的,因此对于异步调用链路,线程切换的时候会丢掉 Context,因此需要手动通过 ContextUtil.runOnContext(context, f) 来变换 context。

Entry

每一次资源调用都会创建一个 Entry。Entry 包含了资源名、curNode(当前统计节点)、originNode(来源统计节点)等信息。CtEntry 为普通的 Entry,在调用 SphU.entry(xxx) 的时候创建。特性:Linked entry within current context(内部维护着 parent 和 child)需要注意的一点:CtEntry 构造函数中会做调用链的变换,即将当前 Entry 接到传入 Context 的调用链路上(setUpEntryFor)。资源调用结束时需要 entry.exit()。exit 操作会过一遍 slot chain exit,恢复调用栈,exit context 然后清空 entry 中的 context 防止重复调用。

Node

Sentinel 里面的各种种类的统计节点:

  • StatisticNode:最为基础的统计节点,包含秒级和分钟级两个滑动窗口结构。
  • DefaultNode:链路节点,用于统计调用链路上某个资源的数据,维持树状结构。
  • ClusterNode:簇点,用于统计每个资源全局的数据(不区分调用链路),以及存放该资源的按来源区分的调用数据(类型为 StatisticNode)。特别地,Constants.ENTRY_NODE 节点用于统计全局的入口资源数据。
  • EntranceNode:入口节点,特殊的链路节点,对应某个 Context 入口的所有调用数据。Constants.ROOT 节点也是入口节点。

构建的时机:

  • EntranceNode 在 ContextUtil.enter(xxx) 的时候就创建了,然后塞到 Context 里面。
  • NodeSelectorSlot:根据 context 创建 DefaultNode,然后 set curNode to context。
  • ClusterBuilderSlot:首先根据 resourceName 创建 ClusterNode,并且 set clusterNode to defaultNode;然后再根据 origin 创建来源节点(类型为 StatisticNode),并且 set originNode to curEntry。

几种 Node 的维度(数目):

  • ClusterNode 的维度是 resource。
  • DefaultNode 的维度是 resource * context,存在每个 NodeSelectorSlot 的 map 里面。
  • EntranceNode 的维度是 context,存在 ContextUtil 类的 contextNameNodeMap 里面。
  • 来源节点(类型为 StatisticNode)的维度是 resource * origin,存在每个 ClusterNode 的 originCountMap 里面。

StatisticSlot

StatisticSlot 是 Sentinel 最为重要的类之一,用于根据规则判断结果进行相应的统计操作。entry 的时候:依次执行后面的判断 slot。每个 slot 触发流控的话会抛出异常(BlockException 的子类)。若有 BlockException 抛出,则记录 block 数据;若无异常抛出则算作可通过(pass),记录 pass 数据。exit 的时候:若无 error(无论是业务异常还是流控异常),记录 complete(success)以及 RT,线程数-1。记录数据的维度:线程数+1、记录当前 DefaultNode 数据、记录对应的 originNode 数据(若存在 origin)、累计 IN 统计数据(若流量类型为 IN)。

Sentinel 定义资源的两种方式

Sentinel 默认会将 Controller 中的方法作为被保护资源,我们来演示一下非 Controller 中方法被 Sentinel 保护的两种方式。

  1. 编码定义资源。

    编码模板如下:

private void sentinelTemplate() {
	//资源名 比如方法名、接口名或其它可唯一标识的字符串
	try (Entry entry = SphU.entry("resourceName")) {
		// 被保护的业务逻辑
	} catch (BlockException ex) {
		// 资源访问阻止,被限流或被降级
	}
}

编码定义资源演示

@GetMapping("/query-order-by-id")
public String queryOrderById(@RequestParam String orderId) {
	return orderService.queryOrderById(orderId);
}

@Override
public String queryOrderById(String orderId) {
	try (Entry ignored = SphU.entry("orderResouce")) {
		return "订单号:" + orderId;
	} catch (BlockException e) {
		log.error("异常信息:", e);
		return null;
	}
}

请求接口,Sentinel 控制台如下:

在这里插入图片描述
2. @SentinelResource 注解定义资源。

注解定义资源演示:

@SentinelResource(value = "sentinelResource")
@Override
public String testSentinelResource(String sentinelResource) {
	return "sentinelResource 测试:" + sentinelResource;
}

@GetMapping("/test-sentinel-resource")
public String testSentinelResource(@RequestParam String testSentinelResource) {
	return orderService.testSentinelResource(testSentinelResource);
}

注解演示结果:
在这里插入图片描述

@SentinelResource 注解的原理

我们上面演示了使用编码方式和注解方式都可以把资源交给 Sentinel 保护,编码方式容易理解,那注解方式下资源是怎么被 Sentinel 保护的呢?我们大胆猜测是否是使用 AOP 切面的方式实现的呢?我们通过搜索发现还真有一个叫做 SentinelResourceAspect 的类,如下:

@Aspect
public class SentinelResourceAspect extends AbstractSentinelAspectSupport {

    //切点 有 @SentinelResource 注解的方法
    @Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)")
    public void sentinelResourceAnnotationPointcut() {
    }

    @Around("sentinelResourceAnnotationPointcut()")
    public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable {
        //获取受保护的方法
        Method originMethod = resolveMethod(pjp);
        //获取注解
        SentinelResource annotation = originMethod.getAnnotation(SentinelResource.class);
        //注解为空判断
        if (annotation == null) {
            // Should not go through here.
            throw new IllegalStateException("Wrong state for SentinelResource annotation");
        }
        //获取资源名称 没有指定指定资源名称 则默认方法名称
        String resourceName = getResourceName(annotation.value(), originMethod);
        //获取 entry 类型
        EntryType entryType = annotation.entryType();
        //获取资源列席
        int resourceType = annotation.resourceType();
        Entry entry = null;
        try {
            //构造 entry 对象
            entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());
            //执行业务方法
            Object result = pjp.proceed();
            //返回
            return result;
        } catch (BlockException ex) {
            //异常处理
            return handleBlockException(pjp, annotation, ex);
        } catch (Throwable ex) {
            //异常处理
            Class<? extends Throwable>[] exceptionsToIgnore = annotation.exceptionsToIgnore();
            // The ignore list will be checked first.
            if (exceptionsToIgnore.length > 0 && exceptionBelongsTo(ex, exceptionsToIgnore)) {
                throw ex;
            }
            if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) {
                traceException(ex);
                return handleFallback(pjp, annotation, ex);
            }

            // No fallback function can handle the exception, so throw it out.
            throw ex;
        } finally {
            if (entry != null) {
                entry.exit(1, pjp.getArgs());
            }
        }
    }
}

通过 SentinelResourceAspect 类的源码可以知道 @SentinelResource 注解是一个标记,而 Sentinel 基于 AOP 思想,对被标记的方法做环绕增强,完成资源 Entry 的创建,实现了对资源的保护。

SentinelResourceAspect 类是何时加载的?

源码阅读的多了,不难想到去 Sentinel 的 META-INF/spring.properties 文件查看一下是否有相关类,spring.properties 如下:

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

从 spring.properties 不难看出有 SentinelAutoConfiguration 类可能会更 SentinelResourceAspect 类的加载有关系,我们来验证一下。

果然在 SentinelAutoConfiguration 类中发现了一段定义 SentinelResourceAspect 的方法,如下:

@Bean
@ConditionalOnMissingBean
public SentinelResourceAspect sentinelResourceAspect() {
	return new SentinelResourceAspect();
}

至此 SentinelResourceAspect 类的加载时机已经很清楚了,SentinelAutoConfiguration 是 Spring Boot 自动装配的,而 SentinelAutoConfiguration 类中又定义了 SentinelResourceAspect 类。

欢迎提出建议及对错误的地方指出纠正。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值