Sentinel之重要类介绍

1,174 阅读6分钟

微信公众号: 房东的小黑黑
路途随遥远,将来更美好
学海无涯,大家一起加油!

该篇是分析Sentinel原理的第一篇文章。由于接触该中间件的时间较短,可能会出现一些理解偏差。希望大家积极交流改正。文章主要内容主要是阅读了一些大神的文章,并加上自己的理解写的。

主要参考的文章是在Sentinel的github中找到的,具体地址如下: 参考文章

Sentinel的主要功能就是流量控制和熔断降级。具体概念在之前写的有关dubbo的限流和熔断中已介绍,这里就不再赘述了。

先简单举一个如何使用的例子。

public static void main(String[] args) {
    try {
        Context context=ContextUtil.enter("context1");
        Entry entry=SphU.entry("HelloWorld");
        entry.exit();
        ContextUtil.exit();
    } catch (BlockException ex) {
        // 处理被流控的逻辑
        System.out.println("blocked!");
    }catch (Exception e){
        e.printStackTrace();
    }
}
try (Entry entry = SphU.entry("HelloWorld")) {
    // Your business logic here.
    System.out.println("hello world");
catch (BlockException e) {
    // Handle rejected request.
    e.printStackTrace();
}

上面是两个典型的流量控制例子。读者会发现有一处很明显的不同,即Context类,它用来表示一个调用的上下文,实际不加也影响,具体的在后面分析。

例子中还有一个重要的类Entry,里面的参数HelloWorld可以看做一个资源名,在具体使用之前,我们需要自己定义一些规则。 可以使用下面的方法定义一个关于流量控制的规则:

private void initFlowQpsRule() {
    List<FlowRule> rules = new ArrayList<>();
    FlowRule rule = new FlowRule(resourceName);
    // set limit qps to 20
    rule.setCount(20);
    rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
    rule.setLimitApp("default");
    rules.add(rule);
    FlowRuleManager.loadRules(rules);
}

当调用SphU.entry("HelloWorld")时,如果没有报异常,说明可以执行自己的业务代码;如果报错,说明当前不符合条件,拒绝执行后面的业务代码。

几个核心概念

Resource

资源是Sentinel中的一个核心概念,它可以是应用程序提供的服务,也可以是应用程序调用的其他服务,也可以是一段代码。当某个服务的请求非常多,经常被陡增的流量冲垮,造成性能降低或者不可用,这时我们可以定义一个Sentinel的资源,通过该资源对请求进行调整,进行流量控制和服务降级。

在Sentinel中表示具体资源的类是ResourceWrapper

public abstract class ResourceWrapper {
    # 资源名
    protected final String name;
    # 是入站还是出站
    protected final EntryType entryType;
    # 资源类型
    protected final int resourceType;

Entry

entry是sentinel中用来表示是否通过限流的一个凭证,如果能正常返回,则说明你可以访问被sentinel保护的后方服务,否则sentinel会抛出一个BlockException。另外,它保存了本次执行entry()方法的一些基本信息。每一次资源调用都会创建一个Entry

public abstract class Entry implements AutoCloseable {
    # 当前Entry的创建时间,主要用来后期计算rt
    private long createTime;
    # 当前Entry所关联的node,该node主要是记录了当前context下该资源的统计信息
    private Node curNode;
    # 当前Entry的调用来源,通常是调用方的应用名称
    private Node originNode;
    private Throwable error;
    # 当前Entry所关联的资源
    protected ResourceWrapper resourceWrapper;
class CtEntry extends Entry {
    protected Entry parent = null;
    protected Entry child = null;
    protected ProcessorSlot<Object> chain;
    protected Context context;

可能这样介绍有些抽象,我借鉴一个别人的例子,很形象。

从图中我们可以看出有一个user-center --> getUserInfo --> getOrderInfo的调用链路。根据代码,我们知道user-center是一个上下文名称,getUserInfogetOrderInfo是两个资源名称。

上面例子中在一个Entry中又调用了一个Entry,存在链路,当在一个上下文中多次调用Sphu.entry()方法,就会创建一个调用树,这个树的节点之间是通过parent和child关系维持的。

Node

node中保存了资源的实时统计数据,例如:passQps,blockQps,rt等实时数据。

node是一个接口,它有一个实现类StatisticNode,但是StatisticNode本身也有两个子类,一个是DefaultNode,另一个是ClusterNodeDefaultNode又有一个子类叫EntranceNode

EntranceNode是每个上下文的入口,该节点是直接挂在root下,是全局唯一的,每一个context都有一个对应的entranceNodeDefaultNode是记录当前实时数据的,每个DefaultNode都关联着一个资源和ClusterNode。有着相同资源的defaultNode,它们关联着同一个clusterNode。

public interface Node extends OccupySupportDebugSupport {
    long totalRequest();
    long totalPass();
    long totalSuccess();
public class DefaultNode extends StatisticNode {
    private ResourceWrapper id;
    private volatile Set<Node> childList = new HashSet<>();
    private ClusterNode clusterNode;

Context

public class Context {
    //上下文名称
    private final String name;
    //当前调用链的入口节点
    private DefaultNode entranceNode;
   //调用链中当前正在处理的entry
    private Entry curEntry;
    //此上下文的来源(通常表示不同的调用方,例如服务使用者名称或来源IP)
    private String origin = "";

Context代表调用链路上下文,贯穿一次调用链路中的所有Entry
每次调用SphU.entry()都需要在一个context中执行,如果没有当前执行时还没有context,那么框架会使用默认的context,即sentinel_default_context

Context是保存在ThreadLocal中的,每次执行的时候会优先到ThreadLocal中获取。如果Context为null才会再次去创建一个context。

Entry执行exit方法时,当当前的entry的parent为null时,说明当前entry是最上层的节点,该上下文的entry已执行完,所以在此时需要将context设置为null,从ThreadLocal中清除。

以上就是比较重要的实体类介绍。

参考文章:
Sentinel核心类解析
Sentinel原理-实体类
阿里Sentinel源码解析

本文使用 mdnice 排版