赞
踩
官网上的架构图
Sentinel 的核心部分为 ProcessorSlotChain
,由图可知是由将不同的 slot 按照指定顺序串在一起(责任链模式),通过此过滤链可以将监控统计、限流、熔断降级等功能整合在一起。系统会为每一个资源创建一套 SlotChain!
ps:使用@SentinelResource 注解定义资源并配置 blockHandler 和 fallback 函数来进行限流之后的处理
在springboot中会有自动配置功能,通过查看spring.factories文件找到 SentinelAutoConfiguration类
此类中,通过下面方法来实现了 Sentinel最核心的功能
点进去查看
此类是利用AspectJ 来实现 aop 增强,对 @SentinelResource标注的资源进行加强!Entry 可以是可以操作资源的对象
进入红框内的方法,经过一系列ctrl + 左键进入到如下方法
从上图可以总结出,Entry对象的创建有两大步:1. 创建 Context 2. 创建 ProcessorSlot
让我们再来重点看看这两个方法
protected static Context trueEnter(String name, String origin) { // 尝试着从ThreadLocal中获取Context Context context = contextHolder.get(); // 若ThreadLocal中没有context,则尝试着从缓存map中获取 if (context == null) { // 缓存map的key为context名称,value为EntranceNode Map<String, DefaultNode> localCacheNameMap = contextNameNodeMap; // 获取EntranceNode——双重检测锁DCL——为了防止并发创建 DefaultNode node = localCacheNameMap.get(name); if (node == null) { // 若缓存map的size 大于 context数量的最大阈值,则直接返回NULL_CONTEXT if (localCacheNameMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) { setNullContext(); return NULL_CONTEXT; } else { LOCK.lock(); try { 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); // Add entrance node.将新建的node添加到ROOT Constants.ROOT.addChild(node); // 将新建node写入到缓存map // 为了防止“迭代稳定性问题”——iterate stable——对于共享集合的写操作 Map<String, DefaultNode> newMap = new HashMap<>(contextNameNodeMap.size() + 1); newMap.putAll(contextNameNodeMap); newMap.put(name, node); contextNameNodeMap = newMap; } } } finally { LOCK.unlock(); } } } // 将context的name与entranceNode封装为context context = new Context(node, name); // 初始化context的来源 context.setOrigin(origin); // 将context写入到ThreadLocal contextHolder.set(context); } return context; }
重点看一下 DCL 机制 和 如何解决集合迭代性问题
ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) { // 从缓存map中获取当前资源的SlotChain // 缓存map的key为资源,value为其相关的SlotChain ProcessorSlotChain chain = chainMap.get(resourceWrapper); // DCL // 若缓存中没有相关的SlotChain,则创建一个并放入到缓存 if (chain == null) { synchronized (LOCK) { chain = chainMap.get(resourceWrapper); if (chain == null) { // Entry size limit. // 缓存map的size >= chain数量最大阈值,则直接返回null,不再创建新的chain 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; }
**SlotChainProvider.newSlotChain(); **
public static ProcessorSlotChain newSlotChain() { // 若builder不为null,则直接使用builder构建一个chain,否则先创建一个builder if (slotChainBuilder != null) { return slotChainBuilder.build(); } // Resolve the slot chain builder SPI. // 通过SPI方式创建一个builder slotChainBuilder = SpiLoader.of(SlotChainBuilder.class).loadFirstInstanceOrDefault(); // 若通过SPI方式未能创建builder,则手工new一个DefaultSlotChainBuilder if (slotChainBuilder == null) { // Should not go through here. RecordLog.warn("[SlotChainProvider] Wrong state when resolving slot chain builder, using default"); slotChainBuilder = new DefaultSlotChainBuilder(); } else { RecordLog.info("[SlotChainProvider] Global slot chain builder resolved: {}", slotChainBuilder.getClass().getCanonicalName()); } // 构建一个chain return slotChainBuilder.build(); }
通过 SPI 机制来加载指定的对象
接下来,开始执行操作(即在上面创建 Entry步骤中)
参考责任链模式,就是通过一个链表把不同 Slot 串联起来。
先了解一下固定的时间窗算法
该算法原理是,系统会自动选定一个时间窗口的起始零点,然后按照固定长度将时间轴划分为若干定长的时间窗口。
当请求到达时,系统会查看请求到达的时间点所在的时间窗口,其统计的数据是否超出了预定的阈值。未超出,则请求通过,否则被限流
存在的问题
跨窗口的时间窗长度范围内统计的数据却超出了阈值。这就是滑动时间窗要解决的问题
算法原理
滑动时间窗限流算法并没有划分固定的时间窗起点与终点,而是将每一次请求到来的时间点作为统计时间窗的终点,起点则是终点向前推时间窗长度的时间点。
算法改进:将时间窗口拆分为若干固定长度的样本窗口
这样每个样本窗口内统计值其对应时间段内的流量数据,可以提高数据的重复利用率
一些比较重要的概念:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。