当前位置: 首页 > news >正文

Sentinel源码—3.ProcessorSlot的执行过程二

大纲

1.NodeSelectorSlot构建资源调用树

2.LogSlot和StatisticSlot采集资源的数据

3.Sentinel监听器模式的规则对象与规则管理

4.AuthoritySlot控制黑白名单权限

5.SystemSlot根据系统保护规则进行流控

3.Sentinel监听器模式的规则对象与规则管理

(1)Sentinel的规则对象

(2)Sentinel的规则管理

(1)Sentinel的规则对象

一.Sentinel中的规则其实就是配置

二.规则接口Rule和抽象父类AbstractRule及其具体实现类

一.Sentinel中的规则其实就是配置

黑白名单控制规则:例如需要设置一份配置,确定哪些请求属于黑名单、哪些请求属于白名单,那么这份配置就是黑白名单控制规则。

系统负载自适应规则:例如需要设置当CPU使用率达到90%时,系统就不再接受新请求以防止系统崩溃,那么这个90%的CPU使用率阈值就是系统负载自适应规则。

流量控制规则:例如需要设置单机QPS最高为100,那么这个单机限流100QPS便是流量控制规则。

熔断降级规则:例如需要设置当错误比例在1秒内超过10次时,系统自动触发熔断降级,那么这个1秒内超过10次的错误比例就是熔断降级规则。

二.规则接口Rule和抽象父类AbstractRule及其具体实现类

首先规则与资源是紧密关联的,规则会对资源起作用,因此规则接口Rule需要一个获取资源的方法getResource()。

然后每一条具体的规则都应继承抽象父类AbstractRule并具备三个字段:规则id、资源name以及针对来源limitApp。其中针对来源指的是诸如黑名单值、白名单值等,默认是default。

//Base interface of all rules.
public interface Rule {//Get target resource of this rule.//获取当前规则起作用的目标资源String getResource();
}//Abstract rule entity. AbstractRule是实现了规则接口Rule的抽象规则类
public abstract class AbstractRule implements Rule {//rule id. 规则idprivate Long id;//Resource name. 资源名称private String resource;//针对来源,默认是default//多个来源使用逗号隔开,比如黑名单规则,限制userId是1和3的访问,那么就设置limitApp为"1,3"//Application name that will be limited by origin.//The default limitApp is default, which means allowing all origin apps.//For authority rules, multiple origin name can be separated with comma (',').private String limitApp;public Long getId() {return id;}public AbstractRule setId(Long id) {this.id = id;return this;}@Overridepublic String getResource() {return resource;}public AbstractRule setResource(String resource) {this.resource = resource;return this;}public String getLimitApp() {return limitApp;}public AbstractRule setLimitApp(String limitApp) {this.limitApp = limitApp;return this;}...
}//Authority rule is designed for limiting by request origins.
public class AuthorityRule extends AbstractRule {...
}public class SystemRule extends AbstractRule {...
}public class FlowRule extends AbstractRule {...
}public class DegradeRule extends AbstractRule {...
}

(2)Sentinel的规则管理

一.PropertyListener<T>监听器接口及其实现类

二.SentinelProperty监听器接口管理所有PropertyListener<T>子类

三.DynamicSentinelProperty会触发监听器PropertyListener<T>的回调

一.PropertyListener<T>监听器接口及其实现类

为了感知规则Rule的变化,需要一个负责监听规则变化的类,也就是需要一个监听器来监听规则Rule的变化,这个监听器就是PropertyListener<T>。

PropertyListener<T>是一个接口,它定义了两个方法:方法一是首次加载规则时触发的回调方法configLoad(),方法二是规则变更时触发的回调方法configUpdate()。

PropertyListener<T>接口使用了泛型T而不是规则接口Rule来定义,是因为除了规则的变化需要监听器监听外,还有其他场景也需要监听。

PropertyListener<T>接口的具体实现类有:

AuthorityRuleManager.RulePropertyListener
FlowRuleManager.FlowPropertyListener
DegradeRuleManager.RulePropertyListener
SystemRuleManager.SystemPropertyListener
//This class holds callback method when SentinelProperty.updateValue(Object) need inform the listener.
public interface PropertyListener<T> {//Callback method when {@link SentinelProperty#updateValue(Object)} need inform the listener.//规则变更时触发的回调方法void configUpdate(T value);//The first time of the {@code value}'s load.//首次加载规则时触发的回调方法void configLoad(T value);
}//Manager for authority rules.
public final class AuthorityRuleManager {//key是资源名称,value是资源对应的规则private static volatile Map<String, Set<AuthorityRule>> authorityRules = new ConcurrentHashMap<>();//饿汉式单例模式实例化黑白名单权限控制规则的监听器对象private static final RulePropertyListener LISTENER = new RulePropertyListener();//监听器对象的管理器private static SentinelProperty<List<AuthorityRule>> currentProperty = new DynamicSentinelProperty<>();static {//将黑白名单权限控制规则的监听器对象添加到DynamicSentinelProperty中currentProperty.addListener(LISTENER);}...private static class RulePropertyListener implements PropertyListener<List<AuthorityRule>> {@Overridepublic synchronized void configLoad(List<AuthorityRule> value) {authorityRules = loadAuthorityConf(value);RecordLog.info("[AuthorityRuleManager] Authority rules loaded: {}", authorityRules);}@Overridepublic synchronized void configUpdate(List<AuthorityRule> conf) {authorityRules = loadAuthorityConf(conf);RecordLog.info("[AuthorityRuleManager] Authority rules received: {}", authorityRules);}private Map<String, Set<AuthorityRule>> loadAuthorityConf(List<AuthorityRule> list) {Map<String, Set<AuthorityRule>> newRuleMap = new ConcurrentHashMap<>();if (list == null || list.isEmpty()) {return newRuleMap;}for (AuthorityRule rule : list) {if (!isValidRule(rule)) {RecordLog.warn("[AuthorityRuleManager] Ignoring invalid authority rule when loading new rules: {}", rule);continue;}if (StringUtil.isBlank(rule.getLimitApp())) {rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT);}String identity = rule.getResource();Set<AuthorityRule> ruleSet = newRuleMap.get(identity);//putIfAbsentif (ruleSet == null) {ruleSet = new HashSet<>();ruleSet.add(rule);newRuleMap.put(identity, ruleSet);} else {//One resource should only have at most one authority rule, so just ignore redundant rules.RecordLog.warn("[AuthorityRuleManager] Ignoring redundant rule: {}", rule.toString());}}return newRuleMap;}}...
}public class FlowRuleManager {private static volatile Map<String, List<FlowRule>> flowRules = new HashMap<>();private static final FlowPropertyListener LISTENER = new FlowPropertyListener();private static SentinelProperty<List<FlowRule>> currentProperty = new DynamicSentinelProperty<List<FlowRule>>();static {currentProperty.addListener(LISTENER);startMetricTimerListener();}...private static final class FlowPropertyListener implements PropertyListener<List<FlowRule>> {@Overridepublic synchronized void configUpdate(List<FlowRule> value) {Map<String, List<FlowRule>> rules = FlowRuleUtil.buildFlowRuleMap(value);if (rules != null) {flowRules = rules;}RecordLog.info("[FlowRuleManager] Flow rules received: {}", rules);}@Overridepublic synchronized void configLoad(List<FlowRule> conf) {Map<String, List<FlowRule>> rules = FlowRuleUtil.buildFlowRuleMap(conf);if (rules != null) {flowRules = rules;}RecordLog.info("[FlowRuleManager] Flow rules loaded: {}", rules);}}...
}public final class DegradeRuleManager {private static final RulePropertyListener LISTENER = new RulePropertyListener();private static SentinelProperty<List<DegradeRule>> currentProperty = new DynamicSentinelProperty<>();static {currentProperty.addListener(LISTENER);}...private static class RulePropertyListener implements PropertyListener<List<DegradeRule>> {...@Overridepublic void configUpdate(List<DegradeRule> conf) {reloadFrom(conf);RecordLog.info("[DegradeRuleManager] Degrade rules has been updated to: {}", ruleMap);}@Overridepublic void configLoad(List<DegradeRule> conf) {reloadFrom(conf);RecordLog.info("[DegradeRuleManager] Degrade rules loaded: {}", ruleMap);}...}...
}

二.SentinelProperty监听器接口管理所有PropertyListener<T>子类

为了在创建规则时回调configLoad()方法初始化规则配置,以及在规则变更时回调configUpdate()方法通知到所有监听者,需要一个类来管理所有监听器,比如将所有监听器添加到集合中。当配置发生变化时,就可以遍历监听器集合然后调用回调方法进行处理。

其实就是使用监听器模式或观察者模式,创建一个实现了SentinelProperty接口的类,专门负责管理所有实现了PropertyListener<T>接口的监听器。

其中SentinelProperty接口如下所示:

//This class holds current value of the config, 
//and is responsible for informing all PropertyListeners added on this when the config is updated.
//Note that not every updateValue(Object newValue) invocation should inform the listeners, 
//only when newValue is not Equals to the old value, informing is needed.
public interface SentinelProperty<T> {//添加监听者//Add a PropertyListener to this SentinelProperty.//After the listener is added, updateValue(Object) will inform the listener if needed.//This method can invoke multi times to add more than one listeners.void addListener(PropertyListener<T> listener);//移除监听者//Remove the PropertyListener on this. //After removing, updateValue(Object) will not inform the listener.void removeListener(PropertyListener<T> listener);//当监听值有变化时,调用此方法进行通知//Update the newValue as the current value of this property and inform all//PropertyListeners added on this only when new value is not Equals to the old value.boolean updateValue(T newValue);
}

三.DynamicSentinelProperty会触发监听器PropertyListener<T>的回调

DynamicSentinelProperty会使用写时复制集合CopyOnWriteArraySet来存储监听器,当DynamicSentinelProperty添加监听器或者更新新值时,便会触发执行PropertyListener<T>接口的两个回调方法。

具体就是:当执行DynamicSentinelProperty的addListener()方法添加监听器时,会将监听器保存到DynamicSentinelProperty的写时复制集合CopyOnWriteArraySet中,并且回调监听器的configLoad()方法来初始化规则配置。由于监听器监听的是规则,而规则又是和资源绑定的,所以初始化就是将资源和规则绑定到一个Map中:即形如Map<String resourcename, Set<Rule>>这样的Map。

当执行DynamicSentinelProperty的updateValue()方法更新规则配置时,则会遍历所有监听器并调用每个监听器的configUpdate()方法进行更新,也就是更新Map<String resourcename, Set<Rule>>这种Map里的value。

public class DynamicSentinelProperty<T> implements SentinelProperty<T> {protected Set<PropertyListener<T>> listeners = new CopyOnWriteArraySet<>();private T value = null;public DynamicSentinelProperty() {}public DynamicSentinelProperty(T value) {super();this.value = value;}//添加监听器到集合@Overridepublic void addListener(PropertyListener<T> listener) {listeners.add(listener);//回调监听器的configLoad()方法初始化规则配置listener.configLoad(value);}//移除监听器@Overridepublic void removeListener(PropertyListener<T> listener) {listeners.remove(listener);}//更新值@Overridepublic boolean updateValue(T newValue) {//如果值没变化,直接返回if (isEqual(value, newValue)) {return false;}RecordLog.info("[DynamicSentinelProperty] Config will be updated to: {}", newValue);value = newValue;//如果值发生了变化,则遍历监听器,回调监听器的configUpdate()方法更新对应的值for (PropertyListener<T> listener : listeners) {listener.configUpdate(newValue);}return true;}...
}

(3)总结

一.PropertyListener<T>

PropertyListener<T>是一个泛型接口,用于监听配置变更。它包含两个方法:configUpdate()方法和configLoad()方法。

PropertyListener的configUpdate()方法在配置发生变化时触发,PropertyListener的configLoad()方法在首次加载配置时触发。通过实现PropertyListener<T>接口,可以实现不同类型的监听器,例如FlowPropertyListener等。

二.SentinelProperty

SentinelProperty是一个用于管理PropertyListener监听器的接口,它提供了添加、移除和更新监听器的方法。

添加监听器可调用SentinelProperty实现类的addListener()方法实现添加,配置变更可调用SentinelProperty实现类的updateValue()方法通知监听器。Sentinel提供了默认的SentinelProperty实现:DynamicSentinelProperty。

4.AuthoritySlot控制黑白名单权限

(1)黑白名单权限控制规则的配置Demo

(2)AuthoritySlot验证黑白名单权限控制规则

(1)黑白名单权限控制规则的配置Demo

一.配置黑白名单权限控制规则的过程

二.AuthorityRuleManager初始化和加载黑白名单权限控制规则详情

一.配置黑白名单权限控制规则的过程

首先创建一个AuthorityRule规则对象,然后设置三个关键要素:通过setStrategy()方法设置规则是黑名单还是白名单、通过setResource()方法设置规则绑定到哪个资源、通过setLimitApp()方法设置限制哪些来源,最后调用AuthorityRuleManager的loadRules()方法加载此规则。所以黑白名单权限规则是通过AuthorityRuleManager类来进行管理的。

//Authority rule is designed for limiting by request origins.
//In blacklist mode, requests will be blocked when blacklist contains current origin, otherwise will pass.
//In whitelist mode, only requests from whitelist origin can pass.
public class AuthorityDemo {private static final String RESOURCE_NAME = "testABC";public static void main(String[] args) {System.out.println("========Testing for black list========");initBlackRules();testFor(RESOURCE_NAME, "appA");testFor(RESOURCE_NAME, "appB");testFor(RESOURCE_NAME, "appC");testFor(RESOURCE_NAME, "appE");System.out.println("========Testing for white list========");initWhiteRules();testFor(RESOURCE_NAME, "appA");testFor(RESOURCE_NAME, "appB");testFor(RESOURCE_NAME, "appC");testFor(RESOURCE_NAME, "appE");}private static void testFor(String resource, String origin) {ContextUtil.enter(resource, origin);Entry entry = null;try {entry = SphU.entry(resource);System.out.println(String.format("Passed for resource %s, origin is %s", resource, origin));} catch (BlockException ex) {System.err.println(String.format("Blocked for resource %s, origin is %s", resource, origin));} finally {if (entry != null) {entry.exit();}ContextUtil.exit();}}private static void initWhiteRules() {AuthorityRule rule = new AuthorityRule();rule.setResource(RESOURCE_NAME);rule.setStrategy(RuleConstant.AUTHORITY_WHITE);rule.setLimitApp("appA,appE");AuthorityRuleManager.loadRules(Collections.singletonList(rule));}private static void initBlackRules() {AuthorityRule rule = new AuthorityRule();rule.setResource(RESOURCE_NAME);rule.setStrategy(RuleConstant.AUTHORITY_BLACK);rule.setLimitApp("appA,appB");AuthorityRuleManager.loadRules(Collections.singletonList(rule));}
}

二.AuthorityRuleManager初始化和加载黑白名单权限控制规则详情

AuthorityRuleManager类中有一个静态变量LISTENER,该变量指向由饿汉式单例模式实例化的黑白名单权限控制规则监听器对象。

AuthorityRuleManager类有一个静态代码块,在该代码块中,会调用DynamicSentinelProperty的addListener(LISTENER)方法,将黑白名单权限控制规则的监听器对象添加到DynamicSentinelProperty。

在DynamicSentinelProperty的addListener()方法中,又会回调LISTENER的configLoad()方法初始化黑白名单权限规则。

当AuthorityDemo调用AuthorityRuleManager的loadRules()方法加载规则时,便会执行DynamicSentinelProperty的updateValue()方法,也就是会触发执行LISTENER的configUpdate()方法加载权限规则到一个map中,即执行RulePropertyListener的loadAuthorityConf()方法加载规则,从而完成黑白名单权限控制规则的加载和初始化。其中map是AuthorityRuleManager的Map<String, Set<AuthorityRule>>。

//Manager for authority rules.
public final class AuthorityRuleManager {//key是资源名称,value是资源对应的规则private static volatile Map<String, Set<AuthorityRule>> authorityRules = new ConcurrentHashMap<>();//饿汉式单例模式实例化黑白名单权限控制规则的监听器对象private static final RulePropertyListener LISTENER = new RulePropertyListener();//监听器对象的管理器private static SentinelProperty<List<AuthorityRule>> currentProperty = new DynamicSentinelProperty<>();static {//将黑白名单权限控制规则的监听器对象添加到DynamicSentinelProperty中currentProperty.addListener(LISTENER);}//Load the authority rules to memory.public static void loadRules(List<AuthorityRule> rules) {currentProperty.updateValue(rules);}...//静态内部类的方式实现黑白名单权限控制规则监听器private static class RulePropertyListener implements PropertyListener<List<AuthorityRule>> {//黑名单权限控制规则初始化@Overridepublic synchronized void configLoad(List<AuthorityRule> value) {authorityRules = loadAuthorityConf(value);RecordLog.info("[AuthorityRuleManager] Authority rules loaded: {}", authorityRules);}//黑名单权限控制规则变更@Overridepublic synchronized void configUpdate(List<AuthorityRule> conf) {authorityRules = loadAuthorityConf(conf);RecordLog.info("[AuthorityRuleManager] Authority rules received: {}", authorityRules);}//加载黑白名单权限控制规则private Map<String, Set<AuthorityRule>> loadAuthorityConf(List<AuthorityRule> list) {Map<String, Set<AuthorityRule>> newRuleMap = new ConcurrentHashMap<>();if (list == null || list.isEmpty()) {return newRuleMap;}//遍历每个黑白名单权限控制规则for (AuthorityRule rule : list) {if (!isValidRule(rule)) {RecordLog.warn("[AuthorityRuleManager] Ignoring invalid authority rule when loading new rules: {}", rule);continue;}if (StringUtil.isBlank(rule.getLimitApp())) {rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT);}//获取黑白名单权限控制规则对应的资源名称String identity = rule.getResource();Set<AuthorityRule> ruleSet = newRuleMap.get(identity);//putIfAbsent//将黑白名单权限控制规则放到newRuleMap中if (ruleSet == null) {ruleSet = new HashSet<>();ruleSet.add(rule);newRuleMap.put(identity, ruleSet);} else {//One resource should only have at most one authority rule, so just ignore redundant rules.RecordLog.warn("[AuthorityRuleManager] Ignoring redundant rule: {}", rule.toString());}}return newRuleMap;}}...
}public class DynamicSentinelProperty<T> implements SentinelProperty<T> {protected Set<PropertyListener<T>> listeners = new CopyOnWriteArraySet<>();private T value = null;public DynamicSentinelProperty() {}public DynamicSentinelProperty(T value) {super();this.value = value;}//添加监听器到集合@Overridepublic void addListener(PropertyListener<T> listener) {listeners.add(listener);//回调监听器的configLoad()方法初始化规则配置listener.configLoad(value);}//移除监听器@Overridepublic void removeListener(PropertyListener<T> listener) {listeners.remove(listener);}//更新值@Overridepublic boolean updateValue(T newValue) {//如果值没变化,直接返回if (isEqual(value, newValue)) {return false;}RecordLog.info("[DynamicSentinelProperty] Config will be updated to: {}", newValue);//如果值发生了变化,则遍历监听器,回调监听器的configUpdate()方法更新对应的值value = newValue;for (PropertyListener<T> listener : listeners) {listener.configUpdate(newValue);}return true;}private boolean isEqual(T oldValue, T newValue) {if (oldValue == null && newValue == null) {return true;}if (oldValue == null) {return false;}return oldValue.equals(newValue);}public void close() {listeners.clear();}
}

(2)AuthoritySlot验证黑白名单权限控制规则

在AuthoritySlot的checkBlackWhiteAuthority()方法中,首先会调用AuthorityRuleManager的getAuthorityRules()方法,从AuthorityRuleManager中获取全部黑白名单权限控制规则,然后再调用AuthorityRuleChecker的passCheck()方法根据规则验证权限。

在AuthorityRuleChecker的passCheck()方法中,首先会从当前上下文Context中获取调用源的名称,然后判断调用源不空且配置了黑白名单规则,才执行黑白名单验证逻辑。接着先通过indexOf()方法进行一次黑白名单的简单匹配,再通过split()方法分割黑白名单数组以实现精确匹配。如果调用源在名单中,再根据黑白名单策略来决定是否拒绝请求。

注意,实现黑白名单权限控制的前提条件是:每个客户端在发起请求时已将自己服务的唯一标志放到Context的origin属性里。

@Spi(order = Constants.ORDER_AUTHORITY_SLOT)
public class AuthoritySlot extends AbstractLinkedProcessorSlot<DefaultNode> {@Overridepublic void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) throws Throwable {//验证黑白名单权限控制规则checkBlackWhiteAuthority(resourceWrapper, context);fireEntry(context, resourceWrapper, node, count, prioritized, args);}@Overridepublic void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {fireExit(context, resourceWrapper, count, args);}void checkBlackWhiteAuthority(ResourceWrapper resource, Context context) throws AuthorityException {//先从AuthorityRuleManager中获取存放全部的黑白名单权限控制规则的MapMap<String, Set<AuthorityRule>> authorityRules = AuthorityRuleManager.getAuthorityRules();if (authorityRules == null) {return;}//获取当前资源对应的黑白名单权限控制规则集合Set<AuthorityRule> rules = authorityRules.get(resource.getName());if (rules == null) {return;}for (AuthorityRule rule : rules) {//验证规则if (!AuthorityRuleChecker.passCheck(rule, context)) {throw new AuthorityException(context.getOrigin(), rule);}}}
}//Rule checker for white/black list authority.
final class AuthorityRuleChecker {static boolean passCheck(AuthorityRule rule, Context context) {String requester = context.getOrigin();//Empty origin or empty limitApp will pass.//如果没设置来源,或者没限制app,那么就直接放行,不进行规则限制if (StringUtil.isEmpty(requester) || StringUtil.isEmpty(rule.getLimitApp())) {return true;}//Do exact match with origin name.//判断此次请求的来源是不是在limitApp里,注意这里用的是近似精确匹配,但不是绝对精确//比如limitApp写的是a,b,而资源名称是",b",那么就匹配不到,因为limitApp是按逗号隔开的,但资源却包含了逗号int pos = rule.getLimitApp().indexOf(requester);boolean contain = pos > -1;//如果近似精确匹配成功,则再进行精确匹配if (contain) {boolean exactlyMatch = false;String[] appArray = rule.getLimitApp().split(",");for (String app : appArray) {if (requester.equals(app)) {exactlyMatch = true;break;}}contain = exactlyMatch;}//获取策略int strategy = rule.getStrategy();//如果是黑名单,并且此次请求的来源在limitApp里,则需返回false,禁止请求if (strategy == RuleConstant.AUTHORITY_BLACK && contain) {return false;}//如果是白名单,并且此次请求的来源不在limitApp里,则也需返回false,禁止请求if (strategy == RuleConstant.AUTHORITY_WHITE && !contain) {return false;}return true;}private AuthorityRuleChecker() {}
}

(3)总结

一.黑白名单权限验证规则涉及的核心类

二.黑白名单权限验证的处理逻辑

三.Sentinel监听器模式的处理逻辑

一.黑白名单权限验证规则涉及的核心类

首先是黑白名单管理器AuthorityRuleManager,调用方直接调用该类的loadRules()方法来通知监听器规则的变更。

然后是黑白名单监听器RulePropertyListener,它实现了PropertyListener接口,负责监听和管理黑白名单的规则变化。

二.黑白名单权限验证的处理逻辑

首先通过AuthorityRuleManager获取全部黑白名单权限控制规则,然后循环遍历这些权限控制规则逐一验证是否匹配。

这里需要注意:来源是从Context里获取的,也就是Context的getOrigin()方法。因此在进行黑白名单权限规则控制时,需要先定义好一个origin。这个origin可以是userId,也可以是IP地址,还可以是项目名称等。

此外,规则里的limitApp字段是字符串,多个时需要使用逗号隔开,然后在验证环节先通过indexOf()方法近似匹配,匹配上之后再通过split()方法转成数组进行精确匹配。

三.Sentinel监听器模式的处理逻辑

Sentinel监听器模式会包含三大角色:

角色一:监听器PropertyListener<T>

角色二:监听器管理器SentinelProperty<T>

角色三:规则管理器RuleManager

首先,规则管理器RuleManager在初始化时:会调用监听器管理器SentinelProperty<T>的addListener()方法将监听器PropertyListener<T>注册到监听器管理器SentinelProperty<T>中。

然后,使用方使用具体的规则时:可以通过调用规则管理器RuleManager的loadRules()方法加载规则。加载规则时会调用监听器管理器SentinelProperty<T>的的updateValue()方法通知每一个监听器,即通过监听器PropertyListener<T>的configUpdate()方法把规则加载到规则管理器RuleManager的本地中。

5.SystemSlot根据系统保护规则进行流控

(1)系统保护规则SystemRule的配置Demo

(2)SystemRuleManager加载规则和获取系统信息

(3)SystemSlot根据系统保护规则进行流控

(1)系统保护规则SystemRule的配置Demo

系统规则类SystemRule包含了以下几个指标:highestSystemLoad、highestCpuUsage、QPS、avgRt、maxThread。

当需要限制系统的这些指标时,可以创建一个SystemRule对象并设置对应的阈值,然后通过调用SystemRuleManager的loadRules()方法,加载系统保护规则设置的阈值到SystemRuleManager。

//Sentinel System Rule makes the inbound traffic and capacity meet. 
//It takes average RT, QPS and thread count of requests into account. 
//And it also provides a measurement of system's load, but only available on Linux.
//We recommend to coordinate highestSystemLoad, qps, avgRt and maxThread to make sure your system run in safety level.
//To set the threshold appropriately, performance test may be needed.
public class SystemRule extends AbstractRule {//对应Dashboard上阈值类型为LOAD的值,代表系统最高负载值,默认为-1,只有大于等于0才生效private double highestSystemLoad = -1;//对应Dashboard上阈值类型为CPU使用率的值,代表系统最高CPU使用率,取值是[0,1]之间,默认为-1,只有大于等于0才生效private double highestCpuUsage = -1;//对应Dashboard上阈值类型为为入口QPS的值,代表限流的阈值,默认为-1,只有大于0才生效private double qps = -1;//对应Dashboard上阈值类型为为RT的值,代表系统的平均响应时间,默认为-1,只有大于0才生效private long avgRt = -1;//对应Dashboard上阈值类型为线程数的值,代表系统允许的最大线程数,默认为-1,只有大于0才生效private long maxThread = -1;...
}public class SystemGuardDemo {private static AtomicInteger pass = new AtomicInteger();private static AtomicInteger block = new AtomicInteger();private static AtomicInteger total = new AtomicInteger();private static volatile boolean stop = false;private static final int threadCount = 100;private static int seconds = 60 + 40;public static void main(String[] args) throws Exception {//启动线程定时输出信息tick();//初始化系统保护规则initSystemRule();//模拟有100个线程在访问系统for (int i = 0; i < threadCount; i++) {Thread entryThread = new Thread(new Runnable() {@Overridepublic void run() {while (true) {Entry entry = null;try {entry = SphU.entry("methodA", EntryType.IN);pass.incrementAndGet();try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {// ignore}} catch (BlockException e1) {block.incrementAndGet();try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {// ignore}} catch (Exception e2) {// biz exception} finally {total.incrementAndGet();if (entry != null) {entry.exit();}}}}});entryThread.setName("working-thread");entryThread.start();}}private static void initSystemRule() {List<SystemRule> rules = new ArrayList<SystemRule>();SystemRule rule = new SystemRule();//最大负载是3rule.setHighestSystemLoad(3.0);//最大CPU使用率是60%rule.setHighestCpuUsage(0.6);//请求的平均响应时间最大是10msrule.setAvgRt(10);//最大的QPS是20rule.setQps(20);//最大的工作线程数是10rule.setMaxThread(10);rules.add(rule);//加载系统保护规则设置的阈值到SystemRuleManager中SystemRuleManager.loadRules(Collections.singletonList(rule));}private static void tick() {Thread timer = new Thread(new TimerTask());timer.setName("sentinel-timer-task");timer.start();}static class TimerTask implements Runnable {@Overridepublic void run() {System.out.println("begin to statistic!!!");long oldTotal = 0;long oldPass = 0;long oldBlock = 0;while (!stop) {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {}long globalTotal = total.get();long oneSecondTotal = globalTotal - oldTotal;oldTotal = globalTotal;long globalPass = pass.get();long oneSecondPass = globalPass - oldPass;oldPass = globalPass;long globalBlock = block.get();long oneSecondBlock = globalBlock - oldBlock;oldBlock = globalBlock;System.out.println(seconds + ", " + TimeUtil.currentTimeMillis() + ", total:"+ oneSecondTotal + ", pass:"+ oneSecondPass + ", block:" + oneSecondBlock);if (seconds-- <= 0) {stop = true;}}System.exit(0);}}
}

(2)SystemRuleManager加载规则和获取系统信息

一.在加载系统保护规则设置的阈值到本地方面

SystemRuleManager会通过loadRules()方法加载系统保护规则的阈值,即调用DynamicSentinelProperty的updateValue()方法通知监听器更新规则。此时会触发执行监听器SystemPropertyListener的configUpdate()方法,从而执行SystemRuleManager的loadSystemConf()方法更新本地规则阈值。

二.在获取系统信息方面

SystemRuleManager初始化时会启动一个线程SystemPropertyListener,每隔1秒定时获取系统的Load、CPU使用率等信息,这样后续便可以通过SystemPropertyListener获取系统负载等信息。

public final class SystemRuleManager {//系统保护规则中的5个阈值:Load、CPU使用率、QPS、最大RT、最大线程数private static volatile double highestSystemLoad = Double.MAX_VALUE;private static volatile double highestCpuUsage = Double.MAX_VALUE;private static volatile double qps = Double.MAX_VALUE;private static volatile long maxRt = Long.MAX_VALUE;private static volatile long maxThread = Long.MAX_VALUE;...//标记系统流控功能是否开启private static AtomicBoolean checkSystemStatus = new AtomicBoolean(false);//定时获取系统状态信息(负载和CPU使用率)的线程private static SystemStatusListener statusListener = null;//饿汉式单例模式实例化系统保护规则的监听器对象private final static SystemPropertyListener listener = new SystemPropertyListener();//监听器对象的管理器private static SentinelProperty<List<SystemRule>> currentProperty = new DynamicSentinelProperty<List<SystemRule>>();private final static ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, new NamedThreadFactory("sentinel-system-status-record-task", true));static {checkSystemStatus.set(false);//启动定时任务获取系统的Load、CPU负载等信息statusListener = new SystemStatusListener();scheduler.scheduleAtFixedRate(statusListener, 0, 1, TimeUnit.SECONDS);//添加监听器currentProperty.addListener(listener);}//Load SystemRules, former rules will be replaced.public static void loadRules(List<SystemRule> rules) {currentProperty.updateValue(rules);}static class SystemPropertyListener extends SimplePropertyListener<List<SystemRule>> {@Overridepublic synchronized void configUpdate(List<SystemRule> rules) {restoreSetting();if (rules != null && rules.size() >= 1) {for (SystemRule rule : rules) {//加载系统保护规则的阈值到本地loadSystemConf(rule);}} else {checkSystemStatus.set(false);}...}}//加载系统保护规则的阈值到本地public static void loadSystemConf(SystemRule rule) {boolean checkStatus = false;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);}...
}public class DynamicSentinelProperty<T> implements SentinelProperty<T> {protected Set<PropertyListener<T>> listeners = new CopyOnWriteArraySet<>();private T value = null;...//添加监听器到集合@Overridepublic void addListener(PropertyListener<T> listener) {listeners.add(listener);//回调监听器的configLoad()方法初始化规则配置listener.configLoad(value);}//更新值@Overridepublic boolean updateValue(T newValue) {//如果值没变化,直接返回if (isEqual(value, newValue)) {return false;}RecordLog.info("[DynamicSentinelProperty] Config will be updated to: {}", newValue);//如果值发生了变化,则遍历监听器,回调监听器的configUpdate()方法更新对应的值value = newValue;for (PropertyListener<T> listener : listeners) {listener.configUpdate(newValue);}return true;}...
}public class SystemStatusListener implements Runnable {volatile double currentLoad = -1;volatile double currentCpuUsage = -1;volatile long processCpuTime = 0;volatile long processUpTime = 0;...@Overridepublic 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;currentCpuUsage = Math.max(processCpuUsage, systemCpuUsage);if (currentLoad > SystemRuleManager.getSystemLoadThreshold()) {writeSystemStatusLog();}} catch (Throwable e) {RecordLog.warn("[SystemStatusListener] Failed to get system metrics from JMX", e);}}public double getSystemAverageLoad() {return currentLoad;}public double getCpuUsage() {return currentCpuUsage;}...
}

(3)SystemSlot根据系统保护规则进行流控

SystemSlot会根据当前系统的实际情况,判断是否需要对请求进行限流,也就是通过调用SystemRuleManager的checkSystem()方法来进行检查。

在SystemRuleManager的checkSystem()方法中:

一.首先通过checkSystemStatus.get()判断系统保护功能是否开启

开启的入口就是:

->SystemRuleManager.loadRules()方法
->DynamicSentinelProperty.updateValue()方法
->SystemPropertyListener.configUpdate()方法
->SystemRuleManager.loadSystemConf()方法

二.接着通过Constants.ENTRY_NODE获取如QPS、threadNum等数据

Constants.ENTRY_NODE其实就是ClusterNode。在StatisticSlot的entry()方法中,会对Constants.ENTRY_NODE进行统计,所以可以通过Constants.ENTRY_NODE获取QPS、threadNum等数据。

三.然后采取BBR算法来检查系统负载是否超过系统保护规则的阈值

BBR是Google开发的一种拥塞控制算法,主要用来解决网络拥塞问题。SystemRuleManager的checkBbr()方法的目的是在系统负载较高的情况下,通过限制并行线程数来防止系统过载。

简单来说就是:检查当前线程数是否大于(每秒最大成功请求数 * 最小响应时间 / 1000)。如果大于这个值,说明系统可能出现拥塞,要返回false,否则返回true。

四.最后判断CPU使用率是否超系统保护规则的阈值

系统负载和CPU使用率是通过SystemStatusListener获取的。

@Spi(order = Constants.ORDER_SYSTEM_SLOT)
public class SystemSlot extends AbstractLinkedProcessorSlot<DefaultNode> {@Overridepublic void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) throws Throwable {//检查系统保护规则SystemRuleManager.checkSystem(resourceWrapper, count);//执行下一个ProcessorSlotfireEntry(context, resourceWrapper, node, count, prioritized, args);}@Overridepublic void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {fireExit(context, resourceWrapper, count, args);}
}public final class SystemRuleManager {//系统保护规则中的5个阈值:Load、CPU使用率、QPS、最大RT、最大线程数private static volatile double highestSystemLoad = Double.MAX_VALUE;private static volatile double highestCpuUsage = Double.MAX_VALUE;private static volatile double qps = Double.MAX_VALUE;private static volatile long maxRt = Long.MAX_VALUE;private static volatile long maxThread = Long.MAX_VALUE;...//标记系统流控功能是否开启private static AtomicBoolean checkSystemStatus = new AtomicBoolean(false);//定时获取系统状态信息(负载和CPU使用率)的线程private static SystemStatusListener statusListener = null;//监听器对象的管理器private static SentinelProperty<List<SystemRule>> currentProperty = new DynamicSentinelProperty<List<SystemRule>>();...//Apply SystemRule to the resource. Only inbound traffic will be checked.public static void checkSystem(ResourceWrapper resourceWrapper, int count) throws BlockException {//资源为空则直接返回if (resourceWrapper == null) {return;}//Ensure the checking switch is on.//判断系统流控功能是否开启,如果没开启则直接返回if (!checkSystemStatus.get()) {return;}//for inbound traffic only//判断资源的流量是否为入口流量,如果不是IN,则直接返回//也就是说Sentinel系统保护规则限流只对入口流量生效,如果类型为OUT则直接返回if (resourceWrapper.getEntryType() != EntryType.IN) {return;}//total qps//获取当前qps,如果当前qps大于系统保护规则SystemRule配置的阈值,则抛出SystemBlockException异常double currentQps = Constants.ENTRY_NODE.passQps();if (currentQps + count > qps) {throw new SystemBlockException(resourceWrapper.getName(), "qps");}//total thread//获取当前线程数,如果当前线程数大于系统保护规则SystemRule配置的阈值,则抛出SystemBlockException异常int currentThread = Constants.ENTRY_NODE.curThreadNum();if (currentThread > maxThread) {throw new SystemBlockException(resourceWrapper.getName(), "thread");}//如果当前请求的平均响应时间大于系统保护规则SystemRule配置的阈值,则抛出SystemBlockException异常double rt = Constants.ENTRY_NODE.avgRt();if (rt > maxRt) {throw new SystemBlockException(resourceWrapper.getName(), "rt");}//load. BBR algorithm.//如果当前系统负载大于系统保护规则SystemRule配置的负载,则采取BBR算法验证,验证不通过则抛出SystemBlockException异常if (highestSystemLoadIsSet && getCurrentSystemAvgLoad() > highestSystemLoad) {if (!checkBbr(currentThread)) {throw new SystemBlockException(resourceWrapper.getName(), "load");}}//cpu usage//判断当前CPU使用率是否大于系统保护规则SystemRule配置的阈值,如果大于则抛出SystemBlockException异常if (highestCpuUsageIsSet && getCurrentCpuUsage() > highestCpuUsage) {throw new SystemBlockException(resourceWrapper.getName(), "cpu");}}//BBR(Bottleneck Bandwidth and Round-trip propagation time)是Google开发的一种拥塞控制算法;//BBR主要用来解决网络拥塞问题;//checkBbr()方法的目的是在系统负载较高的情况下,通过限制并行线程数来防止系统过载;//简单来说就是://检查当前线程数是否大于(每秒最大成功请求数 * 最小响应时间 / 1000);//如果大于这个值,说明系统可能出现拥塞,需要返回false,否则返回true;//具体来说就是://首先检查当前线程数是否大于1,如果不是,则直接返回true,表示通过BBR检查;//如果当前线程数大于1,那么检查当前线程数是否大于://(Constants.ENTRY_NODE.maxSuccessQps() * Constants.ENTRY_NODE.minRt() / 1000);//这里的maxSuccessQps()是每秒最大成功请求数,minRt()是最小响应时间;//如果当前线程数大于这个计算值,那么返回false,表示未通过BBR检查;否则,返回true;//举个例子://假设currentThread为 5,maxSuccessQps()为 10,minRt()为200;//那么计算值为(10 * 200) / 1000 = 2;//因为currentThread大于计算值,所以返回false,表示未通过BBR检查;private static boolean checkBbr(int currentThread) {//检查当前线程数是否大于(每秒最大成功请求数 * 最小响应时间 / 1000)//如果大于这个值,说明系统可能出现拥塞,需要返回false,否则返回trueif (currentThread > 1 && currentThread > Constants.ENTRY_NODE.maxSuccessQps() * Constants.ENTRY_NODE.minRt() / 1000) {return false;}return true;}public static double getCurrentSystemAvgLoad() {return statusListener.getSystemAverageLoad();}public static double getCurrentCpuUsage() {return statusListener.getCpuUsage();}...
}@Spi(order = Constants.ORDER_STATISTIC_SLOT)
public class StatisticSlot extends AbstractLinkedProcessorSlot<DefaultNode> {@Overridepublic void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) throws Throwable {...if (resourceWrapper.getEntryType() == EntryType.IN) {//Add count for global inbound entry node for global statistics.Constants.ENTRY_NODE.increaseThreadNum();Constants.ENTRY_NODE.addPassRequest(count);}...}...
}public final class Constants {...//Global statistic node for inbound traffic. Usually used for {@code SystemRule} checking.public final static ClusterNode ENTRY_NODE = new ClusterNode(TOTAL_IN_RESOURCE_NAME, ResourceTypeConstants.COMMON);...
}

(4)总结

一.SystemSlot的使用和处理流程

二.Sentinel监听器模式的处理逻辑

一.SystemSlot的使用和处理流程

在使用SystemSlot前,需要先定义系统保护规则,设置相应的阈值,然后通过SystemRuleManager加载系统保护规则SystemRule。当请求进入SystemSlot时,会检查系统性能数据是否满足规则中的阈值。如果满足,则请求可以继续执行。如果不满足,则请求将被限流,也就是抛出SystemBlockException异常。

二.Sentinel监听器模式的处理逻辑

Sentinel监听器模式会包含三大角色:

角色一:监听器PropertyListener<T>

角色二:监听器管理器SentinelProperty<T>

角色三:规则管理器RuleManager

首先,规则管理器RuleManager在初始化时:会调用监听器管理器SentinelProperty<T>的addListener()方法将监听器PropertyListener<T>注册到监听器管理器SentinelProperty<T>中。

然后,使用方使用具体的规则时:可以通过调用规则管理器RuleManager的loadRules()方法加载规则。加载规则时会调用监听器管理器SentinelProperty<T>的的updateValue()方法通知每一个监听器,即通过监听器PropertyListener<T>的configUpdate()方法把规则加载到规则管理器RuleManager的本地中。

相关文章:

Sentinel源码—3.ProcessorSlot的执行过程二

大纲 1.NodeSelectorSlot构建资源调用树 2.LogSlot和StatisticSlot采集资源的数据 3.Sentinel监听器模式的规则对象与规则管理 4.AuthoritySlot控制黑白名单权限 5.SystemSlot根据系统保护规则进行流控 3.Sentinel监听器模式的规则对象与规则管理 (1)Sentinel的规则对象 …...

【C++11】列表初始化、右值引用、完美转发、lambda表达式

&#x1f4da; 博主的专栏 &#x1f427; Linux | &#x1f5a5;️ C | &#x1f4ca; 数据结构 | &#x1f4a1;C 算法 | &#x1f310; C 语言 上篇文章&#xff1a;unordered_map、unordered_set底层编写 下篇文章&#xff1a;C11&#xff1a;新的类功能、模板的可…...

Spring中IOC的重点理解(笔记)

Spring: 出现在2002左右.解决企业级开发的难度.减轻对项目模块 类与类之间的管理帮助开发人员创建对象,管理对象之间的关系. 补充&#xff1a;什么是OCP原则&#xff1f;&#xff08;面试&#xff09; &#xff08;1&#xff09;是软件七大开发当中最基本的一个原则&#xff…...

数据库系统概论|第三章:关系数据库标准语言SQL—课程笔记4

前言 前面详细介绍了关于SELECT语句的相关使用方法&#xff0c;继续上文的介绍&#xff0c;本文将继续介绍数据查询的其他相关操作&#xff0c;主要包括排序&#xff08;ORDER BY&#xff09;子句、分组&#xff08;GROUP BY&#xff09;子句。与此同时&#xff0c;介绍完单表…...

【1】CICD持续集成-docker本地搭建gitlab代码仓库社区版

‌一、gitlab社区版概述 GitLab社区版&#xff08;Community Edition, CE&#xff09;是一个开源的版本控制系统&#xff0c;适用于个人开发者、中小团队及大型企业。‌ GitLab社区版采用MIT许可证&#xff0c;用户可以免费使用和修改源代码。其主要功能包括代码托管、版本控制…...

Verdi工具使用心得知识笔记(一)

Verdi工具使用知识点提炼 本文来源于移知&#xff0c;具体文档请咨询厚台 一、基础概念 波形依赖 Verdi本身无法生成波形&#xff0c;需配合VCS等仿真工具生成.fsdb文件。核心功能模块 • nTrace&#xff1a;代码调试与追踪 • nSchema&#xff1a;原理图分析 • nState&…...

【25软考网工笔记】第二章 数据通信基础(4)数据编码

目录 一、曼彻斯特编码 1. 以太网 2. 题型(考试过的选择题) 1&#xff09;题目解析 二、差分曼彻斯特编码 三、两种曼彻斯特编码特点 ​编辑 1. 双相码 2. 将时钟和数据包含在信号数据流中 3. 编码效率低 4. 数据速率是码元速率的一半 5. 应用案例 ​编辑 1&…...

【正点原子STM32MP257连载】第四章 ATK-DLMP257B功能测试——USB OTG测试

1&#xff09;实验平台&#xff1a;正点原子ATK-DLMP257B开发板 2&#xff09;浏览产品&#xff1a;https://www.alientek.com/Product_Details/135.html 3&#xff09;全套实验源码手册视频下载&#xff1a;正点原子资料下载中心 第四章 ATK-DLMP257B功能测试——USB OTG测试…...

现代c++获取linux系统磁盘大小

现代c获取linux系统磁盘大小 前言一、命令获取系统磁盘大小二、使用c获取系统磁盘大小三、总结 前言 本文介绍一种使用c获取linux系统磁盘大小的方法 一、命令获取系统磁盘大小 在linux系统中可以使用lsblk命令显示当前系统磁盘大小&#xff0c;如下图所示 lsblk二、使用c获…...

tcp和udp的数据传输过程以及区别

tcp和udp的数据传输过程以及区别 目录 一、数据传输过程 1.1 UDP 数据报服务图 1.2 TCP 字节流服务图 1.3 tcp和udp的区别 1.3.1 连接特性 1.3.2 可靠性 1.3.3 数据传输形式 1.3.4 传输效率与开销 应用场景 一、数据传输过程 1.1 UDP 数据报服务图 这张图展示了 UDP 数据报服务…...

C++项目-衡码云判项目演示

衡码云判项目是什么呢&#xff1f;简单来说就是这是一个类似于牛客、力扣等在线OJ系统&#xff0c;用户在网页编写代码&#xff0c;点击提交后传递给后端云服务器&#xff0c;云服务器将用户的代码和测试用例进行合并编译&#xff0c;返回结果到网页。 项目最大的两个亮点&…...

C 语言中的 volatile 关键字

1、概念 volatile 是 C/C 语言中的一个类型修饰符&#xff0c;用于告知编译器&#xff1a;该变量的值可能会在程序控制流之外被意外修改&#xff08;如硬件寄存器、多线程共享变量或信号处理函数等&#xff09;&#xff0c;因此编译器不应对其进行激进的优化&#xff08;如缓存…...

mysql表类型查询

普通表 SELECT table_schema AS database_name,table_name FROM information_schema.tables WHERE table_schema NOT IN (information_schema, mysql, performance_schema, sys)AND table_type BASE TABLEAND table_name NOT IN (SELECT DISTINCT table_name FROM informatio…...

JavaScript事件循环

目录 JavaScript 执行机制与事件循环 一、同步与异步代码 1. 同步代码&#xff08;Synchronous Code&#xff09; 2. 异步代码&#xff08;Asynchronous Code&#xff09; 二、事件循环&#xff08;Event Loop&#xff09; 1. 核心组成 2. 事件循环基本流程 3. 运行机制…...

Linux》》bash 、sh 执行脚本

通常使用shell去运行脚本&#xff0c;两种方法 》bash xxx.sh 或 bash “xxx.sh” 、sh xxx.sh 或 sh “xxx.sh” 》bash -c “cmd string” 引号不能省略 我们知道 -c 的意思是 command&#xff0c;所以 bash -c 或 sh -c 后面应该跟一个 command。...

Git完全指南:从入门到精通版本控制 ------- Git 查看提交历史(8)

Git提交历史深度解析&#xff1a;从代码考古到精准回退 前言 在软件开发的生命周期中&#xff0c;提交历史是团队协作的时空胶囊。Git作为分布式版本控制系统&#xff0c;其强大的历史追溯能力可帮助开发者&#xff1a; 精准定位引入Bug的提交分析代码演进趋势恢复误删的重要…...

精益数据分析(2/126):解锁数据驱动的商业成功密码

精益数据分析&#xff08;2/126&#xff09;&#xff1a;解锁数据驱动的商业成功密码 大家好&#xff01;在如今这个数据爆炸的时代&#xff0c;数据就像一座蕴含无限宝藏的矿山&#xff0c;等待着我们去挖掘和利用。最近我在深入研读《精益数据分析》这本书&#xff0c;收获了…...

【ssti模板注入基础】

一、ssti模板注入简介 二、模板在开发中的应用 为什么要使用模板 为什么要用模板来提升效率&#xff1a; 不管我们输入什么&#xff0c;有一部分内容都是不会变的 除了内容之外其他都不会变&#xff0c;如果我们有成千上万的页面&#xff0c;如果不用模板&#xff0c;就算复…...

如何在 Kali 上解决使用 evil-winrm 时 Ruby Reline 的 quoting_detection_proc 警告

在使用 Kali Linux 运行 Ruby 工具&#xff08;例如 evil-winrm&#xff09;时&#xff0c;你可能会遇到以下警告&#xff1a; Warning: Remote path completions is disabled due to ruby limitation: undefined method quoting_detection_proc for module Reline这个警告会导…...

从零开始搭建PyTorch环境(支持CUDA)

从零开始搭建PyTorch环境&#xff08;支持CUDA&#xff09; 本文将详细介绍如何在Windows系统上为RTX 3050显卡配置支持CUDA的PyTorch环境。 环境准备 本教程基于以下环境&#xff1a; 显卡&#xff1a;NVIDIA RTX 3050操作系统&#xff1a;WindowsPython版本&#xff1a;3.1…...

【扩散模型连载 · 第 2 期】逆向扩散建模与神经网络的角色

上期回顾 我们在第 1 期中介绍了 正向扩散过程&#xff08;Forward Process&#xff09;&#xff0c;并用 CIFAR-10 图像演示了加噪过程&#xff1a; 正向过程是固定的&#xff0c;无需训练&#xff0c;但我们感兴趣的是&#xff1a;如何从纯噪声一步步“还原”出真实图像&…...

Mysql约束

约束其实就是创建表的时候给表的某些列加上限制条件。 主键约束和自增长约束比较重要 一、Mysql约束-主键约束 简介 指定的主键不能重复也不可以出现空值 1.添加单列主键 语法1&#xff1a;create table 表名&#xff08;字段名 数据类型 primary key&#xff09;; 点开…...

力扣热题100—滑动窗口(c++)

3.无重复字符的最长子串 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长 子串 的长度。 unordered_set<char> charSet; // 用于保存当前窗口的字符int left 0; // 窗口左指针int maxLength 0; // 最长子串的长度for (int right 0; right < s.siz…...

Linux网络编程第一课:深入浅出TCP/IP协议簇与网络寻址系统

知识点1【网络发展简史】 **网络节点&#xff1a;**路由器和交换机组成 交换机的作用&#xff1a;拓展网络接口 路由&#xff1a;网络通信路径 1、分组交换 分组的目的&#xff1a; 数据量大&#xff0c;不能一次型传输&#xff0c;只能分批次传输&#xff0c;这里的每一批…...

论文阅读笔记:Generative Modeling by Estimating Gradients of the Data Distribution

1、参考来源 论文《Generative Modeling by Estimating Gradients of the Data Distribution》 来源&#xff1a;NeurIPS 2019 论文链接&#xff1a;https://arxiv.org/abs/1907.05600 参考链接&#xff1a; 【AI知识分享】真正搞懂扩散模型Score Matching一定要理解的三大核心…...

C++零基础实践教程 函数 数组、字符串与 Vector

模块四&#xff1a;函数 (代码复用与模块化) 随着程序变得越来越复杂&#xff0c;把所有代码都堆在 main 函数里会变得难以管理和阅读。函数 (Function) 允许你将代码分解成逻辑上独立、可重用的块。这就像把一个大任务分解成几个小任务&#xff0c;每个小任务交给一个专门的“…...

照片处理工具:基于HTML与JavaScript实现详解

在当今数字时代,处理照片已成为日常需求。 本文将详细介绍一个基于HTML和JavaScript的照片处理工具的实现原理,这个工具可以调整图片尺寸、格式,并精确控制输出文件大小。 实现如下,不需要任何编辑器,txt文本、浏览器就行!! 工具功能概述 这个照片处理工具提供以下核心…...

MyBatis-OGNL表达式

介绍 OGNL&#xff08;Object-Graph Navigation Language&#xff09;是一种强大的表达式语言&#xff0c;用于获取和设置Java对象图中的属性。在MyBatis中&#xff0c;OGNL常用于动态SQL构建&#xff0c;如条件判断、循环等。以下是关于OGNL表达式的整合信息&#xff0c;包括…...

Web Worker在uniapp鸿蒙APP中的深度应用

文章目录 一、Web Worker核心概念解析1.1 什么是Web Worker&#xff1f;1.2 为什么在鸿蒙APP中使用Web Worker&#xff1f;1.3 性能对比实测 二、uniapp中的Web Worker完整实现2.1 基础配置步骤2.1.1 项目结构准备2.1.2 鸿蒙平台特殊配置 2.2 Worker脚本编写规范2.3 主线程通信…...

无人机故障冗余设计技术要点与难点!

一、技术要点 1. 冗余架构设计 硬件冗余&#xff1a;关键部件&#xff08;飞控、电机、电池、通信模块&#xff09;采用双余度或三余度设计&#xff0c;例如&#xff1a; 双飞控系统&#xff1a;主飞控失效时&#xff0c;备用飞控无缝接管。 电机动力冗余&#xff1a;六轴无…...

MySQL数据库表查询

测试表company.employee5 mysql> create database company; #创建一个库&#xff1b; 创建一个测试表: mysql> CREATE TABLE company.employee5(id int primary key auto_increment not null,name varchar(30) not null,sex enum(male,female) default male not null,hi…...

ADB的安装及抓取日志(2)

三、ADB抓取日志 在使用ADB抓取日志前&#xff0c;首先要保证电脑已经安装并配置ADB&#xff0c;在上一节已经验证完成。连接设备&#xff1a;可通过USB或者WI-FI&#xff0c;将安卓设备与电脑连接&#xff0c;并启用USB调试模式&#xff0c;此处我选择的是通过电脑与安卓设备…...

【C++】 —— 笔试刷题day_17

一、小乐乐改数字 题目解析 这道题&#xff0c;它们给定一个数&#xff0c;我们要对它进行修改&#xff1b;如果某一位是奇数&#xff0c;就把它变成1,&#xff1b;如果是偶数&#xff0c;就把它变成0&#xff1b; 让我们输出最后得到的数。 算法思路 这道题&#xff0c;总体…...

traceId传递

1、应用内传递通过ThreadLocal,InheritableThreadLocal传递 2、跨进程的应用间传递&#xff0c;这种会涉及到远程rpc通信&#xff0c;mq通信&#xff0c;数据库通信等。 feign:拦截器中改变请求头 feign.RequestInterceptor, 这个机制能够实现修改请求对象的目的&#xff0c…...

自然科技部分详解

光的反射 凸面镜与凹面镜 凸透镜和凹透镜 空气开关原理 短路是指电路中突然的电流过大&#xff0c;这会让线圈的磁性增大&#xff0c;来克服内设的弹簧导致断开 过载会让电流增大&#xff0c;两金属片受热膨胀触发断开 核电荷数是指原子核所带的电荷数。 在原子中&#xf…...

蓝桥杯 9. 九宫幻方

九宫幻方 原题目链接 题目描述 小明最近在教邻居家的小朋友小学奥数&#xff0c;而最近正好讲述到了三阶幻方这个部分。 三阶幻方是指将 1 ~ 9 不重复地填入一个 33 的矩阵中&#xff0c;使得每一行、每一列和每一条对角线的和都是相同的。 三阶幻方又被称作九宫格&#x…...

算法——希尔排序

目录 一、希尔排序定义 二、希尔排序原理 三、希尔排序特点 四、两种解法 五、代码实现 一、希尔排序定义 希尔排序是一种基于插入排序的排序算法&#xff0c;也被称为缩小增量排序。它通过将待排序的数组分割成若干个子序列&#xff0c;对子序列进行排序&#xff0c;然后…...

亚马逊热销变维权?5步搭建跨境产品的安全防火墙

“产品热卖&#xff0c;引来维权”——这已经悄然成为越来越多跨境卖家的“热销烦恼”。曾经拼品拼量&#xff0c;如今却要步步谨慎。商标侵权、专利投诉、图片盗用……这些问题一旦发生&#xff0c;轻则下架、账号被限&#xff0c;重则冻结资金甚至封店。 别让“热销”变“受…...

20250416-Python 中常见的填充 `pad` 方法

Python 中常见的填充 pad 方法 在 Python 中&#xff0c;pad 方法通常与字符串或数组操作相关&#xff0c;用于在数据的前后填充特定的值&#xff0c;以达到指定的长度或格式。以下是几种常见的与 pad 相关的用法&#xff1a; 1. 字符串的 pad 操作 虽然 Python 的字符串没有…...

JavaEE-0416

今天修复了一个查询数据时数据显示哈希码&#xff1a; 搜索检阅后得到显示该格式的原因&#xff1a; 重写 POJO 类的 toString 方法 在 Java 编程中&#xff0c;默认情况下&#xff0c;对象的 toString() 方法会返回类似于 com.cz.pojo.Score2a266d09 的字符串。这是由于默认…...

团体程序设计天梯赛L2-008 最长对称子串

对给定的字符串&#xff0c;本题要求你输出最长对称子串的长度。例如&#xff0c;给定Is PAT&TAP symmetric?&#xff0c;最长对称子串为s PAT&TAP s&#xff0c;于是你应该输出11。 输入格式&#xff1a; 输入在一行中给出长度不超过1000的非空字符串。 输出格式&…...

命令模式 (Command Pattern)

命令模式(Command Pattern)是一种行为型设计模式,它将请求封装成一个对象,从而使你可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。该模式把发出命令的责任和执行命令的责任分割开,委派给不同的对象。 一、基础 1.1 意图 将请求封…...

Elasticsearch 8.18 中提供了原生连接 (Native Joins)

作者&#xff1a;来自 Elastic Costin Leau 探索 LOOKUP JOIN&#xff0c;这是一条在 Elasticsearch 8.18 的技术预览中提供的新 ES|QL 命令。 很高兴宣布 LOOKUP JOIN —— 这是一条在 Elasticsearch 8.18 的技术预览中提供的新 ES|QL 命令&#xff0c;旨在执行左 joins 以进行…...

在线终端(一个基于 Spring Boot 的在线终端模拟器,实现了类 Linux 命令行操作功能)

Online Terminal 一个基于 Spring Boot 的在线终端模拟器,实现了类 Linux 命令行操作功能。 功能特点 模拟 Linux 文件系统操作支持基础的文件和目录管理命令提供文件内容查看和编辑功能支持文件压缩和解压缩操作 快速开始 环境要求 JDK 8Maven 3.6 运行项目 克隆项目到…...

vue+electron ipc+sql相关开发(三)

在 Electron 中使用 IPC(Inter-Process Communication)与 SQLite 数据库进行通信是一个常见的模式,特别是在需要将数据库操作从渲染进程(Vue.js)移到主进程(Electron)的情况下。这样可以更好地管理数据库连接和提高安全性。下一篇介绍结合axios写成通用接口形式,虽然没…...

C++静态变量多线程中的未定义行为

静态变量&#xff0c;是 C 程序员最早接触的语言特性之一。它有状态、生命周期长、初始化一次&#xff0c;用起来真是香。 但只要程序一旦进入多线程的世界&#xff0c;很多你原以为“稳定可靠”的写法&#xff0c;可能就突然开始“不对劲”了。静态变量首当其冲。 今天我们就…...

黑马商城项目(二) Docker

一、Docker快速入门 安装Docker - 飞书云文档 二、命令解读 常见命令&#xff1a; 数据卷&#xff1a; 案例1 数据卷挂载&#xff1a; 案例2 本地目录挂载&#xff1a; 挂载到指定目录能够保存数据&#xff08;即使Mysql容器被删除&#xff09; docker run -d \--name mysql …...

玩转Docker | 使用Docker部署Memos笔记工具

玩转Docker | 使用Docker部署Memos笔记工具 前言一、Memos介绍Memos简介主要特点二、系统要求环境要求环境检查Docker版本检查检查操作系统版本三、部署Memos服务下载镜像创建容器创建容器检查容器状态检查服务端口安全设置四、访问Memos服务访问Memos首页注册账号五、基本使用…...

c#从ftp服务器下载文件读取csv

从 FTP 服务器下载文件的功能&#xff0c;并且支持根据文件名称的前缀或直接文件名进行查找和下载。以下是对代码的一些建议和修改&#xff0c;以确保它能够满足您的需求&#xff0c;尤其是如果您希望仅下载特定类型的文件&#xff08;例如 .csv 文件&#xff09; using Syste…...

电脑知识 | TCP通俗易懂详解 <三>tcp首部中ACK、SYN、FIN等信息填写案例_握手时

目录 一、&#x1f44b;&#x1f3fb;前言 二、&#x1f91d;&#x1f3fb;握手时的快递单 1.&#x1f46b;第一次握手&#xff08;发送方&#xff09; 2.&#x1f46b;第二次握手&#xff08;收件方&#xff09; 3.&#x1f46b;第三次握手&#xff08;发件方&#xff09;…...