Sentinel源碼—7.參數限流和注解的實現一

大綱

1.參數限流的原理和源碼

2.@SentinelResource注解的使用和實現

1.參數限流的原理和源碼

(1)參數限流規則ParamFlowRule的配置Demo

(2)ParamFlowSlot根據參數限流規則驗證請求

(1)參數限流規則ParamFlowRule的配置Demo

一.參數限流的應用場景

二.參數限流規則的屬性

三.參數限流規則的配置Demo

一.參數限流的應用場景

傳統的流量控制,一般是通過資源維度來限制某接口或方法的調用頻率。但有時需要更細粒度地控制不同參數條件下的訪問速率,即參數限流。參數限流允許根據不同的參數條件設置不同的流量控制規則,這種方式非常適合處理特定條件下的請求,因為能更加精細地管理流量。

假設有一個在線電影訂票系統,某個接口允許用戶查詢電影的放映時間。但只希望每個用戶每10秒只能查詢接口1次,以避免過多的查詢請求。這時如果直接將接口的QPS限制為5是不能滿足要求的,因為需求是每個用戶每5分鐘只能查詢1次,而不是每秒一共只能查詢5次,因此參數限流就能派上用場了。

可以設置一個規則,根據用戶ID來限制每個用戶的查詢頻率,將限流的維度從資源維度細化到參數維度,從而實現每個用戶每10秒只能查詢接口1次。比如希望影院工作人員可以每秒查詢10次,老板可以每秒查詢100次,而購票者則只能每10秒查詢一次,其中工作人員的userId值為100和200,老板的userId值為9999,那么可以如下配置:需要注意限流閾值是以秒為單位的,所以需要乘以統計窗口時長10。

二.參數限流規則的屬性

public class ParamFlowRule extends AbstractRule {...//The threshold type of flow control (0: thread count, 1: QPS).//流量控制的閾值類型(0表示線程數,1表示QPS)private int grade = RuleConstant.FLOW_GRADE_QPS;//Parameter index.//參數下標private Integer paramIdx;//The threshold count.//閾值private double count;//Original exclusion items of parameters.//針對特定參數的流量控制規則列表private List<ParamFlowItem> paramFlowItemList = new ArrayList<ParamFlowItem>();//Indicating whether the rule is for cluster mode.//是否集群private boolean clusterMode = false;...
}//針對特定參數的流量控制規則
public class ParamFlowItem {private String object;private Integer count;private String classType;...
}

三.參數限流規則的配置Demo

//This demo demonstrates flow control by frequent ("hot spot") parameters.
public class ParamFlowQpsDemo {private static final int PARAM_A = 1;private static final int PARAM_B = 2;private static final int PARAM_C = 3;private static final int PARAM_D = 4;//Here we prepare different parameters to validate flow control by parameters.private static final Integer[] PARAMS = new Integer[] {PARAM_A, PARAM_B, PARAM_C, PARAM_D};private static final String RESOURCE_KEY = "resA";public static void main(String[] args) throws Exception {initParamFlowRules();final int threadCount = 20;ParamFlowQpsRunner<Integer> runner = new ParamFlowQpsRunner<>(PARAMS, RESOURCE_KEY, threadCount, 120);runner.tick();Thread.sleep(1000);runner.simulateTraffic();}private static void initParamFlowRules() {//QPS mode, threshold is 5 for every frequent "hot spot" parameter in index 0 (the first arg).ParamFlowRule rule = new ParamFlowRule(RESOURCE_KEY).setParamIdx(0).setGrade(RuleConstant.FLOW_GRADE_QPS).setCount(5);//We can set threshold count for specific parameter value individually.//Here we add an exception item. That means: //QPS threshold of entries with parameter `PARAM_B` (type: int) in index 0 will be 10, rather than the global threshold (5).ParamFlowItem item = new ParamFlowItem().setObject(String.valueOf(PARAM_B)).setClassType(int.class.getName()).setCount(10);rule.setParamFlowItemList(Collections.singletonList(item));//ParamFlowRuleManager類加載的一個時機是:它的靜態方法被調用了//所以下面會先初始化ParamFlowRuleManager,再執行loadRules()方法ParamFlowRuleManager.loadRules(Collections.singletonList(rule));}
}public final class ParamFlowRuleManager {private static final Map<String, List<ParamFlowRule>> PARAM_FLOW_RULES = new ConcurrentHashMap<>();private final static RulePropertyListener PROPERTY_LISTENER = new RulePropertyListener();private static SentinelProperty<List<ParamFlowRule>> currentProperty = new DynamicSentinelProperty<>();static {currentProperty.addListener(PROPERTY_LISTENER);}//Load parameter flow rules. Former rules will be replaced.public static void loadRules(List<ParamFlowRule> rules) {try {//設置規則的值為rulescurrentProperty.updateValue(rules);} catch (Throwable e) {RecordLog.info("[ParamFlowRuleManager] Failed to load rules", e);}}static class RulePropertyListener implements PropertyListener<List<ParamFlowRule>> {@Overridepublic void configUpdate(List<ParamFlowRule> list) {Map<String, List<ParamFlowRule>> rules = aggregateAndPrepareParamRules(list);if (rules != null) {PARAM_FLOW_RULES.clear();PARAM_FLOW_RULES.putAll(rules);}RecordLog.info("[ParamFlowRuleManager] Parameter flow rules received: {}", PARAM_FLOW_RULES);}@Overridepublic void configLoad(List<ParamFlowRule> list) {Map<String, List<ParamFlowRule>> rules = aggregateAndPrepareParamRules(list);if (rules != null) {PARAM_FLOW_RULES.clear();PARAM_FLOW_RULES.putAll(rules);}RecordLog.info("[ParamFlowRuleManager] Parameter flow rules received: {}", PARAM_FLOW_RULES);}...}...
}public class DynamicSentinelProperty<T> implements SentinelProperty<T> {protected Set<PropertyListener<T>> listeners = new CopyOnWriteArraySet<>();private T value = null;public DynamicSentinelProperty() {}//添加監聽器到集合@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;}...
}//A traffic runner to simulate flow for different parameters.
class ParamFlowQpsRunner<T> {private final T[] params;private final String resourceName;private int seconds;private final int threadCount;private final Map<T, AtomicLong> passCountMap = new ConcurrentHashMap<>();private final Map<T, AtomicLong> blockCountMap = new ConcurrentHashMap<>();private volatile boolean stop = false;public ParamFlowQpsRunner(T[] params, String resourceName, int threadCount, int seconds) {this.params = params;this.resourceName = resourceName;this.seconds = seconds;this.threadCount = threadCount;for (T param : params) {passCountMap.putIfAbsent(param, new AtomicLong());blockCountMap.putIfAbsent(param, new AtomicLong());}}public void tick() {Thread timer = new Thread(new TimerTask());timer.setName("sentinel-timer-task");timer.start();}public void simulateTraffic() {for (int i = 0; i < threadCount; i++) {Thread t = new Thread(new RunTask());t.setName("sentinel-simulate-traffic-task-" + i);t.start();}}final class TimerTask implements Runnable {@Overridepublic void run() {long start = System.currentTimeMillis();System.out.println("Begin to run! Go go go!");System.out.println("See corresponding metrics.log for accurate statistic data");Map<T, Long> map = new HashMap<>(params.length);for (T param : params) {map.putIfAbsent(param, 0L);}while (!stop) {sleep(1000);//There may be a mismatch for time window of internal sliding window.//See corresponding `metrics.log` for accurate statistic log.for (T param : params) {System.out.println(String.format("[%d][%d] Parameter flow metrics for resource %s: pass count for param <%s> is %d, block count: %d",seconds, TimeUtil.currentTimeMillis(), resourceName, param,passCountMap.get(param).getAndSet(0), blockCountMap.get(param).getAndSet(0)));}System.out.println("=============================");if (seconds-- <= 0) {stop = true;}}long cost = System.currentTimeMillis() - start;System.out.println("Time cost: " + cost + " ms");System.exit(0);}}final class RunTask implements Runnable {@Overridepublic void run() {while (!stop) {Entry entry = null;T param = generateParam();try {entry = SphU.entry(resourceName, EntryType.IN, 1, param);//Add pass for parameter.passFor(param);} catch (BlockException e) {//block.incrementAndGet();blockFor(param);} catch (Exception ex) {//biz exceptionex.printStackTrace();} finally {//total.incrementAndGet();if (entry != null) {entry.exit(1, param);}}sleep(ThreadLocalRandom.current().nextInt(0, 10));}}}//Pick one of provided parameters randomly.private T generateParam() {int i = ThreadLocalRandom.current().nextInt(0, params.length);return params[i];}private void passFor(T param) {passCountMap.get(param).incrementAndGet();}private void blockFor(T param) {blockCountMap.get(param).incrementAndGet();}private void sleep(int timeMs) {try {TimeUnit.MILLISECONDS.sleep(timeMs);} catch (InterruptedException e) {}}
}

(2)ParamFlowSlot根據參數限流規則驗證請求

一.ParamFlowSlot的entry()方法的邏輯

二.不同限流類型 + 閾值類型 + 流控效果的處理

三.流控效果為排隊等待和直接拒絕的實現

四.參數限流是如何進行數據統計

五.參數限流驗證請求的流程圖總結

一.ParamFlowSlot的entry()方法的邏輯

ParamFlowSlot的entry()方法主要干了三件事:參數驗證、獲取當前資源的全部參數限流規則、循環每一個參數限流規則并判斷此次請求是否被允許通過(如果不允許則直接拋出異常)。其中對每一條獲取到的參數限流規則,都會通過ParamFlowChecker的passCheck()方法進行判斷。

@Spi(order = -3000)
public class ParamFlowSlot extends AbstractLinkedProcessorSlot<DefaultNode> {@Overridepublic void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,boolean prioritized, Object... args) throws Throwable {//1.如果沒配置參數限流規則,直接觸發下一個Slotif (!ParamFlowRuleManager.hasRules(resourceWrapper.getName())) {fireEntry(context, resourceWrapper, node, count, prioritized, args);return;}//2.如果配置了參數限流規則,則調用ParamFlowSlot的checkFlow()方法,該方法執行完成后再觸發下一個SlotcheckFlow(resourceWrapper, count, args);fireEntry(context, resourceWrapper, node, count, prioritized, args);}@Overridepublic void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {fireExit(context, resourceWrapper, count, args);}void applyRealParamIdx(/*@NonNull*/ ParamFlowRule rule, int length) {int paramIdx = rule.getParamIdx();if (paramIdx < 0) {if (-paramIdx <= length) {rule.setParamIdx(length + paramIdx);} else {//Illegal index, give it a illegal positive value, latter rule checking will pass.rule.setParamIdx(-paramIdx);}}}void checkFlow(ResourceWrapper resourceWrapper, int count, Object... args) throws BlockException {//1.如果沒傳遞參數,則直接放行,代表不做參數限流邏輯if (args == null) {return;}//2.如果沒給resourceWrapper這個資源配置參數限流規則,則直接放行if (!ParamFlowRuleManager.hasRules(resourceWrapper.getName())) {return;}//3.獲取此資源的全部參數限流規則,規則可能會有很多個,所以是個ListList<ParamFlowRule> rules = ParamFlowRuleManager.getRulesOfResource(resourceWrapper.getName());//4.遍歷獲取到的參數限流規則for (ParamFlowRule rule : rules) {//進行參數驗證applyRealParamIdx(rule, args.length);//Initialize the parameter metrics.ParameterMetricStorage.initParamMetricsFor(resourceWrapper, rule);//進行驗證的核心方法:檢查當前規則是否允許通過此請求,如果不允許,則拋出ParamFlowException異常if (!ParamFlowChecker.passCheck(resourceWrapper, rule, count, args)) {String triggeredParam = "";if (args.length > rule.getParamIdx()) {Object value = args[rule.getParamIdx()];//Assign actual value with the result of paramFlowKey methodif (value instanceof ParamFlowArgument) {value = ((ParamFlowArgument) value).paramFlowKey();}triggeredParam = String.valueOf(value);}throw new ParamFlowException(resourceWrapper.getName(), triggeredParam, rule);}}}
}

二.不同限流類型 + 閾值類型 + 流控效果的處理

在ParamFlowChecker的passCheck()方法中,參數值驗證通過之后,會判斷限流類型。如果是集群限流,則執行ParamFlowChecker的passClusterCheck()方法。如果是單機限流,則執行ParamFlowChecker的passLocalCheck()方法。

在ParamFlowChecker的passLocalCheck()方法中,則會根據不同的參數類型調用ParamFlowChecker的passSingleValueCheck()方法。根據該方法可以知道,參數限流支持兩種閾值類型:一種是QPS,另一種是線程數。而QPS類型還支持兩種流控效果,分別是排隊等待和直接拒絕,但不支持Warm Up。

//Rule checker for parameter flow control.
public final class ParamFlowChecker {public static boolean passCheck(ResourceWrapper resourceWrapper, /*@Valid*/ ParamFlowRule rule, /*@Valid*/ int count, Object... args) {if (args == null) {return true;}//1.判斷參數索引是否合法,這個就是配置參數限流時設置的下標,從0開始,也就是對應args里的下標//比如0就代表args數組里的第一個參數,如果參數不合法直接放行,相當于參數限流沒生效  int paramIdx = rule.getParamIdx();if (args.length <= paramIdx) {return true;}//2.判斷參數值是不是空,如果是空直接放行//Get parameter value.Object value = args[paramIdx];//Assign value with the result of paramFlowKey methodif (value instanceof ParamFlowArgument) {value = ((ParamFlowArgument) value).paramFlowKey();}//If value is null, then passif (value == null) {return true;}//3.集群限流if (rule.isClusterMode() && rule.getGrade() == RuleConstant.FLOW_GRADE_QPS) {return passClusterCheck(resourceWrapper, rule, count, value);}//4.單機限流return passLocalCheck(resourceWrapper, rule, count, value);}private static boolean passLocalCheck(ResourceWrapper resourceWrapper, ParamFlowRule rule, int count, Object value) {try {if (Collection.class.isAssignableFrom(value.getClass())) {//基本類型for (Object param : ((Collection)value)) {if (!passSingleValueCheck(resourceWrapper, rule, count, param)) {return false;}}} else if (value.getClass().isArray()) {//數組類型int length = Array.getLength(value);for (int i = 0; i < length; i++) {Object param = Array.get(value, i);if (!passSingleValueCheck(resourceWrapper, rule, count, param)) {return false;}}} else {//其他類型,也就是引用類型return passSingleValueCheck(resourceWrapper, rule, count, value);}} catch (Throwable e) {RecordLog.warn("[ParamFlowChecker] Unexpected error", e);}return true;}static boolean passSingleValueCheck(ResourceWrapper resourceWrapper, ParamFlowRule rule, int acquireCount, Object value) {if (rule.getGrade() == RuleConstant.FLOW_GRADE_QPS) {//類型是QPSif (rule.getControlBehavior() == RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER) {//流控效果為排隊等待return passThrottleLocalCheck(resourceWrapper, rule, acquireCount, value);} else {//流控效果為直接拒絕return passDefaultLocalCheck(resourceWrapper, rule, acquireCount, value);}} else if (rule.getGrade() == RuleConstant.FLOW_GRADE_THREAD) {//類型是ThreadSet<Object> exclusionItems = rule.getParsedHotItems().keySet();long threadCount = getParameterMetric(resourceWrapper).getThreadCount(rule.getParamIdx(), value);if (exclusionItems.contains(value)) {int itemThreshold = rule.getParsedHotItems().get(value);return ++threadCount <= itemThreshold;}long threshold = (long)rule.getCount();return ++threadCount <= threshold;}return true;}...
}

三.流控效果為排隊等待和直接拒絕的實現

當設置了QPS類型的流控效果為排隊等待時,會調用ParamFlowChecker的passThrottleLocalCheck()方法。該方法實現排隊等待效果的原理和流控規則FlowSlot通過RateLimiterController實現排隊等待效果的原理是一樣的。

//Rule checker for parameter flow control.
public final class ParamFlowChecker {...static boolean passThrottleLocalCheck(ResourceWrapper resourceWrapper, ParamFlowRule rule, int acquireCount, Object value) {ParameterMetric metric = getParameterMetric(resourceWrapper);CacheMap<Object, AtomicLong> timeRecorderMap = metric == null ? null : metric.getRuleTimeCounter(rule);if (timeRecorderMap == null) {return true;}//Calculate max token count (threshold)Set<Object> exclusionItems = rule.getParsedHotItems().keySet();long tokenCount = (long)rule.getCount();if (exclusionItems.contains(value)) {tokenCount = rule.getParsedHotItems().get(value);}if (tokenCount == 0) {return false;}long costTime = Math.round(1.0 * 1000 * acquireCount * rule.getDurationInSec() / tokenCount);while (true) {long currentTime = TimeUtil.currentTimeMillis();AtomicLong timeRecorder = timeRecorderMap.putIfAbsent(value, new AtomicLong(currentTime));if (timeRecorder == null) {return true;}//AtomicLong timeRecorder = timeRecorderMap.get(value);long lastPassTime = timeRecorder.get();long expectedTime = lastPassTime + costTime;if (expectedTime <= currentTime || expectedTime - currentTime < rule.getMaxQueueingTimeMs()) {AtomicLong lastPastTimeRef = timeRecorderMap.get(value);if (lastPastTimeRef.compareAndSet(lastPassTime, currentTime)) {long waitTime = expectedTime - currentTime;if (waitTime > 0) {lastPastTimeRef.set(expectedTime);try {TimeUnit.MILLISECONDS.sleep(waitTime);} catch (InterruptedException e) {RecordLog.warn("passThrottleLocalCheck: wait interrupted", e);}}return true;} else {Thread.yield();}} else {return false;}}}private static ParameterMetric getParameterMetric(ResourceWrapper resourceWrapper) {//Should not be null.return ParameterMetricStorage.getParamMetric(resourceWrapper);}
}

當設置了QPS類型的流控效果為直接拒絕時,會調用ParamFlowChecker的passDefaultLocalCheck()方法。該方法采取令牌桶的方式來實現:控制每個時間窗口只生產一次token令牌,且將令牌放入桶中,每個請求都從桶中取令牌,當可以獲取到令牌時,則正常放行,反之直接拒絕。

//Rule checker for parameter flow control.
public final class ParamFlowChecker {...static boolean passDefaultLocalCheck(ResourceWrapper resourceWrapper, ParamFlowRule rule, int acquireCount, Object value) {ParameterMetric metric = getParameterMetric(resourceWrapper);CacheMap<Object, AtomicLong> tokenCounters = metric == null ? null : metric.getRuleTokenCounter(rule);CacheMap<Object, AtomicLong> timeCounters = metric == null ? null : metric.getRuleTimeCounter(rule);if (tokenCounters == null || timeCounters == null) {return true;}//Calculate max token count (threshold)Set<Object> exclusionItems = rule.getParsedHotItems().keySet();long tokenCount = (long)rule.getCount();if (exclusionItems.contains(value)) {tokenCount = rule.getParsedHotItems().get(value);}if (tokenCount == 0) {return false;}long maxCount = tokenCount + rule.getBurstCount();if (acquireCount > maxCount) {return false;}while (true) {long currentTime = TimeUtil.currentTimeMillis();AtomicLong lastAddTokenTime = timeCounters.putIfAbsent(value, new AtomicLong(currentTime));if (lastAddTokenTime == null) {//Token never added, just replenish the tokens and consume {@code acquireCount} immediately.tokenCounters.putIfAbsent(value, new AtomicLong(maxCount - acquireCount));return true;}//Calculate the time duration since last token was added.long passTime = currentTime - lastAddTokenTime.get();//A simplified token bucket algorithm that will replenish the tokens only when statistic window has passed.if (passTime > rule.getDurationInSec() * 1000) {AtomicLong oldQps = tokenCounters.putIfAbsent(value, new AtomicLong(maxCount - acquireCount));if (oldQps == null) {//Might not be accurate here.lastAddTokenTime.set(currentTime);return true;} else {long restQps = oldQps.get();long toAddCount = (passTime * tokenCount) / (rule.getDurationInSec() * 1000);long newQps = toAddCount + restQps > maxCount ? (maxCount - acquireCount): (restQps + toAddCount - acquireCount);if (newQps < 0) {return false;}if (oldQps.compareAndSet(restQps, newQps)) {lastAddTokenTime.set(currentTime);return true;}Thread.yield();}} else {AtomicLong oldQps = tokenCounters.get(value);if (oldQps != null) {long oldQpsValue = oldQps.get();if (oldQpsValue - acquireCount >= 0) {if (oldQps.compareAndSet(oldQpsValue, oldQpsValue - acquireCount)) {return true;}} else {return false;}}Thread.yield();}}}
}

四.參數限流是如何進行數據統計

由于參數限流的數據統計需要細化到參數值的維度,所以使用參數限流時需要注意OOM問題。比如根據用戶ID進行限流,且用戶數量有幾千萬,那么CacheMap將會包含幾千萬個不會被移除的鍵值對,而且會隨著進程運行時間的增長而不斷增加,最后可能會導致OOM。

public final class ParameterMetricStorage {private static final Map<String, ParameterMetric> metricsMap = new ConcurrentHashMap<>();//Lock for a specific resource.private static final Object LOCK = new Object();//Init the parameter metric and index map for given resource.//該方法在ParamFlowSlot的checkFlow()方法中被調用public static void initParamMetricsFor(ResourceWrapper resourceWrapper, /*@Valid*/ ParamFlowRule rule) {if (resourceWrapper == null || resourceWrapper.getName() == null) {return;}String resourceName = resourceWrapper.getName();ParameterMetric metric;//Assume that the resource is valid.if ((metric = metricsMap.get(resourceName)) == null) {synchronized (LOCK) {if ((metric = metricsMap.get(resourceName)) == null) {metric = new ParameterMetric();metricsMap.put(resourceWrapper.getName(), metric);RecordLog.info("[ParameterMetricStorage] Creating parameter metric for: {}", resourceWrapper.getName());}}}metric.initialize(rule);}//該方法在ParamFlowChecker的passThrottleLocalCheck()和passDefaultLocalCheck()方法執行getParameterMetric()方法時被調用public static ParameterMetric getParamMetric(ResourceWrapper resourceWrapper) {if (resourceWrapper == null || resourceWrapper.getName() == null) {return null;}return metricsMap.get(resourceWrapper.getName());}...
}//Metrics for frequent ("hot spot") parameters.
public class ParameterMetric {private static final int THREAD_COUNT_MAX_CAPACITY = 4000;private static final int BASE_PARAM_MAX_CAPACITY = 4000;private static final int TOTAL_MAX_CAPACITY = 20_0000;private final Object lock = new Object();//Format: (rule, (value, timeRecorder))private final Map<ParamFlowRule, CacheMap<Object, AtomicLong>> ruleTimeCounters = new HashMap<>();//Format: (rule, (value, tokenCounter))private final Map<ParamFlowRule, CacheMap<Object, AtomicLong>> ruleTokenCounter = new HashMap<>();private final Map<Integer, CacheMap<Object, AtomicInteger>> threadCountMap = new HashMap<>();public void initialize(ParamFlowRule rule) {if (!ruleTimeCounters.containsKey(rule)) {synchronized (lock) {if (ruleTimeCounters.get(rule) == null) {long size = Math.min(BASE_PARAM_MAX_CAPACITY * rule.getDurationInSec(), TOTAL_MAX_CAPACITY);ruleTimeCounters.put(rule, new ConcurrentLinkedHashMapWrapper<Object, AtomicLong>(size));}}}if (!ruleTokenCounter.containsKey(rule)) {synchronized (lock) {if (ruleTokenCounter.get(rule) == null) {long size = Math.min(BASE_PARAM_MAX_CAPACITY * rule.getDurationInSec(), TOTAL_MAX_CAPACITY);ruleTokenCounter.put(rule, new ConcurrentLinkedHashMapWrapper<Object, AtomicLong>(size));}}}if (!threadCountMap.containsKey(rule.getParamIdx())) {synchronized (lock) {if (threadCountMap.get(rule.getParamIdx()) == null) {threadCountMap.put(rule.getParamIdx(), new ConcurrentLinkedHashMapWrapper<Object, AtomicInteger>(THREAD_COUNT_MAX_CAPACITY));}}}}//Get the token counter for given parameter rule.//@param rule valid parameter rule//@return the associated token counterpublic CacheMap<Object, AtomicLong> getRuleTokenCounter(ParamFlowRule rule) {return ruleTokenCounter.get(rule);}//Get the time record counter for given parameter rule.//@param rule valid parameter rule//@return the associated time counterpublic CacheMap<Object, AtomicLong> getRuleTimeCounter(ParamFlowRule rule) {return ruleTimeCounters.get(rule);}public long getThreadCount(int index, Object value) {CacheMap<Object, AtomicInteger> cacheMap = threadCountMap.get(index);if (cacheMap == null) {return 0;}AtomicInteger count = cacheMap.get(value);return count == null ? 0L : count.get();}...
}

五.參數限流驗證請求的流程圖總結

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/902287.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/902287.shtml
英文地址,請注明出處:http://en.pswp.cn/news/902287.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

多數據源配置(MyBatis-Plus vs AbstractRoutingDataSource)

MyBatis-Plus vs AbstractRoutingDataSource MyBatis-Plus多數據源配 1.添加依賴 <dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>3.5.0</version> <…

聊透多線程編程-線程互斥與同步-13. C# Mutex類實現線程互斥

目錄 一、什么是臨界區&#xff1f; 二、Mutex類簡介 三、Mutex的基本用法 解釋&#xff1a; 四、Mutex的工作原理 五、使用示例1-保護共享資源 解釋&#xff1a; 六、使用示例2-跨進程同步 示例場景 1. 進程A - 主進程 2. 進程B - 第二個進程 輸出結果 ProcessA …

stm32week12

stm32學習 九.stm32與HAL庫 2.HAL庫框架 總架構&#xff1a; 文件介紹&#xff1a; ppp是某一外設&#xff0c;ex是拓展功能 HAL庫API函數和變量命名規則&#xff1a; HAL庫對寄存器位操作的相關宏定義&#xff1a; HAL庫的回調函數&#xff1a; 3.STM32啟動過程 MDK編譯過…

opencv HSV的具體描述

色調H&#xff1a; 使用角度度量&#xff0c;取值范圍為0\~360&#xff0c;從紅色開始按逆時針方向計算&#xff0c;紅色為0&#xff0c;綠色為120&#xff0c;藍色為240。它們的補色是&#xff1a;黃色為60&#xff0c;青色為180&#xff0c;紫色為300。通過改變H的值&#x…

Java Lambda表達式指南

一、Lambda表達式基礎 1. 什么是Lambda表達式&#xff1f; 匿名函數&#xff1a;沒有名稱的函數函數式編程&#xff1a;可作為參數傳遞的代碼塊簡潔語法&#xff1a;替代匿名內部類的更緊湊寫法 2. 基本語法 (parameters) -> expression 或 (parameters) -> { statem…

面向對象設計中的類的分類:實體類、控制類和邊界類

目錄 前言1. 實體類&#xff08;Entity Class&#xff09;1.1 定義和作用1.2 實體類的特點1.3 實體類的示例 2. 控制類&#xff08;Control Class&#xff09;2.1 定義和作用2.2 控制類的特點2.3 控制類的示例 3. 邊界類&#xff08;Boundary Class&#xff09;3.1 定義和作用3…

C# 封裝教程

原文&#xff1a;C# 封裝_w3cschool &#xff08;注&#xff1a;本文為教程文章&#xff0c;請勿標記為付費文章&#xff01;特此聲明&#xff09; 封裝 被定義為"把一個或多個項目封閉在一個物理的或者邏輯的包中"。在面向對象程序設計方法論中&#xff0c;封裝是…

量化交易 - RSRS(阻力支撐相對強度)- 正確用法 - 年均收益18%

經過研究&#xff0c;發現RSRS的正確用法其實是需要用到兩個數據&#xff0c;分別是 n: 一階擬合樣本數&#xff0c;m:求均值方差樣本數&#xff0c;其中n比較小 如18&#xff0c;m比較大 如1100 經過調優后&#xff0c;收益率顯著上升&#xff01; 如下圖&#xff1a; &…

Oracle expdp的 EXCLUDE 參數詳解

Oracle expdp的 EXCLUDE 參數詳解 EXCLUDE 是 Oracle Data Pump Export (expdp) 工具中的一個關鍵參數&#xff0c;用于指定在導出過程中要排除的對象或對象類型。 一、基本語法 expdp username/password DUMPFILEexport.dmp DIRECTORYdpump_dir EXCLUDEobject_type[:name_c…

如何使用3DMAX插件PFSpliner將3D對象轉化為藝術樣條線?

什么是粒子流源(Particle Flow)是3DMAX的一個功能極其強大的粒子系統。它采用事件驅動模型,使用一個名為“粒子視圖”的特殊對話框。在“粒子視圖”中,您可以將描述粒子屬性(如形狀、速度、方向和一段時間內的旋轉)的單個運算符組合成稱為事件的組。每個操作符都提供一組…

【python】 循環語句(while)

1、循環語句 語法&#xff1a; while 條件:......... #只有條件為真時&#xff0c;才會執行while中的內容。 1.1循環語句基本使用 示例1&#xff1a; print("開始") while 1>2:print("人生得意須盡歡") print("結束") #輸出結果&#…

OOA-CNN-LSTM-Attention、CNN-LSTM-Attention、OOA-CNN-LSTM、CNN-LSTM四模型多變量時序預測一鍵對比

OOA-CNN-LSTM-Attention、CNN-LSTM-Attention、OOA-CNN-LSTM、CNN-LSTM四模型多變量時序預測一鍵對比 目錄 OOA-CNN-LSTM-Attention、CNN-LSTM-Attention、OOA-CNN-LSTM、CNN-LSTM四模型多變量時序預測一鍵對比預測效果基本介紹程序設計參考資料 預測效果 基本介紹 基于OOA-CN…

20250421在榮品的PRO-RK3566開發板的Android13下頻繁重啟RKNPU fde40000.npu: Adding to iommu gr

20250421在榮品的PRO-RK3566開發板的Android13下頻繁重啟RKNPU fde40000.npu: Adding to iommu gr 2025/4/21 14:50 緣起&#xff1a;電池沒電了&#xff0c;導致榮品的PRO-RK3566的核心板頻繁重啟。 內核時間4s就重啟。100%復現。 PRO-RK3566 Android13啟動到這里 復位&#…

動態監控進程

1.介紹: top和ps命令很相似,它們都是用來顯示正在執行的進程,top和ps最大的不同之處,在于top在執行中可以更新正在執行的進程. 2.基本語法&#xff1a; top [選項] 選項說明 ??僵死進程&#xff1a;內存沒有釋放,但是進程已經停止工作了,需要及時清理 交互操作說明 應用案…

657SJBH西藏藏藥特產銷售管理系統

畢業論文&#xff08;設計&#xff09;文獻綜述 西藏藏藥特產銷售管理系統的設計與實現 近年來&#xff0c;隨著網絡技術特別是Internet技術的普及和發展&#xff0c;電子商務的開發和應用成為一個熱門領域&#xff0c;在線藏藥特產銷售系統就是這其中的一員。 藏藥產業在西藏…

棧和隊列--數據結構初階(2)(C/C++)

文章目錄 前言理論部分棧的模擬實現STL中的棧容器隊列的模擬實現STL中的隊列容器 作業部分 前言 這期的話會給大家講解棧和隊列的模擬實現和在STL中棧和隊列怎么用的一些知識和習題部分(這部分側重于理論知識&#xff0c;習題倒還是不難) 理論部分 棧的模擬實現 typedef int…

RNN的理解

對于RNN的理解 import torch import torch.nn as nn import torch.nn.functional as F# 手動實現一個簡單的RNN class RNN(nn.Module):def __init__(self, input_size, hidden_size, output_size):super(RNN, self).__init__()# 定義權重矩陣和偏置項self.hidden_size hidden…

二叉查找樹和B樹

二叉查找樹&#xff08;Binary Search Tree, BST&#xff09;和 B 樹&#xff08;B-tree&#xff09;都是用于組織和管理數據的數據結構&#xff0c;但它們在結構、應用場景和性能方面有顯著區別。 二叉查找樹&#xff08;Binary Search Tree, BST&#xff09; 特點&#xff1…

一段式端到端自動駕駛:VAD:Vectorized Scene Representation for Efficient Autonomous Driving

論文地址&#xff1a;https://github.com/hustvl/VAD 代碼地址&#xff1a;https://arxiv.org/pdf/2303.12077 1. 摘要 自動駕駛需要對周圍環境進行全面理解&#xff0c;以實現可靠的軌跡規劃。以往的方法依賴于密集的柵格化場景表示&#xff08;如&#xff1a;占據圖、語義…

OpenCV訓練題

一、創建一個 PyQt 應用程序&#xff0c;該應用程序能夠&#xff1a; 使用 OpenCV 加載一張圖像。在 PyQt 的窗口中顯示這張圖像。提供四個按鈕&#xff08;QPushButton&#xff09;&#xff1a; 一個用于將圖像轉換為灰度圖一個用于將圖像恢復為原始彩色圖一個用于將圖像進行…