Sentinel笔记-核心类
一些概念
- 资源:资源是 Sentinel 的关键概念。资源,可以是一个方法、一段代码、由应用提供的接口,或者由应用调用其它应用的接口。
- 规则:围绕资源的实时状态设定的规则,包括流量控制规则、熔断降级规则以及系统保护规则、自定义规则。
- 降级:在流量剧增的情况下,为保证系统能够正常运行,根据资源的实时状态、访问流量以及系统负载有策略的拒绝掉一部分流量。
Resource
Sentinel 中指标数据统计以资源为维度。资源使用 ResourceWrapper 对象表示,我们把 ResourceWrapper 对象称为资源 ID。如果一个资源描述的是一个接口,那么资源名称通常就是接口的 url,例如“GET:/v1/demo”。
ResourceWrapper 有三个字段:
- name 为资源名称,例如:“GET:/v1/demo”。
- entryType 为流量类型,即流入流量还是流出流量,通俗点说就是发起请求还是接收请求。
- resourceType 表示资源的类型,例如 Dubbo RPC、Web MVC 或者 API Gateway 网关。
public abstract class ResourceWrapper { protected final String name; protected final EntryType entryType; protected final int resourceType; public ResourceWrapper(String name, EntryType entryType, int resourceType) { this.name = name; this.entryType = entryType; this.resourceType = resourceType; } } //entryType public enum EntryType { IN, OUT; } //resourceType public final class ResourceTypeConstants { public static final int COMMON = 0; public static final int COMMON_WEB = 1; public static final int COMMON_RPC = 2; public static final int COMMON_API_GATEWAY = 3; public static final int COMMON_DB_SQL = 4; }
Node
Node 用于持有实时统计的指标数据,Node 接口定义了一个 Node 类所需要提供的各项指标数据统计的相关功能,内部由具体滑动窗口实现。
它的几个实现类:DefaultNode、ClusterNode、EntranceNode、StatisticNode 的关系如下图所示。
StatisticNode
Statistic 即统计的意思,StatisticNode 是 Node 接口的实现类,是实现实时指标数据统计 Node,包含秒级(可抢占OccupiableBucketLeapArray)和分钟级(BucketLeapArray)两个滑动窗口结构。
StatisticNode 还负责统计并行占用的线程数,用于实现信号量隔离,按资源所能并发占用的最大线程数实现限流。当接收到一个请求就将 curThreadNum 自增 1,当处理完请求时就将 curThreadNum 自减一,如果同时处理 10 个请求,那么 curThreadNum 的值就为 10。
//对外提供统一的metric操作接口 public interface Node { long totalRequest(); // 获取总的请求数 long totalPass(); // 获取通过的请求总数 long totalSuccess(); // 获取成功的请求总数 ***** void increaseThreadNum(); // 自增占用线程 void decreaseThreadNum(); // 自减占用线程 void reset(); // 重置滑动窗口 } public class StatisticNode implements Node { // 秒级滑动窗口,2 个时间窗口大小为 500 毫秒的 Bucket private Metric rollingCounterInSecond = new ArrayMetric(2,1000); // 分钟级滑动窗口,60 个 Bucket 数组,每个 Bucket 统计的时间窗口大小为 1 秒 private Metric rollingCounterInMinute = new ArrayMetric(60, 60 * 1000, false); // 统计并发使用的线程数 private LongAdder curThreadNum = new LongAdder(); //**具体操作由内部的ArrayMetric 滑动窗口来实现。 @Override public void addPassRequest(int count) { rollingCounterInSecond.addPass(count); rollingCounterInMinute.addPass(count); } @Override public void addRtAndSuccess(long rt, int successCount) { rollingCounterInSecond.addSuccess(successCount); rollingCounterInSecond.addRT(rt); rollingCounterInMinute.addSuccess(successCount); rollingCounterInMinute.addRT(rt); } ************* @Override public void increaseThreadNum() { curThreadNum.increment(); } @Override public void decreaseThreadNum() { curThreadNum.decrement(); } }
ClusterNode
Sentinel 使用 ClusterNode 统计每个资源全局的指标数据,以及统计该资源按调用来源区分的指标数据。全局数据指的是不区分调用链路,一个资源 ID(Resource) 只对应一个 ClusterNode。
public class ClusterNode extends StatisticNode { // 资源名称 private final String name; // 资源类型 private final int resourceType; // 来源指标数据统计,记录不同来源App的统计信息。 private Map<String, StatisticNode> originCountMap = new HashMap<>(); }
DefaultNode
DefaultNode 是实现以资源为维度的指标数据统计的 Node,是将资源 ID 和 StatisticNode 映射到一起的 Node,用于统计调用链路上某个资源的数据,维持树状结构。
DefaultNode 是 StatisticNode 的子类,构造方法要求传入资源 ID,表示该 Node 用于统计哪个资源的实时指标数据,指标数据统计则由父类 StatisticNode 完成。
public class DefaultNode extends StatisticNode { private ResourceWrapper id; private volatile Set<Node> childList = new HashSet<>(); //每个resourceId 对应一个clusterNode private ClusterNode clusterNode; }
childList 是一个 Node(DefaultNode)集合,用于存放子节点.
备注:在使用同一个 Context 的情况下,只会为一个资源创建一个 DefaultNode
EntranceNode
- 入口节点,特殊的链路节点,对应某个 Context 入口的所有调用数据。
Constants.ROOT
节点也是入口节点。 - EntranceNode 是一个特殊的 Node,它继承 DefaultNode,用于维护一颗树,从根节点到每个叶子节点都是不同请求的调用链路,所经过的每个节点都对应着调用链路上被 Sentinel 保护的资源,一个请求调用链路上的节点顺序正是资源被访问的顺序。
Demo:
在一个 Web MVC 应用中,每个接口就是一个资源,Sentinel 通过 Spring MVC 拦截器拦截每个接口的入口,统一创建名为“sentinel_spring_web_context”的 Context,名称相同的 Context 都使用同一个 EntranceNode。一个 Web 应用可能有多个接口,而 childList 就用于存储每个接口对应的 DefaultNode。
如果想统计一个应用的所有接口(不一定是所有,没有被调用过的接口不会创建对应的 DefaultNode)总的 QPS,只需要调用 EntranceNode 的 totalQps 就能获取到。EntranceNode 的 totalQps 方法代码如下:
public class EntranceNode extends DefaultNode { public EntranceNode(ResourceWrapper id, ClusterNode clusterNode) { super(id, clusterNode); } @Override public double avgRt() { double total = 0; double totalQps = 0; for (Node node : getChildList()) { total += node.avgRt() * node.passQps(); totalQps += node.passQps(); } return total / (totalQps == 0 ? 1 : totalQps); } @Override public double totalQps() { double r = 0; for (Node node : getChildList()) { r += node.totalQps(); } return r; } *** }
Sentinel 中的 Context 与 Entry
Context
Context 代表调用链路上下文,贯穿一次调用链路中的所有 Entry
。Context 维持着入口节点(entranceNode
)、本次调用链路的 curNode、调用来源(origin
)等信息。Context 名称即为调用链路入口名称。
Context 维持的方式:通过 ThreadLocal 传递,只有在入口 enter
的时候生效。由于 Context 是通过 ThreadLocal 传递的,因此对于异步调用链路,线程切换的时候会丢掉 Context,因此需要手动通过 ContextUtil.runOnContext(context, f)
来变换 context。
//This class holds metadata of current invocation public class Context { private final String name; private DefaultNode entranceNode; private Entry curEntry; private String origin = ""; }
- name:Context 的名称。
- entranceNode:当前调用树的入口节点,类型为 EntranceNode。同一个入口的资源,每个资源对应一个 DefaultNode,entranceNode#childList 用于存储这些资源的 DefaultNode。
- curEntry:当前 Entry(CtEntry),用于操作当前的node节点
- origin:调用来源的名称,即服务消费者的名称或者服务消费者的来源 IP,取决于服务消费者是否使用 Sentinel,由 Sentinel 适配层传递过来。例如:服务提供者是 Spring MVC 应用,且服务提供者使用 Sentinel 的 Web MVC 适配,那么 Sentinel 会尝试从请求头获取"S-user",如果服务消费者有在请求头传递这个参数,那么就能够获取到。
Entry(CtEntry)
- 在调用 Context#getCurNode 方法获取调用链路上当前访问到的资源的 DefaultNode 时,实际是从 Context#curEntry 获取的,Entry 维护了当前资源的 DefaultNode,以及调用来源的 StatisticNode。
- 每一次资源调用都会创建一个
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 防止重复调用。
//com.alibaba.csp.sentinel.context.Context#getCurNode public Node getCurNode() { return curEntry == null ? null : curEntry.getCurNode(); } public abstract class Entry implements AutoCloseable { private static final Object[] OBJECTS0 = new Object[0]; private long createTime; // 当前节点(DefaultNode) private Node curNode; // 来源节点 private Node originNode; private Throwable error; // 资源 protected ResourceWrapper resourceWrapper; }
class CtEntry extends Entry { // 当前 Entry 指向的父 Entry protected Entry parent = null; // 父 Entry 指向当前 Entry protected Entry child = null; // 当前资源的 ProcessorSlotChain protected ProcessorSlot<Object> chain; // 当前上下文 protected Context context; }
CtEntry 用于维护父子 Entry,每一次调用 SphU#entry 方法都会创建一个 CtEntry。如果服务 B 在处理一个请求的路径上会多次调用 SphU#entry,那么这些 CtEntry 会构成一个双向链表。在每次创建 CtEntry,都会将 Context.curEntry 设置为这个新的 CtEntry,双向链表的作用就是在调用 CtEntry#exit 方法时,能够将 Context.curEntry 还原为上一个资源的 CtEntry。
几种 Node 的维度(数目):
ClusterNode
的维度是 resourceDefaultNode
的维度是 resource * context,存在每个 NodeSelectorSlot 的map
里面EntranceNode
的维度是 context,存在 ContextUtil 类的contextNameNodeMap
里面- 来源节点(类型为
StatisticNode
)的维度是 resource * origin,存在每个 ClusterNode 的originCountMap
里面