Sentinel 原理分析之简单使用

Sentinel是一个用于流量控制的Java库,该文详细解释了其内部如何通过SphU.entry()进行资源保护,包括上下文信息的存储(使用ThreadLocal和默认上下文)、资源与处理槽链的映射以及BlockException的处理。Sentinel使用SlotChain进行资源规则检查,维护了资源名称与SlotChain的映射,并通过SPI机制加载自定义的SlotChainBuilder。
摘要由CSDN通过智能技术生成

在这里插入图片描述

官方实例程序

public static void main(String[] args) {
    // 配置规则.
    initFlowRules();

    while (true) {
        // 1.5.0 版本开始可以直接利用 try-with-resources 特性
        try (Entry entry = SphU.entry("HelloWorld")) {
            // 被保护的逻辑
            System.out.println("hello world");
	} catch (BlockException ex) {
            // 处理被流控的逻辑
	    System.out.println("blocked!");
	}
    }
}

private static void initFlowRules(){
    List<FlowRule> rules = new ArrayList<>();
    FlowRule rule = new FlowRule();
    rule.setResource("HelloWorld");
    rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
    // Set limit QPS to 20.
    rule.setCount(20);
    rules.add(rule);
    FlowRuleManager.loadRules(rules);
}

执行结果

INFO: log base dir is: C:\Users\cuiyutian1\logs\csp\
INFO: log name use pid is: false
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
blocked!
blocked!
blocked!
blocked!
blocked!

通过上面的执行结果,我们可以看到程序被限流了!System.out.println(“hello world”); 针对我们保护的资源超出预期设置的流控规则时候将会走到 BlockException 异常分支 , 下面将开始一探究竟!

资源保护第一步 SphU.entry()

SphU.entry()

探究第一步 SphU.entry() 做了什么, 我们可以看到 SphU.entry() 执行了如下的逻辑 通过代码分析,可以得到 sentinel 对资源的访问分为入口和出口, 并且在运行的过程中需要上下文信息 (系统会默认初始化), 上下文信息使用Thread Local 进行存放 sentinel 会对访问的资源生成对应的 规则检查链条

    public static Entry entry(String name) throws BlockException {
        return Env.sph.entry(name, EntryType.OUT, 1, OBJECTS0);
    }


@Override
    public Entry entry(String name, EntryType type, int count, Object... args) throws BlockException {
        // 将访问的资源包装成  StringResourceWrapper,并且设置成  EntryType.OUT 
        StringResourceWrapper resource = new StringResourceWrapper(name, type);
        return entry(resource, count, args);
    }

// 核心逻辑
    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) {
            context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);
        }

        // 全局开关是否打开,关闭的话就不执规则
        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);
        }
		// 创建 CtEntry 
        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;
    }

上下文信息

使用默认上下文信息

InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME) 下面的逻辑中能够看到上下文的名称还有 origin 调用来源信息

Constants.CONTEXT_DEFAULT_NAME = sentinel_default_context;

private final static class InternalContextUtil extends ContextUtil {
        static Context internalEnter(String name) {
            // 会走到这个方法里面
            return trueEnter(name, "");
        }

        static Context internalEnter(String name, String origin) {
            return trueEnter(name, origin);
        }
    }

下面的过程中能看到 sentinel 维护了一个 EntranceNode 和 上下文名称 之间的全局映射关系 并且上下文对应的 EntranceNode 节点会挂在系统 machine-root 节点下

  • Constants.ROOT 是一个EntranceNode节点 名称是 machine-root
// ContextUtil类
// 映射关系
private static volatile Map<String, DefaultNode> contextNameNodeMap = new HashMap<>();    
protected static Context trueEnter(String name, String origin) {
        // ThraedLocal中获取
        Context context = contextHolder.get();
        if (context == null) {
            Map<String, DefaultNode> localCacheNameMap = contextNameNodeMap;
            DefaultNode node = localCacheNameMap.get(name);
            // 上下文名称获取  DefaultNode
            if (node == null) {
                if (localCacheNameMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) {
                    setNullContext();
                    return NULL_CONTEXT;
                } else {
                    try {
                        LOCK.lock();
                        node = contextNameNodeMap.get(name);
                        if (node == null) {
                            if (contextNameNodeMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) {
                                setNullContext();
                                return NULL_CONTEXT;
                            } else {
                                // 使用上下文名称创建一个入口类型的资源, 并且创建EntranceNode 
                                node = new EntranceNode(new StringResourceWrapper(name, EntryType.IN), null);
                                // 添加到 machine-root 下面的子节点
                                Constants.ROOT.addChild(node);

                                Map<String, DefaultNode> newMap = new HashMap<>(contextNameNodeMap.size() + 1);
                                newMap.putAll(contextNameNodeMap);
                                newMap.put(name, node);
                                contextNameNodeMap = newMap;
                            }
                        }
                    } finally {
                        LOCK.unlock();
                    }
                }
            }
            // 创建上下文信息
            context = new Context(node, name);
            context.setOrigin(origin);
            // ThreadLocal存放
            contextHolder.set(context);
        }
        return context;
    }

上下文信息小结

经过上面的流程可以得到一些关键信息 Context 是存储ThreadLocal中的 并且系统中维护了上下文名称和 EntranceNode 节点的映射关系 我们的不同线程可以创建多个同名的上下文实例 但是在系统中只对应一个 EntranceNode 节点

资源保护的功能责任链

资源责任链 lookProcessChain(resourceWrapper)

CtSph 类中维护了一份资源和处理槽链的映射 , 如果映射中存在了 对应资源的 处理槽链 则直接返回 不存在时候进行创建 并且维护 chainMap 的映射关系

   
private static volatile Map<ResourceWrapper, ProcessorSlotChain> chainMap
        = new HashMap<ResourceWrapper, ProcessorSlotChain>();

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
                    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;
    }

这里看一下资源类的 hashCode() 和 equals() 方法 可以得到 chainMap 是按照资源名称做映射关系的

public abstract class ResourceWrapper {
    protected final String name;
    protected final EntryType entryType;
    protected final int resourceType;
    /**
     * Only {@link #getName()} is considered.
     */
    @Override
    public int hashCode() {
        return getName().hashCode();
    }

    /**
     * Only {@link #getName()} is considered.
     */
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof ResourceWrapper) {
            ResourceWrapper rw = (ResourceWrapper)obj;
            return rw.getName().equals(getName());
        }
        return false;
    }
}

具体构建流程 SlotChainProvider.newSlotChain() 中 可以得到sentinel 使用SPI机制 对 SlotChainBuilder 进行了扩展,并且优先使用自定义的SlotChainBuilder, 无自定义情况下使用 DefaultSlotChainBuilder

public final class SlotChainProvider {

    private static volatile SlotChainBuilder builder = null;
	// SPI 机制
    private static final ServiceLoader<SlotChainBuilder> LOADER = ServiceLoader.load(SlotChainBuilder.class);

	// 创建执行槽链
    public static ProcessorSlotChain newSlotChain() {
        // 不为空则进行build()
        if (builder != null) {
            return builder.build();
        }
		// 解析builder
        resolveSlotChainBuilder();
		// 解析完毕之后, builder 还是空的,则使用默认 DefaultSlotChainBuilder 
        if (builder == null) {
            RecordLog.warn("[SlotChainProvider] Wrong state when resolving slot chain builder, using default");
            builder = new DefaultSlotChainBuilder();
        }
        return builder.build();
    }
	// 解析  SlotChainBuilder 
    private static void resolveSlotChainBuilder() {
        List<SlotChainBuilder> list = new ArrayList<SlotChainBuilder>();
        boolean hasOther = false;
        // 拿到SPI获取的 builder
        for (SlotChainBuilder builder : LOADER) {
            if (builder.getClass() != DefaultSlotChainBuilder.class) {
                // 发现了自定义的 builder 
                hasOther = true;
                list.add(builder);
            }
        }
        // 使用自定义的 builder 
        if (hasOther) {
            builder = list.get(0);
        } else {
            // 没自定义 builder, 开始使用默认
            builder = new DefaultSlotChainBuilder();
        }

        RecordLog.info("[SlotChainProvider] Global slot chain builder resolved: "
            + builder.getClass().getCanonicalName());
    }

    private SlotChainProvider() {}
}

DefaultSlotChainBuilder 是如何构建的呢 这里的逻辑比较简单 可以理解为使用虚拟头节点方式 尾插发构建了一个链表

public class DefaultSlotChainBuilder implements SlotChainBuilder {

    @Override
    public ProcessorSlotChain build() {
        ProcessorSlotChain chain = new DefaultProcessorSlotChain();
        chain.addLast(new NodeSelectorSlot());
        chain.addLast(new ClusterBuilderSlot());
        chain.addLast(new LogSlot());
        chain.addLast(new StatisticSlot());
        chain.addLast(new SystemSlot());
        chain.addLast(new AuthoritySlot());
        chain.addLast(new FlowSlot());
        chain.addLast(new DegradeSlot());

        return chain;
    }

}

SlotChain 小结

通过SlotChain 的分析 可以得到 SlotChain 是和资源绑定在一起的 系统中维护了一份资源名称和SlotChain 的映射关系 结合上下文来说 可以理解为不同的上下文访问同一个资源时候 执行的是同一个 SlotChain

简单使用小结

sentinel 使用是需要建立在上下文中 并且存在以下关键信息: ResourceWrapper \ ProcessorSlotChain \ EntranceNode \ Context \ CtEntry 概念

执行流程图

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Sentinel是一个开源的分布式系统的实时监控与保护平台,它可以帮助我们在生产环境中实时监控系统的运行状态,并在出现异常或故障时进行预警和自动处理。其原理主要包括以下几个方面: 1. 流量控制:Sentinel可以通过定义流量控制规则,对系统的入口流量进行实时监控和控制。它可以根据流量的请求量、并发数、响应时间等指标来限制流量的访问,防止系统因为过载而崩溃。 2. 熔断降级:Sentinel可以通过定义熔断规则,当系统出现异常或故障时,自动切断对该服务的访问,避免故障在整个系统中的扩散。同时,可以通过降级规则,在系统压力过大时,对某些不重要或耗时较长的服务进行降级处理,保证核心功能的稳定性。 3. 系统保护:Sentinel可以对系统中的关键资源进行实时监控,当资源使用达到阈值时,自动触发保护机制。例如,可以对数据库连接池、线程池等关键资源进行监控,当资源使用过高时,可以拒绝新的请求,避免系统过载。 4. 实时统计与监控:Sentinel可以对系统的运行指标进行实时统计和监控,例如请求量、错误率、响应时间等。同时,它还提供了实时的可视化监控界面,方便开发人员实时查看系统的运行状态。 总的来说,Sentinel通过定义规则、实时监控和自动保护等机制,可以帮助我们实现对分布式系统的实时监控、流量控制、熔断降级和系统保护等功能,从而提高系统的稳定性和可靠性。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值