仙桃做网站找谁域名流量查询工具
Sentinel系统自适应保护从整体维度对应用入口流量进行控制,结合应用的Load、总体平均RT、入口QPS和线程数等几个维度的监控指标,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
背景
在开始之前,先回顾一下Sentinel做系统自适应保护的目的:
- 保证系统不被拖垮
- 在系统稳定的前提下,保持系统的吞吐量
长期以来,系统自适应保护的思路是根据硬指标,即系统的负载 (load) 来做系统过载保护。当系统负载高于某个阈值,就禁止或者减少流量的进入;当load开始好转,则恢复流量的进入。这个思路给我们带来了不可避免的两个问题:
- load是一个“果”,如果根据load的情况来调节流量的通过率,那么就始终有延迟性。也就意味着通过率的任何调整,都会过一段时间才能看到效果。如果当前通过率是使load恶化的一个动作,那么也至少要过1秒之后才能观测到;同理,如果当前通过率调整是让load好转的一个动作,也需要1秒之后才能继续调整,这样就浪费了系统的处理能力。所以我们看到的曲线,总是会有抖动。
- 恢复慢。想象一下这样的一个场景(真实),出现了这样一个问题,下游应用不可靠,导致应用RT很高,从而load到了一个很高的点。过了一段时间之后下游应用恢复了,应用RT也相应减少。这个时候,其实应该大幅度增大流量的通过率;但是由于这个时候load仍然很高,通过率的恢复仍然不高。
TCP BBR的思想给了我们一个很大的启发。我们应该根据系统能够处理的请求,和允许进来的请求,来做平衡,而不是根据一个间接的指标(系统 load)来做限流。最终我们追求的目标是 在系统不被拖垮的情况下,提高系统的吞吐率,而不是load一定要到低于某个阈值。如果我们还是按照固有的思维,超过特定的load就禁止流量进入,系统load恢复就放开流量,这样做的结果是无论我们怎么调参数,调比例,都是按照果来调节因,都无法取得良好的效果。
Sentinel在系统自适应保护的做法是,用load作为启动控制流量的值,而允许通过的流量由处理请求的能力,即请求的响应时间以及当前系统正在处理的请求速率来决定。
系统规则
系统保护规则(简称系统规则)是从应用级别的入口流量进行控制,从单台机器的总体Load、RT、入口QPS和线程数四个维度监控应用数据,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(EntryType.IN),比如Web服务或Dubbo服务端接收的请求,都属于入口流量。
系统规则支持以下的阈值类型:
- Load(仅对Linux/Unix-like机器生效):当系统load超过阈值,且系统当前的并发线程数超过系统容量时才会触发系统保护。系统容量由系统的
maxQps*minRt
计算得出。设定参考值一般是CPU cores * 2.5
。 - CPU usage(1.5.0+ 版本):当系统CPU使用率超过阈值即触发系统保护(取值范围 0.0-1.0)。
- RT:当单台机器上所有入口流量的平均RT达到阈值即触发系统保护,单位是毫秒。
- 线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
- 入口QPS:当单台机器上所有入口流量的QPS达到阈值即触发系统保护。
原理
先用经典图来镇楼:
我们把系统处理请求的过程想象为一个水管,到来的请求是往这个水管灌水,当系统处理顺畅的时候,请求不需要排队,直接从水管中穿过,这个请求的RT是最短的;反之,当请求堆积的时候,那么处理请求的时间则会变为:排队时间 + 最短处理时间。
- 推论一:如果我们能够保证水管里的水量,能够让水顺畅的流动,则不会增加排队的请求;也就是说,这个时候的系统负载不会进一步恶化。我们用T来表示(水管内部的水量),用RT来表示请求的处理时间,用P来表示进来的请求数,那么一个请求从进入水管道到从水管出来,这个水管会存在
P*RT
个请求。换一句话来说,当T ≈ QPS * Avg(RT)
的时候,我们可以认为系统的处理能力和允许进入的请求个数达到了平衡,系统的负载不会进一步恶化。
接下来的问题是,水管的水位是可以达到了一个平衡点,但是这个平衡点只能保证水管的水位不再继续增高,但是还面临一个问题,就是在达到平衡点之前,这个水管里已经堆积了多少水。如果之前水管的水已经在一个量级了,那么这个时候系统允许通过的水量可能只能缓慢通过,RT会大,之前堆积在水管里的水会滞留;反之,如果之前的水管水位偏低,那么又会浪费了系统的处理能力。
- 推论二:当保持入口的流量使水管出来的流量达到最大值的时候,可以最大利用水管的处理能力。
然而,和TCP BBR的不一样的地方在于,还需要用一个系统负载的值(load)来激发这套机制启动。
注:这种系统自适应算法对于低load的请求,它的效果是一个“兜底”的角色。对于不是应用本身造成的load高的情况(如其它进程导致的不稳定的情况),效果不明显。
源码分析
Sentinel的系统规则的逻辑是由责任链中的SystemSlot来实现的。
SystemSlot
SystemSlot的逻辑主要是在请求目标方法之前调用SystemRuleManager.checkSystem
校验是否让请求通过。
com.alibaba.csp.sentinel.slots.system.SystemSlot#entry
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,boolean prioritized, Object... args) throws Throwable {SystemRuleManager.checkSystem(resourceWrapper);fireEntry(context, resourceWrapper, node, count, prioritized, args);
}
SystemRuleManager.checkSystem
比较入口流量的QPS,比较入口流量的并发线程数,比较平均RT响应时间,比较CPU使用率等,判断是否需要抛出SystemBlockException。
public static void checkSystem(ResourceWrapper resourceWrapper) throws BlockException {if (resourceWrapper == null) {return;}// Ensure the checking switch is on.if (!checkSystemStatus.get()) {// 校验未开启,直接返回return;}// for inbound traffic onlyif (resourceWrapper.getEntryType() != EntryType.IN) {return;}// total qps// 所有入口流量的QPSdouble currentQps = Constants.ENTRY_NODE == null ? 0.0 : Constants.ENTRY_NODE.successQps();if (currentQps > qps) {throw new SystemBlockException(resourceWrapper.getName(), "qps");}// total thread// 所有入口流量的并发线程数int currentThread = Constants.ENTRY_NODE == null ? 0 : Constants.ENTRY_NODE.curThreadNum();if (currentThread > maxThread) {throw new SystemBlockException(resourceWrapper.getName(), "thread");}// 所有入口流量的平均RT响应时间double rt = Constants.ENTRY_NODE == null ? 0 : Constants.ENTRY_NODE.avgRt();if (rt > maxRt) {throw new SystemBlockException(resourceWrapper.getName(), "rt");}// load. BBR algorithm.if (highestSystemLoadIsSet && getCurrentSystemAvgLoad() > highestSystemLoad) {// 系统load超过阈值if (!checkBbr(currentThread)) {throw new SystemBlockException(resourceWrapper.getName(), "load");}}// cpu usage// 系统CPU使用率if (highestCpuUsageIsSet && getCurrentCpuUsage() > highestCpuUsage) {throw new SystemBlockException(resourceWrapper.getName(), "cpu");}
}private static boolean checkBbr(int currentThread) {if (currentThread > 1 &¤tThread > Constants.ENTRY_NODE.maxSuccessQps() * Constants.ENTRY_NODE.minRt() / 1000) {// 系统当前的并发线程数超过系统容量// 系统容量由系统的 `maxQps*minRt`计算得出return false;}return true;
}
SystemStatusListener
系统的入口流量的QPS、平均RT响应时间、并发线程数都可以由责任链中的StatisticSlot进行统计的数据得到。
那么系统的CPU使用率和系统的平均负载从哪来呢?
在SystemRuleManager中,有一个静态代码块会启动一个线程池来每秒执行SystemStatusListener任务。
static {checkSystemStatus.set(false);statusListener = new SystemStatusListener();scheduler.scheduleAtFixedRate(statusListener, 0, 1, TimeUnit.SECONDS);currentProperty.addListener(listener);
}
SystemStatusListener每秒执行一次,调用操作系统的API获取系统的平均负载和CPU的使用率,保存至属性中,这样SystemRuleManager可以直接读取SystemStatusListener的属性来获取系统的平均负载和CPU的使用率。
com.alibaba.csp.sentinel.slots.system.SystemStatusListener#run
public void run() {try {// 获取操作系统的信息OperatingSystemMXBean osBean = ManagementFactory.getPlatformMXBean(OperatingSystemMXBean.class);// 系统的平均负载currentLoad = osBean.getSystemLoadAverage();double systemCpuUsage = osBean.getSystemCpuLoad();// calculate process cpu usage to support application running in container environmentRuntimeMXBean runtimeBean = ManagementFactory.getPlatformMXBean(RuntimeMXBean.class);long newProcessCpuTime = osBean.getProcessCpuTime();long newProcessUpTime = runtimeBean.getUptime();int cpuCores = osBean.getAvailableProcessors();long processCpuTimeDiffInMs = TimeUnit.NANOSECONDS.toMillis(newProcessCpuTime - processCpuTime);long processUpTimeDiffInMs = newProcessUpTime - processUpTime;double processCpuUsage = (double) processCpuTimeDiffInMs / processUpTimeDiffInMs / cpuCores;processCpuTime = newProcessCpuTime;processUpTime = newProcessUpTime;// 当前CPU的负载currentCpuUsage = Math.max(processCpuUsage, systemCpuUsage);if (currentLoad > SystemRuleManager.getSystemLoadThreshold()) {writeSystemStatusLog();}} catch (Throwable e) {RecordLog.warn("[SystemStatusListener] Failed to get system metrics from JMX", e);}
}
DynamicSentinelProperty
最后来看下系统规则加载过程。
在SystemRuleManager中,有一个静态方法(loadRules(List<SystemRule> rules))
去初始化SystemRule配置。
com.alibaba.csp.sentinel.slots.system.SystemRuleManager#loadRules
public static void loadRules(List<SystemRule> rules) {currentProperty.updateValue(rules);
}
而在更新currentProperty的时候,实质是通知观察者去更新,这里使用的是观察者模式。
com.alibaba.csp.sentinel.property.DynamicSentinelProperty#updateValue
protected Set<PropertyListener<T>> listeners = Collections.synchronizedSet(new HashSet<PropertyListener<T>>());
private T value = null;public boolean updateValue(T newValue) {// 如果两个值一样,则返回false,不修改if (isEqual(value, newValue)) {return false;}RecordLog.info("[DynamicSentinelProperty] Config will be updated to: {}", newValue);value = newValue;// 通知各个观察者for (PropertyListener<T> listener : listeners) {listener.configUpdate(newValue);}return true;
}
这里的观察者为SystemPropertyListener,在SystemRuleManager的静态方法区已经添加进去。
private final static SystemPropertyListener listener = new SystemPropertyListener();
private static SentinelProperty<List<SystemRule>> currentProperty = new DynamicSentinelProperty<List<SystemRule>>();static {checkSystemStatus.set(false);statusListener = new SystemStatusListener();scheduler.scheduleAtFixedRate(statusListener, 0, 1, TimeUnit.SECONDS);// 添加观察者currentProperty.addListener(listener);
}
而在SystemPropertyListener更新的时候,会先关闭系统检查,当配置修改完成之后,再启用。
com.alibaba.csp.sentinel.slots.system.SystemRuleManager.SystemPropertyListener#configUpdate
public synchronized void configUpdate(List<SystemRule> rules) {// 恢复到默认状态restoreSetting();// systemRules = rules;if (rules != null && rules.size() >= 1) {for (SystemRule rule : rules) {// 加载系统规则配置loadSystemConf(rule);}} else {checkSystemStatus.set(false);}RecordLog.info(String.format("[SystemRuleManager] Current system check status: %s, "+ "highestSystemLoad: %e, "+ "highestCpuUsage: %e, "+ "maxRt: %d, "+ "maxThread: %d, "+ "maxQps: %e",checkSystemStatus.get(),highestSystemLoad,highestCpuUsage,maxRt,maxThread,qps));
}protected void restoreSetting() {// 将各个参数恢复到默认值checkSystemStatus.set(false);// should restore changeshighestSystemLoad = Double.MAX_VALUE;highestCpuUsage = Double.MAX_VALUE;maxRt = Long.MAX_VALUE;maxThread = Long.MAX_VALUE;qps = Double.MAX_VALUE;highestSystemLoadIsSet = false;highestCpuUsageIsSet = false;maxRtIsSet = false;maxThreadIsSet = false;qpsIsSet = false;
}
加载规则的时候则判断是否小于默认配置,由于之前已经重新初始化过了,所以如果有修改,肯定会比默认的值小。
com.alibaba.csp.sentinel.slots.system.SystemRuleManager#loadSystemConf
public static void loadSystemConf(SystemRule rule) {boolean checkStatus = false;// Check if it's valid.if (rule.getHighestSystemLoad() >= 0) {highestSystemLoad = Math.min(highestSystemLoad, rule.getHighestSystemLoad());highestSystemLoadIsSet = true;checkStatus = true;}if (rule.getHighestCpuUsage() >= 0) {if (rule.getHighestCpuUsage() > 1) {RecordLog.warn(String.format("[SystemRuleManager] Ignoring invalid SystemRule: "+ "highestCpuUsage %.3f > 1", rule.getHighestCpuUsage()));} else {highestCpuUsage = Math.min(highestCpuUsage, rule.getHighestCpuUsage());highestCpuUsageIsSet = true;checkStatus = true;}}if (rule.getAvgRt() >= 0) {maxRt = Math.min(maxRt, rule.getAvgRt());maxRtIsSet = true;checkStatus = true;}if (rule.getMaxThread() >= 0) {maxThread = Math.min(maxThread, rule.getMaxThread());maxThreadIsSet = true;checkStatus = true;}if (rule.getQps() >= 0) {qps = Math.min(qps, rule.getQps());qpsIsSet = true;checkStatus = true;}checkSystemStatus.set(checkStatus);}
配置加载完毕,如果有SystemRule配置,则将checkSystemStatus改为true。