Spring Cloud + Alibaba Sentinel 源码原理深度剖析!(下)

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
接上:
Spring Cloud + Alibaba Sentinel 源码原理深度剖析!(上)
Spring Cloud + Alibaba Sentinel 源码原理深度剖析!(中)

3、预热/冷启动策略

WarmUpController

首先看WarmUpController的属性和构造方法:

// 阈值
protected double count;
/**
* 冷启动的因子 ,默认为3 {@link SentinelConfig#coldFactor()}
*/
private int coldFactor;
// 转折点的令牌数
protected int warningToken = 0;
// 最大令牌数
private int maxToken;
// 折线初始斜率,标志流量的变化程度
protected double slope;

// 累积的令牌数 ,累积的令牌数越多,说明系统利用率越低,说明当前流量低,是冷状态
protected AtomicLong storedTokens = new AtomicLong(0);
// 最后更新令牌的时间
protected AtomicLong lastFilledTime = new AtomicLong(0);

public WarmUpController(double count, int warmUpPeriodInSec, int coldFactor) {
   construct(count, warmUpPeriodInSec, coldFactor);
}

public WarmUpController(double count, int warmUpPeriodInSec) {
   construct(count, warmUpPeriodInSec, 3);
}

/**
* @param count             用户设定的阈值(这里假设设定为100)
* @param warmUpPeriodInSec 默认为10
* @param coldFactor       默认为3
*/
private void construct(double count, int warmUpPeriodInSec, int coldFactor) {

   if (coldFactor <= 1) {
       throw new IllegalArgumentException("Cold factor should be larger than 1");
  }

   this.count = count;

   this.coldFactor = coldFactor;

   // thresholdPermits = 0.5 * warmupPeriod / stableInterval.
   // warningToken = 100;

   // 按默认的warmUpPeriodInSec = 10,表示1秒钟10个请求,则每个请求为间隔stableInterval = 100ms,那么coldInterval=stableInterval * coldFactor = 100 * 3 = 300ms
   // warningToken = 10 * 100 / (3 - 1) = 500
   // thresholdPermits = warningToken = 0.5 * warmupPeriod / stableInterval = 0.5 * warmupPeriod / 100ms = 500 ==>> warmupPeriod = 100000ms
   warningToken = (int)(warmUpPeriodInSec * count) / (coldFactor - 1);

   // / maxPermits = thresholdPermits + 2 * warmupPeriod /
   // (stableInterval + coldInterval)
   // maxToken = 200

   // maxPermits = 500 + 2 * 100000ms / (100ms + 300ms) = 1000
   // maxToken = 500 + (2 * 10 * 100 / (1.0 + 3)) = 1000
   // maxPermits = maxToken
   maxToken = warningToken + (int)(2 * warmUpPeriodInSec * count / (1.0 + coldFactor));

   // slope
   // slope = (coldIntervalMicros - stableIntervalMicros) / (maxPermits
   // - thresholdPermits);

   // slope = (3 - 1.0) / 100 / (600 - 500) = 0.0002
   slope = (coldFactor - 1.0) / count / (maxToken - warningToken);

}


属性说明:

  • count: 用户设定的qps阈值。

  • coldFactor: 冷启动的因子,初始默认为3,通过SentinelConfig类的coldFactor()方法获取,这里会有个判断,如果启动因子小于等于1,则会设置为默认值3,因为如果是小于等于1,是没有意义的,就不是预热启动了。

  • warningToken:转折点的令牌数,当令牌数开始小于该值得时候,就要开启预热了。

  • maxToken:最大令牌数。

  • slope:折线的斜率。

  • storedTokens:当前存储的令牌数。

  • lastFilledTime:上一次更新令牌的时间。


总体思路:当系统存储的令牌为最大值时,说明系统访问流量较低,处于冷状态,这时候当有正常请求过来时,会让请求通过,并且会补充消耗的令牌数。当瞬时流量来临时,一旦剩余的令牌数小于警戒令牌数(restToken <= warningToken),则表示有大流量过来,需要开启预热过程,开始逐渐增大允许的qps。当qps达到用户设定的阈值后,系统已经预热完毕,这时候就进入了正常的请求阶段。


源码分析如下:

@Override
public boolean canPass(Node node, int acquireCount, boolean prioritized) {
   // 当前已经通过的qps
   long passQps = (long) node.passQps();

   // 上一个滑动窗口的qps
   long previousQps = (long) node.previousPassQps();
   // 同步令牌,如果是出于冷启动或预热完毕状态,则考虑要添加令牌
   syncToken(previousQps);

   // 开始计算它的斜率
   // 如果进入了警戒线,开始调整他的qps
   long restToken = storedTokens.get();
   if (restToken >= warningToken) { // 说明一瞬间有大流量过来,消耗了大量的存储令牌,造成剩余令牌数第一警戒值,则要开启预热默认,逐渐增加qps
       // 计算当前离警戒线的距离
       long aboveToken = restToken - warningToken;
       // 消耗的速度要比warning快,但是要比慢
       // current interval = restToken*slope+1/count
       // restToken越小,interval就越小,表示系统越热
       // 随着aboveToken的减小,warningQps会逐渐增大
       double warningQps = Math.nextUp(1.0 / (aboveToken * slope + 1.0 / count));
       if (passQps + acquireCount <= warningQps) { // 随着warningQps的增大,acquireCount = 1,那么passQps允许的范围就变大,相应的流量就越大,系统越热
           return true;
      }
  } else {
       if (passQps + acquireCount <= count) {
           return true;
      }
  }

   return false;
}

/**
* 同步令牌
* @param passQps
*/
protected void syncToken(long passQps) {
   long currentTime = TimeUtil.currentTimeMillis();
   // 把当前时间的后三位置为0 e.g. 1601456312835 = 1601456312835 - 1601456312835 % 1000 = 1601456312000
   currentTime = currentTime - currentTime % 1000;
   // 获取上一次更新令牌的时间
   long oldLastFillTime = lastFilledTime.get();
   if (currentTime <= oldLastFillTime) {
       return;
  }

   // 获得目前的令牌数
   long oldValue = storedTokens.get();
   // 获取新的令牌数
   long newValue = coolDownTokens(currentTime, passQps);

   // 更新累积令牌数
   if (storedTokens.compareAndSet(oldValue, newValue)) {
       // 去除上一次的qps,设置剩下的令牌数
       long currentValue = storedTokens.addAndGet(0 - passQps);
       if (currentValue < 0) {
           // 如果剩下的令牌数小于0,则置为0。
           storedTokens.set(0L);
      }
       // 设置令牌更新时间
       lastFilledTime.set(currentTime);
  }
}

private long coolDownTokens(long currentTime, long passQps) {
   // 当前拥有的令牌数
   long oldValue = storedTokens.get();
   long newValue = oldValue;

   // 添加令牌的判断前提条件:
   // 当令牌的消耗程度远远低于警戒线的时候
   if (oldValue < warningToken) { // 这种情况表示已经预热结束,可以开始生成令牌了
       // 这里按照count = 100来计算的话,表示旧值oldValue + 距离上次更新的秒数时间差 * count ,表示每秒增加count个令牌
       // 这里的currentTime 和 lastFilledTime.get() 都是已经去掉毫秒数的
       newValue = (long)(oldValue + (currentTime - lastFilledTime.get()) * count / 1000);
  } else if (oldValue > warningToken) { // 进入这里表示当前是冷状态或正处于预热状态
       if (passQps < (int)count / coldFactor) { // 如果是冷状态,则补充令牌数,避免令牌数为0
           newValue = (long)(oldValue + (currentTime - lastFilledTime.get()) * count / 1000);
      }
       // 预热阶段则不添加令牌数,从而限制流量的急剧攀升
  }
   // 限制令牌数不能超过最大令牌数maxToken
   return Math.min(newValue, maxToken);
}


4、预热的匀速排队策略

WarmUpRateLimiterController

这种是匀速排队模式和预热模式的结合,这里不深入了。搞懂了上面两种,再看这种也比较清晰了。

5、DegradeSlot

官方文档说明:

这个 slot 主要针对资源的平均响应时间(RT)以及异常比率,来决定资源是否在接下来的时间被自动熔断掉。

源码解析:

@Override
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
                 boolean prioritized, Object... args) throws Throwable {
   //降级判断
   performChecking(context, resourceWrapper);

   // 如果有自定义的slot,还会继续进行
   fireEntry(context, resourceWrapper, node, count, prioritized, args);
}

void performChecking(Context context, ResourceWrapper r) throws BlockException {
   // 使用DegradeRuleManager获得当前资源的熔断器
   List<CircuitBreaker> circuitBreakers =DegradeRuleManager.getCircuitBreakers(r.getName());
   if (circuitBreakers == null || circuitBreakers.isEmpty()) {
       return;
  }
   // 遍历熔断器,只要有任何一个满足熔断条件,就抛出DegradeException异常。
   for (CircuitBreaker cb : circuitBreakers) {
       if (!cb.tryPass(context)) {
           throw new DegradeException(cb.getRule().getLimitApp(), cb.getRule());
      }
  }
}

这里有个关键类,DegradeRuleManager,该类中会保存所有的熔断规则,使用Map<String, List>的格式进行保存。当需要使用的时候,就直接根据资源名称,从该map中获取对应的熔断器列表。


那么规则是如何加载的呢?我们看到DegradeRuleManager这个类,在加载时候,有个静态代码块:

private static final RulePropertyListener LISTENER = new RulePropertyListener();
private static SentinelProperty<List<DegradeRule>> currentProperty
   = new DynamicSentinelProperty<>();

static {
   currentProperty.addListener(LISTENER);
}


currentProperty.addListener(LISTENER);继续分析该段代码,找到DynamicSentinelProperty的addListener(…)方法:

@Override
public void addListener(PropertyListener<T> listener) {
   listeners.add(listener);
   listener.configLoad(value);
}
12345

发现会调用监听器的configLoad(…)方法,最终会调用RulePropertyListener这个类的reloadFrom(…)方法。具体怎么解析的其实就是将规则根据资源名称进行归类,并保存为map格式。


-     FlowSlot 限流规则引擎之限流算法原理     -

1、滑动窗口实现原理

attachments-2021-01-x5JbWHJx5ffe6ba81bc3b.jpg

  • 每个时间窗口最大流量为100QPS;

  • 20和80表示当时的真实QPS数量;

  • 一个时间窗口分为两个半限,上半限和下半限;

  • 如果时间窗口1的下半限和时间窗口2的上半限的峰值超过100QPS,那么就丢失一部分流量。


但是这样并不是我们想要的,那么我们来看看计数器滑动窗口。


2、计数器滑动窗口原理

attachments-2021-01-VJwKiWMO5ffe6b9df1b50.jpg

  • 在滑动窗口算法上优化;

  • 相邻的两个半限总和>总阈值,才丢弃流量。


3、令牌桶算法

attachments-2021-01-c4sfQn365ffe6b923a2af.jpg

  • 令牌漏斗桶存着所有的Token;

  • 按期发放Token;

  • 如果桶满了,就会熔断;

  • 达到Token的Request可以获取资源;

  • 得不到的就抛弃。


-     图文总结     -

1、整体流程

(1)请求发送到web容器;

(2)Sentinel Aop拦截所有Sentinel Resouce;

(3)如果资源的规则通过则执行正常流程;

(4)不通过则返回流控异常提示。

attachments-2021-01-HOXrF4AI5ffe6b83e834b.jpg

2、Sentinel AOP切面运行流程

attachments-2021-01-ScvqYZPk5ffe6b7a054e2.jpg

希望对大家有帮助!

attachments-2021-01-1vPV3aRh5ffe6c082c220.jpeg

  • 发表于 2021-01-13 11:41
  • 阅读 ( 34 )

0 条评论

请先 登录 后评论
奈学教育
奈学教育

官方

150 篇文章

作家榜 »

  1. NX小编 1251 文章
  2. 58沈剑 322 文章
  3. 热爱技术的小仓鼠 169 文章
  4. 奈学教育 150 文章
  5. 李希沅 | 奈学教育 51 文章
  6. 江帅帅 | 奈学教育 32 文章
  7. 林淮川 | 奈学教育 12 文章
  8. 邱鹏超 3 文章