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();}...
}
五.参数限流验证请求的流程图总结
相关文章:
Sentinel源码—7.参数限流和注解的实现一
大纲 1.参数限流的原理和源码 2.SentinelResource注解的使用和实现 1.参数限流的原理和源码 (1)参数限流规则ParamFlowRule的配置Demo (2)ParamFlowSlot根据参数限流规则验证请求 (1)参数限流规则ParamFlowRule的配置Demo 一.参数限流的应用场景 二.参数限流规则的属性 …...
JAVA:利用 Apache Tika 提取文件内容的技术指南
1、简述 Apache Tika 是一个强大的工具,用于从各种文件中提取内容和元数据。📄Tika 支持解析文档、📸图像、🎵音频、🎥视频文件以及其他多种格式,非常适合构建🔍搜索引擎、📂内容管理系统和📊数据分析工具。 样例代码:https://gitee.com/lhdxhl/springboot-…...
SVM(支持向量机)
SVM(支持向量机) 原理 SVM的核心目标是找到一个最大化分类间隔的超平面,将不同类别的样本分隔开。其原理可分为三部分: 线性可分情况 通过硬间隔最大化确定超平面,确保所有样本正确分类且间隔最大间隔定义为超平面到最…...
Spark,hadoop的组成
(一)Hadoop的组成 对普通用户来说, Hadoop就是一个东西,一个整体,它能给我们提供无限的磁盘用来保存文件,可以使用提供强大的计算能力。 在Hadoop3.X中,hadoop一共有三个组成部分&#…...
数据结构中的各种排序
排序之冒泡排序 原理:比较相邻的元素,将大的元素放右边,小的元素放左边。每一趟排的最后的元素一定是最大的元素,所有下一趟需要排列的元素可减少一个 public int[] bubbleSort(int[] attr) {for (int i 0; i < attr.length…...
Android 中实现 GIF 图片动画
在 Android 中,ImageView 从 Android 9.0(API 级别 28) 开始原生支持 GIF 动画,通过 AnimatedImageDrawable 类实现。在之前的版本中,ImageView 并不支持直接播放 GIF 动画,只能显示 GIF 的第一帧。 一、 …...
linux安装mysql数据库
1.判断系统是多少位的 file /sbin/init2.下载linux安装包 5.7.25.64位安装包链接:https://pan.baidu.com/s/13vFuRikwJaI96K0AmUQXzg提取码:ga7h其他版本安装 去官网下载:https://dev.mysql.com/downloads/mysql/3.创建mysql文件夹 mkdir /…...
基于SA模拟退火算法的车间调度优化matlab仿真,输出甘特图和优化收敛曲线
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 基于SA模拟退火算法的车间调度优化matlab仿真,输出甘特图和优化收敛曲线。输出指标包括最小平均流动时间,最大完工时间,最小间隙时间。 2…...
uniapp云打包针对谷歌视频图片权限的解决方案
谷歌在24年底推出把图片和视频细分为两个权限,uniapp使用uni.chooseImage云打包默认图片视频为一个权限,不符合谷歌要求会被下架 解决方法,在项目根目录下新建AndroidManifest.xml移除不必要的权限 <?xml version"1.0" encoding"utf…...
DSRAM介绍
DSRAM(双端口静态随机存储器)介绍 1. 基本概念 DSRAM(Dual-Port Static Random Access Memory)是一种双端口SRAM,支持两个独立的读写接口,允许两个设备(如CPU、DMA、FPGA)同时访问…...
【仿Mudou库one thread per loop式并发服务器实现】HTTP协议模块实现
HTTP协议模块实现 1. Util模块2. HttpRequest模块3. HttpResponse模块4. HttpContext模块5. HttpServer模块 1. Util模块 这个模块是一个工具模块,主要提供HTTP协议模块所用到的一些工具函数,比如url编解码,文件读写…等。 #include "s…...
教育行业网络安全:守护学校终端安全,筑牢教育行业网络安全防线!
教育行业面临的终端安全问题日益突出,主要源于教育信息化进程的加速、终端设备多样化以及网络环境的开放性。 以下是教育行业终端安全面临的主要挑战: 1、设备类型复杂化 问题:教育机构使用的终端设备包括PC、服务器等,操作系统…...
【网工第6版】第5章 网络互联②
目录 ■ IPV6 ▲ IPV6报文格式 ◎ IPV6扩展报头(RFC2460) ◎ IPv6相关协议 ▲ IPV6地址分类 ◎ IPv6地址基础 ◎ IPv6地址举例 ◎ IPv6地址分类 ◎ 特殊地址对比IPv4 vs IPv6 ▲ 过渡技术 本章重要程度:☆☆☆☆☆ ■ IPV6 与IPv4…...
ASP.NET Core 分层项目中EFCore的使用
文章目录 前言一、核心二、项目分层结构1)安装 NuGet 包Web 项目InfrastructureLibrary项目 2)领域模型和仓储接口 (Domain 层)3)基础设施层实现 (Infrastructure 层)4)应用层服务 (Application 层)5)Web API 配置6&am…...
.net core 中directory , directoryinfo ,file, fileinfo区别,联系,场景
一、类定义及核心功能 Directory类 类型:静态类 功能:提供目录操作的静态方法,包括创建、删除、移动目录,以及获取子目录或文件列表等。例如Directory.CreateDirectory()、Directory.GetFiles()。 适用场景&…...
jvm-获取方法签名的方法
在Java中,获取方法签名的方法可以通过以下几种方式实现,具体取决于你的需求和使用场景。以下是详细的介绍: 1. 使用反射 API Java 提供了 java.lang.reflect.Method 类来获取方法的相关信息,包括方法签名。 示例代码:…...
three.js中的instancedMesh类优化渲染多个同网格材质的模型
three.js小白的学习之路。 在上上一篇博客中,简单验证了一下three.js中的网格共享。写的时候就有一些想法,如果说某个场景中有一万棵树,这些树共享一个geometry和material,有没有好的办法将其进行一定程度上的渲染优化࿰…...
2025年一站式AI创作平台主要功能介绍及使用教程
在当今迅速发展的数字时代,人工智能(AI)已成为推动创新和提升工作效率的关键工具。今天给大家分享一个全面的一站式AIGC内容创作平台,对其主要功能及使用教程进行讲解,旨在帮助用户显著提升工作和学习效率。无论您需要…...
YOLO11改进,尺度动态损失函数Scale-based Dynamic Loss,减少标签不准确对损失函数稳定性的影响
在目标检测领域,标签噪声与尺度敏感问题始终是制约模型性能提升的"阿喀琉斯之踵"。2025年CVPR最佳论文提出的尺度动态损失函数(Scale-based Dynamic Loss, SDL),通过构建自适应损失调节机制,不仅实现了对YOLOv11检测精度的指数级提升,更重新定义了损失函数的设…...
<项目代码>YOLO小船识别<目标检测>
项目代码下载链接 YOLOv8是一种单阶段(one-stage)检测算法,它将目标检测问题转化为一个回归问题,能够在一次前向传播过程中同时完成目标的分类和定位任务。相较于两阶段检测算法(如Faster R-CNN)࿰…...
我用deepseek做了一个提取压缩文件夹下pdf和word文件工具
由于最近需要把大量的压缩文件的pdf和word文件统一复制到一个文件夹中。 我们一般正常操作方式的是把一个压缩文件一个一个解压,然后在把一个的解压好的文件夹下文件复制到另外一个文件夹中。 这个也需太繁琐了,从以往统计的需要花费两个小时间&#x…...
单例模式 (Singleton Pattern)
单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来访问该实例。 核心特点 唯一性:一个类只能有一个实例 全局访问:提供全局访问该实例的方式 延迟初始化:通常在第一次被请求时才创建实…...
01-初识前端
一、邂逅前端开发 1.1. 软件开发、软件开发体系 这儿放个图~ 1.2.完善的应用程序包括哪些? 服务器开发 iOS开发、Android开发 Web开发 桌面开发(windows,mac os) iOS、mac os(OC,swift)&am…...
【JavaWeb后端开发03】MySQL入门
文章目录 1. 前言1.1 引言1.2 相关概念 2. MySQL概述2.1 安装2.2 连接2.2.1 介绍2.2.2 企业使用方式(了解) 2.3 数据模型2.3.1 **关系型数据库(RDBMS)**2.3.2 数据模型 3. SQL语句3.1 DDL语句3.1.1 数据库操作3.1.1.1 查询数据库3.1.1.2 创建数据库3.1.1…...
使用纯前端技术html+css+js实现一个蔬果商城的前端模板!
当我们刚开始学习前端的时候,我们都会先学习一些基础的编程知识点。对于网站开发前端学习,我们就会学习 html css js 等基础的前端技术,我们学习了基础编程知识后,肯定是需要一些项目,或者一些练习题,巩固一…...
SAP系统生产跟踪报表入库数异常
生产跟踪报表入库数异常 交库21820,入库43588是不可能的 原因排查: 报表的入库数取值,是取移动类型321 (即系检验合格后过账到非限制使用)的数. 查凭证,101过账2次21807,321过账了2次21794,然后用102退1次21794.就是说这批物料重复交库了. 解决: 方案一:开发增强设…...
mac 本地 docker 部署 nacos
标题查看 docker 的 nacos 版本 查看可用的Nacos版本,以最新版为例. 指定版本 自己修改即可. 访问Nacos镜像库地址:https://hub.docker.com/r/nacos/nacos-server/tags?page1&orderinglast_updated 标题二、挂载目录配置步骤 标题创建本地目录 按用户要…...
cgroup threaded功能例子
一、背景 cgroup在如今的系统里基本都是默认打开的一个功能。对于cgroup的cpu子系统,默认的颗粒度是进程为维度进行cgroup的cpu及cpuset的控制。而对于一些复杂进程,可能的需求是进程里一些个别线程要绑定在X1-Xn这些cpu核上,而除了这些个别…...
Elasticsearch插件:IDEA中的Elasticsearch开发利器
Elasticsearch插件:IDEA中的Elasticsearch开发利器 一、插件概述 Elasticsearch插件是为IntelliJ IDEA设计的专业工具,它让开发者能在IDE内直接与Elasticsearch集群交互,提供了查询编写、索引管理、数据分析等全方位支持。 核心价值&#…...
electron从安装到启动再到打包全教程
目录 介绍 安装 修改npm包配置 执行安装命令 源代码 运行 打包 先安装git, 安装打包工具 导入打包工具 执行打包命令 总结 介绍 electron确实好用,但安装是真的要耗费半条命。每次安装都会遇到各种问题,然后解决了之后。后面就不需要安装了,但有时候比如电脑重装…...
【Linux】轻量级命令解释器minishell
Minishell 一、项目背景 在linux操作系统中,用户对操作系统进行的一系列操作都不能直接操作内核,而是通过shell间接对内核进行操作。 Shell 是操作系统中的一种程序,它为用户提供了一种与操作系统内核和计算机硬件进行交互的界面。用户可以通…...
KEIL报错解决方案:No Algorithm found for: 08001000H - 080012EBH?
改这里: Cortex JLink/JTrace Target Drive - Flash Download - Size: 配好你这款芯片应该用的空间大小...
用银河麒麟 LiveCD 快速查看原系统 IP 和打印机配置
原文链接:用银河麒麟 LiveCD 快速查看原系统 IP 和打印机配置 Hello,大家好啊!今天给大家带来一篇在银河麒麟操作系统的 LiveCD 或系统试用镜像环境下,如何查看原系统中电脑的 IP 地址与网络打印机 IP 地址的实用教程。在系统损坏…...
DeepseekV3MLP 模块
目录 代码代码解释导入和激活函数配置类初始化方法前向传播方法计算流程 代码可视化 代码 import torch import torch.nn as nn import torch.nn.functional as F# 定义激活函数字典 ACT2FN {"relu": F.relu,"gelu": F.gelu,"silu": F.silu,&q…...
Ubuntu 系统下安装和使用性能分析工具 perf
在 Ubuntu 系统下安装和使用性能分析工具 perf 的步骤如下: 1. 安装 perf perf 是 Linux 内核的一部分,通常通过安装 linux-tools 包获取: # 更新软件包列表 sudo apt update# 安装 perf(根据当前内核版本自动匹配) …...
安恒Web安全面试题
《网安面试指南》https://mp.weixin.qq.com/s/RIVYDmxI9g_TgGrpbdDKtA?token1860256701&langzh_CN 5000篇网安资料库https://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247486065&idx2&snb30ade8200e842743339d428f414475e&chksmc0e4732df793fa3bf39…...
OSPF --- LSA
文章目录 一、OSPF LSA(链路状态通告)详解1. LSA通用头部2. OSPFv2 主要LSA类型a. Type 1 - Router LSAb. Type 2 - Network LSAc. Type 3 - Summary LSAd. Type 4 - ASBR Summary LSAe. Type 5 - AS External LSAf. Type 7 - NSSA External LSA 3. LSA泛…...
IDEA/WebStorm中Git操作缓慢的解决方案
问题描述 在WebStorm中进行前端开发时,发现Git操作(如push、checkout、pull等)特别缓慢,而在命令行(cmd)中执行相同的Git命令却很快,排除了网络问题。 解决方案 通过修改WebStorm安装目录下的runnerw.exe文件名可以…...
网络威胁情报 | Yara
Yara 是一个在威胁情报、数字取证和威胁猎取方面较为常用的语言。本文并非是Yara语言的教程,更多的是希望可以让大家知道这个语言的神奇之处及其在当今信息安全领域的重要性。 Yara 是什么? “恶意软件研究人员(以及其他所有人)…...
12.QT-Combo Box|Spin Box|模拟点餐|从文件中加载选项|调整点餐份数(C++)
Combo Box QComboBox 表⽰下拉框 核⼼属性 属性说明currentText当前选中的⽂本currentIndex当前选中的条⽬下标.从0开始计算.如果当前没有条⽬被选中,值为-1editable是否允许修改设为true时, QComboBox 的⾏为就⾮常接近 QLineEdit ,也可以 设置 validatoriconSize下拉框图标…...
FTTR 全屋光纤架构分享
随着光纤网络技术的发展,FTTR 技术逐步普及到千家万户,为了战未来,从现在开始构建并铺设 FTTR 全屋光纤是非常有必要的。 在前期 FTTR 全屋光纤网络的载荷搭建,可以额定为千兆网络或者2.5GE光纤网络,万兆光网最大的成本…...
内网穿透快解析免费开放硬件集成SDK
一、行业问题 随着物联网技术的发展,符合用户需求的智能硬件设备被广泛的应用到各个领域,而智能设备的远程运维管理也是企业用户遇到的问题 二、快解析内网穿透解决方案 快解析是一款内网穿透产品,可以实现内网资源在外网访问,…...
实验八 版本控制
实验八 版本控制 一、实验目的 掌握Git基本命令的使用。 二、实验内容 1.理解版本控制工具的意义。 2.安装Windows和Linux下的git工具。 3.利用git bash结合常用Linux命令管理文件和目录。 4.利用git创建本地仓库并进行简单的版本控制实验。 三、主要实验步骤 1.下载并安…...
《马尼拉》桌游期望计算器
《马尼拉》桌游期望计算器:做出最明智的决策 注:本项目仍在开发验证中,计算结果可能不够准确,欢迎游戏爱好者提供协助! 在线使用 | GitHub 项目简介 马尼拉期望计算器是一个基于 Vue 3 Vite 开发的网页应用ÿ…...
VLAN间通讯技术
多臂路由 路由器使用多条物理线路,每条物理线路充当一个 VLAN 的网管 注意:路由器对端的交换机接口,需要设定 Access 类型,因为路由器的物理接口无法处理 VLAN 标签 。 单臂路由 使用 以太网子接口 (sub-interface) 实现。 …...
linux基础学习--linux文件与目录管理
linux文件与目录管理 1. 目录与路径 1.1 相对路径与绝对路径 绝对路径:路径写法一定从根目录/写起。 绝对路径的正确度要高。 相对路径:路径写法不是由/写起。 1.2 目录的相关操作 切换目录的命令是cd,下面是比较特殊的目录:…...
云原生--基础篇-2--云计算概述(云计算是云原生的基础,IaaS、PaaS和SaaS服务模型)
1、云计算概念 云计算是一种通过互联网提供计算资源(包括服务器、存储、数据库、网络、软件等)和服务的技术模式。用户无需拥有和维护物理硬件,而是可以根据需要租用这些资源,并按使用量付费。 2、云计算特点 (1&am…...
存储器综合:内存条
一、RW 1000题刷题 1、计算Cache缺失率 2、 二、前提回顾 1、CPU从单个DRAM芯片中取地址 注意:Cache与主存的交互以“主存块”为单位,当出现Cache Miss时,主存以“主存块”为单位传输至Cache中。 2、内存条编址 多个DRAM芯片组成内存条&a…...
树莓派超全系列教程文档--(38)config.txt视频配置
config.txt视频配置 视频选项HDMI模式树莓派4-系列的HDMI树莓派5-系列的HDMI 复合视频模式enable_tvout LCD显示器和触摸屏ignore_lcddisable_touchscreen 通用显示选项disable_fw_kms_setup 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 视频选…...
pytest-项目结构
项目结构 api_test_project/ ├── config/ │ └── config.py # 配置文件,存储接口的基本信息,如 URL、请求头、认证信息等 ├── data/ │ └── test_data.json # 测试数据文件,存储接口的请求参数、预期结果等 ├── tests/…...