Sentinel -- FLOW SLOT核心原理篇
这个节点是sentinel流控接口,主要承担的作用是限流和预热。还是老套路,在介绍源码之前先介绍一下源码中用到的几个核心原理,这样大家看源码相对轻松一些。
1、核心算法
1.1 漏洞算法和令牌通算法
1.2预热桶算法
系统在初始化或者长时间处于低利用率下,系统由于所依赖资源的限制,并不能立马达到它正常的服务水平;例如系统依赖的缓存过期导致新的请求会直接请求 db,再比如很多系统使用了连接池,长时间的 idle 情况下连接池只会保持少量的连接,新的请求会重新创建连接,这些都是耗时操作,完成这些操作之后,系统才能到达正常的服务水平。这时候变需要预热桶算法。
预热桶算法本质是按照一个 y=mx+b的一个线性函数,将投入令牌桶的时间间隔又大到小的过程。下面是guava源码中的一个函数图。
throttling | cold + | / interval | | /. | | / . | |/ . | + . ← "f(storedPermits)" | /| . | / | . | / | . stable +----------/ | . ← "warmup period" interval | . | . is the area of the trapezoid between thresholdPermits and maxPermits | . | . | . | . 0 +----------+---|---+--------------→ storedPermits 0 thresholdPermits maxPermits
我们可以定义一个冷却因子(coldFactor) ,令系统处于最冷的状态下获取一个令牌的时长 coldInterval = stableInterval * coldFactor
;「预热桶」从最冷状态到完成预热进入稳定期有个转折点,到达这个转折点时的令牌数量我们用 thresholdPermits 表示;这样,我们就获得了一个获取(一个)令牌的时长随着令牌数量变化的连续函数 f(storedPermits) :
1) 0 <= storedPermits <= thresholdPermits 时;f(storedPermits) = stableInterval; // 常数函数,函数值始终为 stableInterval ;
2) thresholdPermits <= storedPermits <= maxPermits 时,f(storedPermits) = (coldInterval - stableInterval) * storedPermits / (maxPermits -thresholdPermits); // 正比例函数,比例常数为 (coldInterval - stableInterval) / (maxPermits - thresholdPermits) ;
在上面这张图中,我们画一条与 x 轴垂直的线 n,这条线与函数曲线的交点的纵坐标当前 storedPermits 数量下获取单个令牌所需的时间;
当我们从右向左移动 n 时,表示系统接收到请求,令牌正在被消耗,假设系统连续接收到 k 个请求,获取对应令牌所需要的时间为:t = f(maxPermits) + f(maxPermits - 1) + f(maxPermits - 2) + ... + f(maxPermits - k),通过微积分的知识可以看出来这是在求函数 f 在 maxPermits - k 到 maxPermits 区间的定积分,可以用这个区间的函数图形的面积表示。也就是说图中梯形的面积为我们的预热时间。图中矩形的面积为预热时间的一半。网上及guava源码出的解释:英文冷却因子是3,然后code inteval 到 stable inteval的值是stable inteval到0值的2倍所以预热面积也是2倍的关系。
2、规则加载FlowRuleManager
1 FlowRuleManager初始化属性
存放限流规则的集合
private static volatile Map<String, List<FlowRule>> flowRules = new HashMap<>();
规则加载/更新
// 规则加载/更新器 private static final FlowPropertyListener LISTENER = new FlowPropertyListener(); // 私有内部类实现(策略模式)限流属性更新加载器 private static final class FlowPropertyListener implements PropertyListener<List<FlowRule>> { @Override public void configUpdate(List<FlowRule> value) { Map<String, List<FlowRule>> rules = FlowRuleUtil.buildFlowRuleMap(value); if (rules != null) { flowRules.clear(); flowRules.putAll(rules); } RecordLog.info("[FlowRuleManager] Flow rules received: " + flowRules); } @Override public void configLoad(List<FlowRule> conf) { Map<String, List<FlowRule>> rules = FlowRuleUtil.buildFlowRuleMap(conf); if (rules != null) { flowRules.clear(); flowRules.putAll(rules); } RecordLog.info("[FlowRuleManager] Flow rules loaded: " + flowRules); } }
变化规则集合
// 规则更新执行的调用者 private static SentinelProperty<List<FlowRule>> currentProperty = new DynamicSentinelProperty<List<FlowRule>>();
创建一个延迟线程池(数据统计监听器)
@SuppressWarnings("PMD.ThreadPoolCreationRule") private static final ScheduledExecutorService SCHEDULER = Executors.newScheduledThreadPool(1, new NamedThreadFactory("sentinel-metrics-record-task", true));
规则初始化和开启数据统计监听任务
static { // 设置监听 currentProperty.addListener(LISTENER); // 每一秒钟调用MetricTimerListener的方法 SCHEDULER.scheduleAtFixedRate(new MetricTimerListener(), 0, 1, TimeUnit.SECONDS); }
3、FLOW SLOT源码解读