This commit is contained in:
shuhongfan
2023-09-04 16:40:17 +08:00
commit cf5ac25c14
8267 changed files with 1305066 additions and 0 deletions

View File

@@ -0,0 +1,59 @@
# Sentinel Parameter Flow Control
This component provides functionality of flow control by frequent ("hot spot") parameters.
## Usage
To use Sentinel Parameter Flow Control, you need to add the following dependency to `pom.xml`:
```xml
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-parameter-flow-control</artifactId>
<version>x.y.z</version>
</dependency>
```
First you need to pass parameters with the following `SphU.entry` overloaded methods:
```java
public static Entry entry(String name, EntryType type, int count, Object... args) throws BlockException
public static Entry entry(Method method, EntryType type, int count, Object... args) throws BlockException
```
For example, if there are two parameters to provide, you can:
```java
// paramA in index 0, paramB in index 1.
SphU.entry(resourceName, EntryType.IN, 1, paramA, paramB);
```
Then you can configure parameter flow control rules via `loadRules` method in `ParamFlowRuleManager`:
```java
// 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)
.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.loadRules(Collections.singletonList(rule));
```
The description for fields of `ParamFlowRule`:
| Field | Description | Default |
| :----: | :----| :----|
| resource| resource name (**required**) ||
| count | flow control threshold (**required**) ||
| grade | metric type (QPS or thread count) | QPS mode |
| paramIdx | the index of provided parameter in `SphU.entry(xxx, args)` (**required**) ||
| paramFlowItemList | the exception items of parameter; you can set threshold to a specific parameter value ||
Now the parameter flow control rules will take effect.

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>sentinel-extension</artifactId>
<groupId>com.alibaba.csp</groupId>
<version>1.8.3</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sentinel-parameter-flow-control</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-common</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.googlecode.concurrentlinkedhashmap</groupId>
<artifactId>concurrentlinkedhashmap-lru</artifactId>
<version>1.4.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,36 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.command.handler;
import com.alibaba.csp.sentinel.command.CommandHandler;
import com.alibaba.csp.sentinel.command.CommandRequest;
import com.alibaba.csp.sentinel.command.CommandResponse;
import com.alibaba.csp.sentinel.command.annotation.CommandMapping;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager;
import com.alibaba.fastjson.JSON;
/**
* @author Eric Zhao
* @since 0.2.0
*/
@CommandMapping(name = "getParamFlowRules", desc = "Get all parameter flow rules")
public class GetParamFlowRulesCommandHandler implements CommandHandler<String> {
@Override
public CommandResponse<String> handle(CommandRequest request) {
return CommandResponse.ofSuccess(JSON.toJSONString(ParamFlowRuleManager.getRules()));
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.command.handler;
import java.net.URLDecoder;
import java.util.List;
import com.alibaba.csp.sentinel.command.CommandHandler;
import com.alibaba.csp.sentinel.command.CommandRequest;
import com.alibaba.csp.sentinel.command.CommandResponse;
import com.alibaba.csp.sentinel.command.annotation.CommandMapping;
import com.alibaba.csp.sentinel.datasource.WritableDataSource;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.fastjson.JSONArray;
/**
* @author Eric Zhao
* @since 0.2.0
*/
@CommandMapping(name = "setParamFlowRules", desc = "Set parameter flow rules, while previous rules will be replaced.")
public class ModifyParamFlowRulesCommandHandler implements CommandHandler<String> {
private static WritableDataSource<List<ParamFlowRule>> paramFlowWds = null;
@Override
public CommandResponse<String> handle(CommandRequest request) {
String data = request.getParam("data");
if (StringUtil.isBlank(data)) {
return CommandResponse.ofFailure(new IllegalArgumentException("Bad data"));
}
try {
data = URLDecoder.decode(data, "utf-8");
} catch (Exception e) {
RecordLog.info("Decode rule data error", e);
return CommandResponse.ofFailure(e, "decode rule data error");
}
RecordLog.info("[API Server] Receiving rule change (type:parameter flow rule): {}", data);
String result = SUCCESS_MSG;
List<ParamFlowRule> flowRules = JSONArray.parseArray(data, ParamFlowRule.class);
ParamFlowRuleManager.loadRules(flowRules);
if (!writeToDataSource(paramFlowWds, flowRules)) {
result = WRITE_DS_FAILURE_MSG;
}
return CommandResponse.ofSuccess(result);
}
/**
* Write target value to given data source.
*
* @param dataSource writable data source
* @param value target value to save
* @param <T> value type
* @return true if write successful or data source is empty; false if error occurs
*/
private <T> boolean writeToDataSource(WritableDataSource<T> dataSource, T value) {
if (dataSource != null) {
try {
dataSource.write(value);
} catch (Exception e) {
RecordLog.warn("Write data source failed", e);
return false;
}
}
return true;
}
public synchronized static WritableDataSource<List<ParamFlowRule>> getWritableDataSource() {
return paramFlowWds;
}
public synchronized static void setWritableDataSource(WritableDataSource<List<ParamFlowRule>> hotParamWds) {
ModifyParamFlowRulesCommandHandler.paramFlowWds = hotParamWds;
}
private static final String SUCCESS_MSG = "success";
private static final String WRITE_DS_FAILURE_MSG = "partial success (write data source failed)";
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.init;
import com.alibaba.csp.sentinel.slots.statistic.ParamFlowStatisticEntryCallback;
import com.alibaba.csp.sentinel.slots.statistic.ParamFlowStatisticExitCallback;
import com.alibaba.csp.sentinel.slots.statistic.StatisticSlotCallbackRegistry;
/**
* Init function for adding callbacks to {@link StatisticSlotCallbackRegistry} to record metrics
* for frequent parameters in {@link com.alibaba.csp.sentinel.slots.statistic.StatisticSlot}.
*
* @author Eric Zhao
* @since 0.2.0
*/
public class ParamFlowStatisticSlotCallbackInit implements InitFunc {
@Override
public void init() {
StatisticSlotCallbackRegistry.addEntryCallback(ParamFlowStatisticEntryCallback.class.getName(),
new ParamFlowStatisticEntryCallback());
StatisticSlotCallbackRegistry.addExitCallback(ParamFlowStatisticExitCallback.class.getName(),
new ParamFlowStatisticExitCallback());
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.slots;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowSlot;
/**
* @author Eric Zhao
* @since 0.2.0
*
* @deprecated since 1.7.2, we can use @Spi(order = -3000) to adjust the order of {@link ParamFlowSlot},
* this class is reserved for compatibility with older versions.
*
* @see ParamFlowSlot
* @see DefaultSlotChainBuilder
*/
@Deprecated
public class HotParamSlotChainBuilder extends DefaultSlotChainBuilder {
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.slots.block.flow.param;
/**
* ParamFlowArgument
*/
public interface ParamFlowArgument {
/**
* @return the object as a key of param flow limit
*/
Object paramFlowKey();
}

View File

@@ -0,0 +1,327 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.slots.block.flow.param;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import com.alibaba.csp.sentinel.cluster.ClusterStateManager;
import com.alibaba.csp.sentinel.cluster.TokenResult;
import com.alibaba.csp.sentinel.cluster.TokenResultStatus;
import com.alibaba.csp.sentinel.cluster.TokenService;
import com.alibaba.csp.sentinel.cluster.client.TokenClientProvider;
import com.alibaba.csp.sentinel.cluster.server.EmbeddedClusterTokenServerProvider;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.statistic.cache.CacheMap;
import com.alibaba.csp.sentinel.util.TimeUtil;
/**
* Rule checker for parameter flow control.
*
* @author Eric Zhao
* @since 0.2.0
*/
public final class ParamFlowChecker {
public static boolean passCheck(ResourceWrapper resourceWrapper, /*@Valid*/ ParamFlowRule rule, /*@Valid*/ int count,
Object... args) {
if (args == null) {
return true;
}
int paramIdx = rule.getParamIdx();
if (args.length <= paramIdx) {
return true;
}
// Get parameter value.
Object value = args[paramIdx];
// Assign value with the result of paramFlowKey method
if (value instanceof ParamFlowArgument) {
value = ((ParamFlowArgument) value).paramFlowKey();
}
// If value is null, then pass
if (value == null) {
return true;
}
if (rule.isClusterMode() && rule.getGrade() == RuleConstant.FLOW_GRADE_QPS) {
return passClusterCheck(resourceWrapper, rule, count, value);
}
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) {
if (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) {
Set<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;
}
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();
}
}
}
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);
}
@SuppressWarnings("unchecked")
private static Collection<Object> toCollection(Object value) {
if (value instanceof Collection) {
return (Collection<Object>)value;
} else if (value.getClass().isArray()) {
List<Object> params = new ArrayList<Object>();
int length = Array.getLength(value);
for (int i = 0; i < length; i++) {
Object param = Array.get(value, i);
params.add(param);
}
return params;
} else {
return Collections.singletonList(value);
}
}
private static boolean passClusterCheck(ResourceWrapper resourceWrapper, ParamFlowRule rule, int count,
Object value) {
try {
Collection<Object> params = toCollection(value);
TokenService clusterService = pickClusterService();
if (clusterService == null) {
// No available cluster client or server, fallback to local or
// pass in need.
return fallbackToLocalOrPass(resourceWrapper, rule, count, params);
}
TokenResult result = clusterService.requestParamToken(rule.getClusterConfig().getFlowId(), count, params);
switch (result.getStatus()) {
case TokenResultStatus.OK:
return true;
case TokenResultStatus.BLOCKED:
return false;
default:
return fallbackToLocalOrPass(resourceWrapper, rule, count, params);
}
} catch (Throwable ex) {
RecordLog.warn("[ParamFlowChecker] Request cluster token for parameter unexpected failed", ex);
return fallbackToLocalOrPass(resourceWrapper, rule, count, value);
}
}
private static boolean fallbackToLocalOrPass(ResourceWrapper resourceWrapper, ParamFlowRule rule, int count,
Object value) {
if (rule.getClusterConfig().isFallbackToLocalWhenFail()) {
return passLocalCheck(resourceWrapper, rule, count, value);
} else {
// The rule won't be activated, just pass.
return true;
}
}
private static TokenService pickClusterService() {
if (ClusterStateManager.isClient()) {
return TokenClientProvider.getClient();
}
if (ClusterStateManager.isServer()) {
return EmbeddedClusterTokenServerProvider.getServer();
}
return null;
}
private ParamFlowChecker() {
}
}

View File

@@ -0,0 +1,125 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.slots.block.flow.param;
import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
/**
* Parameter flow rule config in cluster mode.
*
* @author Eric Zhao
* @since 1.4.0
*/
public class ParamFlowClusterConfig {
/**
* Global unique ID.
*/
private Long flowId;
/**
* Threshold type (average by local value or global value).
*/
private int thresholdType = ClusterRuleConstant.FLOW_THRESHOLD_AVG_LOCAL;
private boolean fallbackToLocalWhenFail = false;
private int sampleCount = ClusterRuleConstant.DEFAULT_CLUSTER_SAMPLE_COUNT;
/**
* The time interval length of the statistic sliding window (in milliseconds)
*/
private int windowIntervalMs = RuleConstant.DEFAULT_WINDOW_INTERVAL_MS;
public Long getFlowId() {
return flowId;
}
public ParamFlowClusterConfig setFlowId(Long flowId) {
this.flowId = flowId;
return this;
}
public int getThresholdType() {
return thresholdType;
}
public ParamFlowClusterConfig setThresholdType(int thresholdType) {
this.thresholdType = thresholdType;
return this;
}
public boolean isFallbackToLocalWhenFail() {
return fallbackToLocalWhenFail;
}
public ParamFlowClusterConfig setFallbackToLocalWhenFail(boolean fallbackToLocalWhenFail) {
this.fallbackToLocalWhenFail = fallbackToLocalWhenFail;
return this;
}
public int getSampleCount() {
return sampleCount;
}
public ParamFlowClusterConfig setSampleCount(int sampleCount) {
this.sampleCount = sampleCount;
return this;
}
public int getWindowIntervalMs() {
return windowIntervalMs;
}
public ParamFlowClusterConfig setWindowIntervalMs(int windowIntervalMs) {
this.windowIntervalMs = windowIntervalMs;
return this;
}
@Override
public boolean equals(Object o) {
if (this == o) { return true; }
if (o == null || getClass() != o.getClass()) { return false; }
ParamFlowClusterConfig config = (ParamFlowClusterConfig)o;
if (thresholdType != config.thresholdType) { return false; }
if (fallbackToLocalWhenFail != config.fallbackToLocalWhenFail) { return false; }
if (sampleCount != config.sampleCount) { return false; }
if (windowIntervalMs != config.windowIntervalMs) { return false; }
return flowId != null ? flowId.equals(config.flowId) : config.flowId == null;
}
@Override
public int hashCode() {
int result = flowId != null ? flowId.hashCode() : 0;
result = 31 * result + thresholdType;
result = 31 * result + (fallbackToLocalWhenFail ? 1 : 0);
result = 31 * result + sampleCount;
result = 31 * result + windowIntervalMs;
return result;
}
@Override
public String toString() {
return "ParamFlowClusterConfig{" +
"flowId=" + flowId +
", thresholdType=" + thresholdType +
", fallbackToLocalWhenFail=" + fallbackToLocalWhenFail +
", sampleCount=" + sampleCount +
", windowIntervalMs=" + windowIntervalMs +
'}';
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.slots.block.flow.param;
import com.alibaba.csp.sentinel.slots.block.BlockException;
/**
* Block exception for frequent ("hot-spot") parameter flow control.
*
* @author jialiang.linjl
* @since 0.2.0
*/
public class ParamFlowException extends BlockException {
private final String resourceName;
public ParamFlowException(String resourceName, String message, Throwable cause) {
super(message, cause);
this.resourceName = resourceName;
}
public ParamFlowException(String resourceName, String param) {
super(param, param);
this.resourceName = resourceName;
}
public ParamFlowException(String resourceName, String param, ParamFlowRule rule) {
super(param, param);
this.resourceName = resourceName;
this.rule = rule;
}
public String getResourceName() {
return resourceName;
}
@Override
public Throwable fillInStackTrace() {
return this;
}
/**
* Get the parameter value that triggered the parameter flow control.
*
* @return the parameter value
* @since 1.4.2
*/
public String getLimitParam() {
return getMessage();
}
/**
* Get triggered rule.
* Note: the rule result is a reference to rule map and SHOULD NOT be modified.
*
* @return triggered rule
* @since 1.4.2
*/
@Override
public ParamFlowRule getRule() {
return rule.as(ParamFlowRule.class);
}
}

View File

@@ -0,0 +1,101 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.slots.block.flow.param;
/**
* A flow control item for a specific parameter value.
*
* @author jialiang.linjl
* @author Eric Zhao
* @since 0.2.0
*/
public class ParamFlowItem {
private String object;
private Integer count;
private String classType;
public ParamFlowItem() {}
public ParamFlowItem(String object, Integer count, String classType) {
this.object = object;
this.count = count;
this.classType = classType;
}
public static <T> ParamFlowItem newItem(T object, Integer count) {
if (object == null) {
throw new IllegalArgumentException("Invalid object: null");
}
return new ParamFlowItem(object.toString(), count, object.getClass().getName());
}
public String getObject() {
return object;
}
public ParamFlowItem setObject(String object) {
this.object = object;
return this;
}
public Integer getCount() {
return count;
}
public ParamFlowItem setCount(Integer count) {
this.count = count;
return this;
}
public String getClassType() {
return classType;
}
public ParamFlowItem setClassType(String classType) {
this.classType = classType;
return this;
}
@Override
public boolean equals(Object o) {
if (this == o) { return true; }
if (o == null || getClass() != o.getClass()) { return false; }
ParamFlowItem item = (ParamFlowItem)o;
if (object != null ? !object.equals(item.object) : item.object != null) { return false; }
if (count != null ? !count.equals(item.count) : item.count != null) { return false; }
return classType != null ? classType.equals(item.classType) : item.classType == null;
}
@Override
public int hashCode() {
int result = object != null ? object.hashCode() : 0;
result = 31 * result + (count != null ? count.hashCode() : 0);
result = 31 * result + (classType != null ? classType.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "ParamFlowItem{" +
"object=" + object +
", count=" + count +
", classType='" + classType + '\'' +
'}';
}
}

View File

@@ -0,0 +1,244 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.slots.block.flow.param;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import com.alibaba.csp.sentinel.slots.block.AbstractRule;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
/**
* Rules for "hot-spot" frequent parameter flow control.
*
* @author jialiang.linjl
* @author Eric Zhao
* @since 0.2.0
*/
public class ParamFlowRule extends AbstractRule {
public ParamFlowRule() {}
public ParamFlowRule(String resourceName) {
setResource(resourceName);
}
/**
* The threshold type of flow control (0: thread count, 1: QPS).
*/
private int grade = RuleConstant.FLOW_GRADE_QPS;
/**
* Parameter index.
*/
private Integer paramIdx;
/**
* The threshold count.
*/
private double count;
/**
* Traffic shaping behavior (since 1.6.0).
*/
private int controlBehavior = RuleConstant.CONTROL_BEHAVIOR_DEFAULT;
private int maxQueueingTimeMs = 0;
private int burstCount = 0;
private long durationInSec = 1;
/**
* Original exclusion items of parameters.
*/
private List<ParamFlowItem> paramFlowItemList = new ArrayList<ParamFlowItem>();
/**
* Parsed exclusion items of parameters. Only for internal use.
*/
private Map<Object, Integer> hotItems = new HashMap<Object, Integer>();
/**
* Indicating whether the rule is for cluster mode.
*/
private boolean clusterMode = false;
/**
* Cluster mode specific config for parameter flow rule.
*/
private ParamFlowClusterConfig clusterConfig;
public int getControlBehavior() {
return controlBehavior;
}
public ParamFlowRule setControlBehavior(int controlBehavior) {
this.controlBehavior = controlBehavior;
return this;
}
public int getMaxQueueingTimeMs() {
return maxQueueingTimeMs;
}
public ParamFlowRule setMaxQueueingTimeMs(int maxQueueingTimeMs) {
this.maxQueueingTimeMs = maxQueueingTimeMs;
return this;
}
public int getBurstCount() {
return burstCount;
}
public ParamFlowRule setBurstCount(int burstCount) {
this.burstCount = burstCount;
return this;
}
public long getDurationInSec() {
return durationInSec;
}
public ParamFlowRule setDurationInSec(long durationInSec) {
this.durationInSec = durationInSec;
return this;
}
public int getGrade() {
return grade;
}
public ParamFlowRule setGrade(int grade) {
this.grade = grade;
return this;
}
public Integer getParamIdx() {
return paramIdx;
}
public ParamFlowRule setParamIdx(Integer paramIdx) {
this.paramIdx = paramIdx;
return this;
}
public double getCount() {
return count;
}
public ParamFlowRule setCount(double count) {
this.count = count;
return this;
}
public List<ParamFlowItem> getParamFlowItemList() {
return paramFlowItemList;
}
public ParamFlowRule setParamFlowItemList(List<ParamFlowItem> paramFlowItemList) {
this.paramFlowItemList = paramFlowItemList;
return this;
}
public Integer retrieveExclusiveItemCount(Object value) {
if (value == null || hotItems == null) {
return null;
}
return hotItems.get(value);
}
Map<Object, Integer> getParsedHotItems() {
return hotItems;
}
ParamFlowRule setParsedHotItems(Map<Object, Integer> hotItems) {
this.hotItems = hotItems;
return this;
}
public boolean isClusterMode() {
return clusterMode;
}
public ParamFlowRule setClusterMode(boolean clusterMode) {
this.clusterMode = clusterMode;
return this;
}
public ParamFlowClusterConfig getClusterConfig() {
return clusterConfig;
}
public ParamFlowRule setClusterConfig(ParamFlowClusterConfig clusterConfig) {
this.clusterConfig = clusterConfig;
return this;
}
@Override
public boolean equals(Object o) {
if (this == o) { return true; }
if (o == null || getClass() != o.getClass()) { return false; }
if (!super.equals(o)) { return false; }
ParamFlowRule that = (ParamFlowRule)o;
if (grade != that.grade) { return false; }
if (Double.compare(that.count, count) != 0) { return false; }
if (controlBehavior != that.controlBehavior) { return false; }
if (maxQueueingTimeMs != that.maxQueueingTimeMs) { return false; }
if (burstCount != that.burstCount) { return false; }
if (durationInSec != that.durationInSec) { return false; }
if (clusterMode != that.clusterMode) { return false; }
if (!Objects.equals(paramIdx, that.paramIdx)) { return false; }
if (!Objects.equals(paramFlowItemList, that.paramFlowItemList)) { return false; }
return Objects.equals(clusterConfig, that.clusterConfig);
}
@Override
public int hashCode() {
int result = super.hashCode();
long temp;
result = 31 * result + grade;
result = 31 * result + (paramIdx != null ? paramIdx.hashCode() : 0);
temp = Double.doubleToLongBits(count);
result = 31 * result + (int)(temp ^ (temp >>> 32));
result = 31 * result + controlBehavior;
result = 31 * result + maxQueueingTimeMs;
result = 31 * result + burstCount;
result = 31 * result + (int)(durationInSec ^ (durationInSec >>> 32));
result = 31 * result + (paramFlowItemList != null ? paramFlowItemList.hashCode() : 0);
result = 31 * result + (clusterMode ? 1 : 0);
result = 31 * result + (clusterConfig != null ? clusterConfig.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "ParamFlowRule{" +
"grade=" + grade +
", paramIdx=" + paramIdx +
", count=" + count +
", controlBehavior=" + controlBehavior +
", maxQueueingTimeMs=" + maxQueueingTimeMs +
", burstCount=" + burstCount +
", durationInSec=" + durationInSec +
", paramFlowItemList=" + paramFlowItemList +
", clusterMode=" + clusterMode +
", clusterConfig=" + clusterConfig +
'}';
}
}

View File

@@ -0,0 +1,153 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.slots.block.flow.param;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.property.DynamicSentinelProperty;
import com.alibaba.csp.sentinel.property.PropertyListener;
import com.alibaba.csp.sentinel.property.SentinelProperty;
import com.alibaba.csp.sentinel.util.AssertUtil;
/**
* Manager for frequent ("hot-spot") parameter flow rules.
*
* @author jialiang.linjl
* @author Eric Zhao
* @since 0.2.0
*/
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.
*
* @param rules new rules to load.
*/
public static void loadRules(List<ParamFlowRule> rules) {
try {
currentProperty.updateValue(rules);
} catch (Throwable e) {
RecordLog.info("[ParamFlowRuleManager] Failed to load rules", e);
}
}
/**
* Listen to the {@link SentinelProperty} for {@link ParamFlowRule}s. The
* property is the source of {@link ParamFlowRule}s. Parameter flow rules
* can also be set by {@link #loadRules(List)} directly.
*
* @param property the property to listen
*/
public static void register2Property(SentinelProperty<List<ParamFlowRule>> property) {
AssertUtil.notNull(property, "property cannot be null");
synchronized (PROPERTY_LISTENER) {
currentProperty.removeListener(PROPERTY_LISTENER);
property.addListener(PROPERTY_LISTENER);
currentProperty = property;
RecordLog.info("[ParamFlowRuleManager] New property has been registered to hot param rule manager");
}
}
public static List<ParamFlowRule> getRulesOfResource(String resourceName) {
return new ArrayList<>(PARAM_FLOW_RULES.get(resourceName));
}
public static boolean hasRules(String resourceName) {
List<ParamFlowRule> rules = PARAM_FLOW_RULES.get(resourceName);
return rules != null && !rules.isEmpty();
}
/**
* Get a copy of the rules.
*
* @return a new copy of the rules.
*/
public static List<ParamFlowRule> getRules() {
List<ParamFlowRule> rules = new ArrayList<>();
for (Map.Entry<String, List<ParamFlowRule>> entry : PARAM_FLOW_RULES.entrySet()) {
rules.addAll(entry.getValue());
}
return rules;
}
static class RulePropertyListener implements PropertyListener<List<ParamFlowRule>> {
@Override
public 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);
}
@Override
public 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);
}
private Map<String, List<ParamFlowRule>> aggregateAndPrepareParamRules(List<ParamFlowRule> list) {
Map<String, List<ParamFlowRule>> newRuleMap = ParamFlowRuleUtil.buildParamRuleMap(list);
if (newRuleMap == null || newRuleMap.isEmpty()) {
// No parameter flow rules, so clear all the metrics.
ParameterMetricStorage.getMetricsMap().clear();
RecordLog.info("[ParamFlowRuleManager] No parameter flow rules, clearing all parameter metrics");
return newRuleMap;
}
// Clear unused parameter metrics.
for (Map.Entry<String, List<ParamFlowRule>> entry : PARAM_FLOW_RULES.entrySet()) {
String resource = entry.getKey();
if (!newRuleMap.containsKey(resource)) {
ParameterMetricStorage.clearParamMetricForResource(resource);
continue;
}
List<ParamFlowRule> newRuleList = newRuleMap.get(resource);
List<ParamFlowRule> oldRuleList = new ArrayList<>(entry.getValue());
oldRuleList.removeAll(newRuleList);
for (ParamFlowRule rule : oldRuleList) {
ParameterMetric parameterMetric = ParameterMetricStorage.getParamMetricForResource(resource);
if (parameterMetric != null) {
parameterMetric.clearForRule(rule);
}
}
}
return newRuleMap;
}
}
private ParamFlowRuleManager() {}
}

View File

@@ -0,0 +1,250 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.slots.block.flow.param;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleUtil;
import com.alibaba.csp.sentinel.util.AssertUtil;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.csp.sentinel.util.function.Function;
import com.alibaba.csp.sentinel.util.function.Predicate;
/**
* @author Eric Zhao
*/
public final class ParamFlowRuleUtil {
/**
* Check whether the provided rule is valid.
*
* @param rule any parameter rule
* @return true if valid, otherwise false
*/
public static boolean isValidRule(ParamFlowRule rule) {
return rule != null && !StringUtil.isBlank(rule.getResource()) && rule.getCount() >= 0
&& rule.getGrade() >= 0 && rule.getParamIdx() != null
&& rule.getBurstCount() >= 0 && rule.getControlBehavior() >= 0
&& rule.getDurationInSec() > 0 && rule.getMaxQueueingTimeMs() >= 0
&& checkCluster(rule);
}
private static boolean checkCluster(/*@PreChecked*/ ParamFlowRule rule) {
if (!rule.isClusterMode()) {
return true;
}
ParamFlowClusterConfig clusterConfig = rule.getClusterConfig();
if (clusterConfig == null) {
return false;
}
if (!FlowRuleUtil.isWindowConfigValid(clusterConfig.getSampleCount(), clusterConfig.getWindowIntervalMs())) {
return false;
}
return validClusterRuleId(clusterConfig.getFlowId());
}
public static boolean validClusterRuleId(Long id) {
return id != null && id > 0;
}
/**
* Fill the parameter rule with parsed items.
*
* @param rule valid parameter rule
*/
public static void fillExceptionFlowItems(ParamFlowRule rule) {
if (rule != null) {
if (rule.getParamFlowItemList() == null) {
rule.setParamFlowItemList(new ArrayList<ParamFlowItem>());
}
Map<Object, Integer> itemMap = parseHotItems(rule.getParamFlowItemList());
rule.setParsedHotItems(itemMap);
}
}
/**
* Build the flow rule map from raw list of flow rules, grouping by resource name.
*
* @param list raw list of flow rules
* @return constructed new flow rule map; empty map if list is null or empty, or no valid rules
* @since 1.6.1
*/
public static Map<String, List<ParamFlowRule>> buildParamRuleMap(List<ParamFlowRule> list) {
return buildParamRuleMap(list, null);
}
/**
* Build the parameter flow rule map from raw list of rules, grouping by resource name.
*
* @param list raw list of parameter flow rules
* @param filter rule filter
* @return constructed new parameter flow rule map; empty map if list is null or empty, or no wanted rules
* @since 1.6.1
*/
public static Map<String, List<ParamFlowRule>> buildParamRuleMap(List<ParamFlowRule> list,
Predicate<ParamFlowRule> filter) {
return buildParamRuleMap(list, filter, true);
}
/**
* Build the parameter flow rule map from raw list of rules, grouping by resource name.
*
* @param list raw list of parameter flow rules
* @param filter rule filter
* @param shouldSort whether the rules should be sorted
* @return constructed new parameter flow rule map; empty map if list is null or empty, or no wanted rules
* @since 1.6.1
*/
public static Map<String, List<ParamFlowRule>> buildParamRuleMap(List<ParamFlowRule> list,
Predicate<ParamFlowRule> filter,
boolean shouldSort) {
return buildParamRuleMap(list, EXTRACT_RESOURCE, filter, shouldSort);
}
/**
* Build the rule map from raw list of parameter flow rules, grouping by provided group function.
*
* @param list raw list of parameter flow rules
* @param groupFunction grouping function of the map (by key)
* @param filter rule filter
* @param shouldSort whether the rules should be sorted
* @param <K> type of key
* @return constructed new rule map; empty map if list is null or empty, or no wanted rules
* @since 1.6.1
*/
public static <K> Map<K, List<ParamFlowRule>> buildParamRuleMap(List<ParamFlowRule> list,
Function<ParamFlowRule, K> groupFunction,
Predicate<ParamFlowRule> filter,
boolean shouldSort) {
AssertUtil.notNull(groupFunction, "groupFunction should not be null");
Map<K, List<ParamFlowRule>> newRuleMap = new ConcurrentHashMap<>();
if (list == null || list.isEmpty()) {
return newRuleMap;
}
Map<K, Set<ParamFlowRule>> tmpMap = new ConcurrentHashMap<>();
for (ParamFlowRule rule : list) {
if (!ParamFlowRuleUtil.isValidRule(rule)) {
RecordLog.warn("[ParamFlowRuleManager] Ignoring invalid rule when loading new rules: " + rule);
continue;
}
if (filter != null && !filter.test(rule)) {
continue;
}
if (StringUtil.isBlank(rule.getLimitApp())) {
rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT);
}
ParamFlowRuleUtil.fillExceptionFlowItems(rule);
K key = groupFunction.apply(rule);
if (key == null) {
continue;
}
Set<ParamFlowRule> flowRules = tmpMap.get(key);
if (flowRules == null) {
// Use hash set here to remove duplicate rules.
flowRules = new HashSet<>();
tmpMap.put(key, flowRules);
}
flowRules.add(rule);
}
for (Entry<K, Set<ParamFlowRule>> entries : tmpMap.entrySet()) {
List<ParamFlowRule> rules = new ArrayList<>(entries.getValue());
if (shouldSort) {
// TODO: Sort the rules.
}
newRuleMap.put(entries.getKey(), rules);
}
return newRuleMap;
}
static Map<Object, Integer> parseHotItems(List<ParamFlowItem> items) {
if (items == null || items.isEmpty()) {
return new HashMap<>();
}
Map<Object, Integer> itemMap = new HashMap<>(items.size());
for (ParamFlowItem item : items) {
// Value should not be null.
Object value;
try {
value = parseItemValue(item.getObject(), item.getClassType());
} catch (Exception ex) {
RecordLog.warn("[ParamFlowRuleUtil] Failed to parse value for item: " + item, ex);
continue;
}
if (item.getCount() == null || item.getCount() < 0 || value == null) {
RecordLog.warn("[ParamFlowRuleUtil] Ignoring invalid exclusion parameter item: " + item);
continue;
}
itemMap.put(value, item.getCount());
}
return itemMap;
}
static Object parseItemValue(String value, String classType) {
if (value == null) {
throw new IllegalArgumentException("Null value");
}
if (StringUtil.isBlank(classType)) {
// If the class type is not provided, then treat it as string.
return value;
}
// Handle primitive type.
if (int.class.toString().equals(classType) || Integer.class.getName().equals(classType)) {
return Integer.parseInt(value);
} else if (boolean.class.toString().equals(classType) || Boolean.class.getName().equals(classType)) {
return Boolean.parseBoolean(value);
} else if (long.class.toString().equals(classType) || Long.class.getName().equals(classType)) {
return Long.parseLong(value);
} else if (double.class.toString().equals(classType) || Double.class.getName().equals(classType)) {
return Double.parseDouble(value);
} else if (float.class.toString().equals(classType) || Float.class.getName().equals(classType)) {
return Float.parseFloat(value);
} else if (byte.class.toString().equals(classType) || Byte.class.getName().equals(classType)) {
return Byte.parseByte(value);
} else if (short.class.toString().equals(classType) || Short.class.getName().equals(classType)) {
return Short.parseShort(value);
} else if (char.class.toString().equals(classType)) {
char[] array = value.toCharArray();
return array.length > 0 ? array[0] : null;
}
return value;
}
private static final Function<ParamFlowRule, String> EXTRACT_RESOURCE = new Function<ParamFlowRule, String>() {
@Override
public String apply(ParamFlowRule rule) {
return rule.getResource();
}
};
private ParamFlowRuleUtil() {}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.slots.block.flow.param;
import com.alibaba.csp.sentinel.context.Context;
import com.alibaba.csp.sentinel.node.DefaultNode;
import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot;
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.spi.Spi;
import java.util.List;
/**
* A processor slot that is responsible for flow control by frequent ("hot spot") parameters.
*
* @author jialiang.linjl
* @author Eric Zhao
* @since 0.2.0
*/
@Spi(order = -3000)
public class ParamFlowSlot extends AbstractLinkedProcessorSlot<DefaultNode> {
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
boolean prioritized, Object... args) throws Throwable {
if (!ParamFlowRuleManager.hasRules(resourceWrapper.getName())) {
fireEntry(context, resourceWrapper, node, count, prioritized, args);
return;
}
checkFlow(resourceWrapper, count, args);
fireEntry(context, resourceWrapper, node, count, prioritized, args);
}
@Override
public 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 {
if (args == null) {
return;
}
if (!ParamFlowRuleManager.hasRules(resourceWrapper.getName())) {
return;
}
List<ParamFlowRule> rules = ParamFlowRuleManager.getRulesOfResource(resourceWrapper.getName());
for (ParamFlowRule rule : rules) {
applyRealParamIdx(rule, args.length);
// Initialize the parameter metrics.
ParameterMetricStorage.initParamMetricsFor(resourceWrapper, rule);
if (!ParamFlowChecker.passCheck(resourceWrapper, rule, count, args)) {
String triggeredParam = "";
if (args.length > rule.getParamIdx()) {
Object value = args[rule.getParamIdx()];
triggeredParam = String.valueOf(value);
}
throw new ParamFlowException(resourceWrapper.getName(), triggeredParam, rule);
}
}
}
}

View File

@@ -0,0 +1,267 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.slots.block.flow.param;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.slots.statistic.cache.CacheMap;
import com.alibaba.csp.sentinel.slots.statistic.cache.ConcurrentLinkedHashMapWrapper;
/**
* Metrics for frequent ("hot spot") parameters.
*
* @author Eric Zhao
* @since 0.2.0
*/
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))
*
* @since 1.6.0
*/
private final Map<ParamFlowRule, CacheMap<Object, AtomicLong>> ruleTimeCounters = new HashMap<>();
/**
* Format: (rule, (value, tokenCounter))
*
* @since 1.6.0
*/
private final Map<ParamFlowRule, CacheMap<Object, AtomicLong>> ruleTokenCounter = new HashMap<>();
private final Map<Integer, CacheMap<Object, AtomicInteger>> threadCountMap = new HashMap<>();
/**
* Get the token counter for given parameter rule.
*
* @param rule valid parameter rule
* @return the associated token counter
* @since 1.6.0
*/
public 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 counter
* @since 1.6.0
*/
public CacheMap<Object, AtomicLong> getRuleTimeCounter(ParamFlowRule rule) {
return ruleTimeCounters.get(rule);
}
public void clear() {
synchronized (lock) {
threadCountMap.clear();
ruleTimeCounters.clear();
ruleTokenCounter.clear();
}
}
public void clearForRule(ParamFlowRule rule) {
synchronized (lock) {
ruleTimeCounters.remove(rule);
ruleTokenCounter.remove(rule);
threadCountMap.remove(rule.getParamIdx());
}
}
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));
}
}
}
}
@SuppressWarnings("rawtypes")
public void decreaseThreadCount(Object... args) {
if (args == null) {
return;
}
try {
for (int index = 0; index < args.length; index++) {
CacheMap<Object, AtomicInteger> threadCount = threadCountMap.get(index);
if (threadCount == null) {
continue;
}
Object arg = args[index];
if (arg == null) {
continue;
}
if (Collection.class.isAssignableFrom(arg.getClass())) {
for (Object value : ((Collection)arg)) {
AtomicInteger oldValue = threadCount.putIfAbsent(value, new AtomicInteger());
if (oldValue != null) {
int currentValue = oldValue.decrementAndGet();
if (currentValue <= 0) {
threadCount.remove(value);
}
}
}
} else if (arg.getClass().isArray()) {
int length = Array.getLength(arg);
for (int i = 0; i < length; i++) {
Object value = Array.get(arg, i);
AtomicInteger oldValue = threadCount.putIfAbsent(value, new AtomicInteger());
if (oldValue != null) {
int currentValue = oldValue.decrementAndGet();
if (currentValue <= 0) {
threadCount.remove(value);
}
}
}
} else {
AtomicInteger oldValue = threadCount.putIfAbsent(arg, new AtomicInteger());
if (oldValue != null) {
int currentValue = oldValue.decrementAndGet();
if (currentValue <= 0) {
threadCount.remove(arg);
}
}
}
}
} catch (Throwable e) {
RecordLog.warn("[ParameterMetric] Param exception", e);
}
}
@SuppressWarnings("rawtypes")
public void addThreadCount(Object... args) {
if (args == null) {
return;
}
try {
for (int index = 0; index < args.length; index++) {
CacheMap<Object, AtomicInteger> threadCount = threadCountMap.get(index);
if (threadCount == null) {
continue;
}
Object arg = args[index];
if (arg == null) {
continue;
}
if (Collection.class.isAssignableFrom(arg.getClass())) {
for (Object value : ((Collection)arg)) {
AtomicInteger oldValue = threadCount.putIfAbsent(value, new AtomicInteger());
if (oldValue != null) {
oldValue.incrementAndGet();
} else {
threadCount.put(value, new AtomicInteger(1));
}
}
} else if (arg.getClass().isArray()) {
int length = Array.getLength(arg);
for (int i = 0; i < length; i++) {
Object value = Array.get(arg, i);
AtomicInteger oldValue = threadCount.putIfAbsent(value, new AtomicInteger());
if (oldValue != null) {
oldValue.incrementAndGet();
} else {
threadCount.put(value, new AtomicInteger(1));
}
}
} else {
AtomicInteger oldValue = threadCount.putIfAbsent(arg, new AtomicInteger());
if (oldValue != null) {
oldValue.incrementAndGet();
} else {
threadCount.put(arg, new AtomicInteger(1));
}
}
}
} catch (Throwable e) {
RecordLog.warn("[ParameterMetric] Param exception", e);
}
}
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();
}
/**
* Get the token counter map. Package-private for test.
*
* @return the token counter map
*/
Map<ParamFlowRule, CacheMap<Object, AtomicLong>> getRuleTokenCounterMap() {
return ruleTokenCounter;
}
Map<Integer, CacheMap<Object, AtomicInteger>> getThreadCountMap() {
return threadCountMap;
}
Map<ParamFlowRule, CacheMap<Object, AtomicLong>> getRuleTimeCounterMap() {
return ruleTimeCounters;
}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright 1999-2019 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.slots.block.flow.param;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
import com.alibaba.csp.sentinel.util.StringUtil;
/**
* @author Eric Zhao
* @since 1.6.1
*/
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.
* Package-private for test.
*
* @param resourceWrapper resource to init
* @param rule relevant rule
*/
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);
}
public static ParameterMetric getParamMetric(ResourceWrapper resourceWrapper) {
if (resourceWrapper == null || resourceWrapper.getName() == null) {
return null;
}
return metricsMap.get(resourceWrapper.getName());
}
public static ParameterMetric getParamMetricForResource(String resourceName) {
if (resourceName == null) {
return null;
}
return metricsMap.get(resourceName);
}
public static void clearParamMetricForResource(String resourceName) {
if (StringUtil.isBlank(resourceName)) {
return;
}
metricsMap.remove(resourceName);
RecordLog.info("[ParameterMetricStorage] Clearing parameter metric for: {}", resourceName);
}
static Map<String, ParameterMetric> getMetricsMap() {
return metricsMap;
}
private ParameterMetricStorage() {}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.slots.block.flow.param;
/**
* @author Eric Zhao
* @since 0.2.0
*/
public enum RollingParamEvent {
/**
* Indicates that the request successfully passed the slot chain (entry).
*/
REQUEST_PASSED,
/**
* Indicates that the request is blocked by a specific slot.
*/
REQUEST_BLOCKED
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.slots.statistic;
import com.alibaba.csp.sentinel.context.Context;
import com.alibaba.csp.sentinel.node.DefaultNode;
import com.alibaba.csp.sentinel.slotchain.ProcessorSlotEntryCallback;
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParameterMetric;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParameterMetricStorage;
/**
* @author Eric Zhao
* @since 0.2.0
*/
public class ParamFlowStatisticEntryCallback implements ProcessorSlotEntryCallback<DefaultNode> {
@Override
public void onPass(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, Object... args) {
// The "hot spot" parameter metric is present only if parameter flow rules for the resource exist.
ParameterMetric parameterMetric = ParameterMetricStorage.getParamMetric(resourceWrapper);
if (parameterMetric != null) {
parameterMetric.addThreadCount(args);
}
}
@Override
public void onBlocked(BlockException ex, Context context, ResourceWrapper resourceWrapper, DefaultNode param,
int count, Object... args) {
// Here we don't add block count here because checking the type of block exception can affect performance.
// We add the block count when throwing the ParamFlowException instead.
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.slots.statistic;
import com.alibaba.csp.sentinel.context.Context;
import com.alibaba.csp.sentinel.slotchain.ProcessorSlotExitCallback;
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParameterMetric;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParameterMetricStorage;
/**
* @author Eric Zhao
* @since 0.2.0
*/
public class ParamFlowStatisticExitCallback implements ProcessorSlotExitCallback {
@Override
public void onExit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
if (context.getCurEntry().getBlockError() == null) {
ParameterMetric parameterMetric = ParameterMetricStorage.getParamMetric(resourceWrapper);
if (parameterMetric != null) {
parameterMetric.decreaseThreadCount(args);
}
}
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.slots.statistic.cache;
import java.util.Set;
/**
* A common cache map interface.
*
* @param <K> type of the key
* @param <V> type of the value
* @author Eric Zhao
* @since 0.2.0
*/
public interface CacheMap<K, V> {
boolean containsKey(K key);
V get(K key);
V remove(K key);
V put(K key, V value);
V putIfAbsent(K key, V value);
long size();
void clear();
Set<K> keySet(boolean ascending);
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.slots.statistic.cache;
import java.util.Set;
import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
import com.googlecode.concurrentlinkedhashmap.Weighers;
/**
* A {@link ConcurrentLinkedHashMap} wrapper for the universal {@link CacheMap}.
*
* @author Eric Zhao
* @since 0.2.0
*/
public class ConcurrentLinkedHashMapWrapper<T, R> implements CacheMap<T, R> {
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
private final ConcurrentLinkedHashMap<T, R> map;
public ConcurrentLinkedHashMapWrapper(long size) {
if (size <= 0) {
throw new IllegalArgumentException("Cache max capacity should be positive: " + size);
}
this.map = new ConcurrentLinkedHashMap.Builder<T, R>()
.concurrencyLevel(DEFAULT_CONCURRENCY_LEVEL)
.maximumWeightedCapacity(size)
.weigher(Weighers.singleton())
.build();
}
public ConcurrentLinkedHashMapWrapper(ConcurrentLinkedHashMap<T, R> map) {
if (map == null) {
throw new IllegalArgumentException("Invalid map instance");
}
this.map = map;
}
@Override
public boolean containsKey(T key) {
return map.containsKey(key);
}
@Override
public R get(T key) {
return map.get(key);
}
@Override
public R remove(T key) {
return map.remove(key);
}
@Override
public R put(T key, R value) {
return map.put(key, value);
}
@Override
public R putIfAbsent(T key, R value) {
return map.putIfAbsent(key, value);
}
@Override
public long size() {
return map.weightedSize();
}
@Override
public void clear() {
map.clear();
}
@Override
public Set<T> keySet(boolean ascending) {
if (ascending) {
return map.ascendingKeySet();
} else {
return map.descendingKeySet();
}
}
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.slots.statistic.data;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import com.alibaba.csp.sentinel.slots.block.flow.param.RollingParamEvent;
import com.alibaba.csp.sentinel.slots.statistic.cache.CacheMap;
import com.alibaba.csp.sentinel.slots.statistic.cache.ConcurrentLinkedHashMapWrapper;
import com.alibaba.csp.sentinel.util.AssertUtil;
/**
* Represents metric bucket of frequent parameters in a period of time window.
*
* @author Eric Zhao
* @since 0.2.0
*/
public class ParamMapBucket {
private final CacheMap<Object, AtomicInteger>[] data;
public ParamMapBucket() {
this(DEFAULT_MAX_CAPACITY);
}
@SuppressWarnings("unchecked")
public ParamMapBucket(int capacity) {
AssertUtil.isTrue(capacity > 0, "capacity should be positive");
RollingParamEvent[] events = RollingParamEvent.values();
this.data = new CacheMap[events.length];
for (RollingParamEvent event : events) {
data[event.ordinal()] = new ConcurrentLinkedHashMapWrapper<Object, AtomicInteger>(capacity);
}
}
public void reset() {
for (RollingParamEvent event : RollingParamEvent.values()) {
data[event.ordinal()].clear();
}
}
public int get(RollingParamEvent event, Object value) {
AtomicInteger counter = data[event.ordinal()].get(value);
return counter == null ? 0 : counter.intValue();
}
public ParamMapBucket add(RollingParamEvent event, int count, Object value) {
AtomicInteger counter = data[event.ordinal()].get(value);
// Note: not strictly concise.
if (counter == null) {
AtomicInteger old = data[event.ordinal()].putIfAbsent(value, new AtomicInteger(count));
if (old != null) {
old.addAndGet(count);
}
} else {
counter.addAndGet(count);
}
return this;
}
public Set<Object> ascendingKeySet(RollingParamEvent type) {
return data[type.ordinal()].keySet(true);
}
public Set<Object> descendingKeySet(RollingParamEvent type) {
return data[type.ordinal()].keySet(false);
}
public static final int DEFAULT_MAX_CAPACITY = 200;
}

View File

@@ -0,0 +1,2 @@
com.alibaba.csp.sentinel.command.handler.GetParamFlowRulesCommandHandler
com.alibaba.csp.sentinel.command.handler.ModifyParamFlowRulesCommandHandler

View File

@@ -0,0 +1 @@
com.alibaba.csp.sentinel.init.ParamFlowStatisticSlotCallbackInit

View File

@@ -0,0 +1 @@
com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowSlot

View File

@@ -0,0 +1,233 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.slots.block.flow.param;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.statistic.cache.ConcurrentLinkedHashMapWrapper;
import com.alibaba.csp.sentinel.util.TimeUtil;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Test cases for {@link ParamFlowChecker}.
*
* @author Eric Zhao
*/
public class ParamFlowCheckerTest {
@Test
public void testHotParamCheckerPassCheckExceedArgs() {
final String resourceName = "testHotParamCheckerPassCheckExceedArgs";
final ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN);
int paramIdx = 1;
ParamFlowRule rule = new ParamFlowRule();
rule.setResource(resourceName);
rule.setCount(10);
rule.setParamIdx(paramIdx);
assertTrue("The rule will pass if the paramIdx exceeds provided args",
ParamFlowChecker.passCheck(resourceWrapper, rule, 1, "abc"));
}
@Test
public void testSingleValueCheckQpsWithExceptionItems() throws InterruptedException {
final String resourceName = "testSingleValueCheckQpsWithExceptionItems";
final ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN);
TimeUtil.currentTimeMillis();
int paramIdx = 0;
long globalThreshold = 5L;
int thresholdB = 0;
int thresholdD = 7;
ParamFlowRule rule = new ParamFlowRule();
rule.setResource(resourceName);
rule.setCount(globalThreshold);
rule.setParamIdx(paramIdx);
rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER);
String valueA = "valueA";
String valueB = "valueB";
String valueC = "valueC";
String valueD = "valueD";
// Directly set parsed map for test.
Map<Object, Integer> map = new HashMap<Object, Integer>();
map.put(valueB, thresholdB);
map.put(valueD, thresholdD);
rule.setParsedHotItems(map);
ParameterMetric metric = new ParameterMetric();
ParameterMetricStorage.getMetricsMap().put(resourceWrapper.getName(), metric);
metric.getRuleTimeCounterMap().put(rule, new ConcurrentLinkedHashMapWrapper<Object, AtomicLong>(4000));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueB));
TimeUnit.SECONDS.sleep(3);
}
@Test
public void testSingleValueCheckThreadCountWithExceptionItems() {
final String resourceName = "testSingleValueCheckThreadCountWithExceptionItems";
final ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN);
int paramIdx = 0;
long globalThreshold = 5L;
int thresholdB = 3;
int thresholdD = 7;
ParamFlowRule rule = new ParamFlowRule(resourceName).setCount(globalThreshold).setParamIdx(paramIdx)
.setGrade(RuleConstant.FLOW_GRADE_THREAD);
String valueA = "valueA";
String valueB = "valueB";
String valueC = "valueC";
String valueD = "valueD";
// Directly set parsed map for test.
Map<Object, Integer> map = new HashMap<Object, Integer>();
map.put(valueB, thresholdB);
map.put(valueD, thresholdD);
rule.setParsedHotItems(map);
ParameterMetric metric = mock(ParameterMetric.class);
when(metric.getThreadCount(paramIdx, valueA)).thenReturn(globalThreshold - 1);
when(metric.getThreadCount(paramIdx, valueB)).thenReturn(globalThreshold - 1);
when(metric.getThreadCount(paramIdx, valueC)).thenReturn(globalThreshold - 1);
when(metric.getThreadCount(paramIdx, valueD)).thenReturn(globalThreshold + 1);
ParameterMetricStorage.getMetricsMap().put(resourceWrapper.getName(), metric);
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueB));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueC));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueD));
when(metric.getThreadCount(paramIdx, valueA)).thenReturn(globalThreshold);
when(metric.getThreadCount(paramIdx, valueB)).thenReturn(thresholdB - 1L);
when(metric.getThreadCount(paramIdx, valueC)).thenReturn(globalThreshold + 1);
when(metric.getThreadCount(paramIdx, valueD)).thenReturn(globalThreshold - 1).thenReturn((long) thresholdD);
assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueB));
assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueC));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueD));
assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueD));
}
@Test
public void testPassLocalCheckForCollection() throws InterruptedException {
final String resourceName = "testPassLocalCheckForCollection";
final ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN);
int paramIdx = 0;
double globalThreshold = 1;
ParamFlowRule rule = new ParamFlowRule(resourceName).setParamIdx(paramIdx).setCount(globalThreshold);
String v1 = "a", v2 = "B", v3 = "Cc";
List<String> list = Arrays.asList(v1, v2, v3);
ParameterMetric metric = new ParameterMetric();
ParameterMetricStorage.getMetricsMap().put(resourceWrapper.getName(), metric);
metric.getRuleTimeCounterMap().put(rule, new ConcurrentLinkedHashMapWrapper<Object, AtomicLong>(4000));
metric.getRuleTokenCounterMap().put(rule, new ConcurrentLinkedHashMapWrapper<Object, AtomicLong>(4000));
assertTrue(ParamFlowChecker.passCheck(resourceWrapper, rule, 1, list));
assertFalse(ParamFlowChecker.passCheck(resourceWrapper, rule, 1, list));
}
@Test
public void testPassLocalCheckForArray() throws InterruptedException {
final String resourceName = "testPassLocalCheckForArray";
final ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN);
int paramIdx = 0;
double globalThreshold = 1;
ParamFlowRule rule = new ParamFlowRule(resourceName).setParamIdx(paramIdx)
.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER).setCount(globalThreshold);
TimeUtil.currentTimeMillis();
String v1 = "a", v2 = "B", v3 = "Cc";
Object arr = new String[]{v1, v2, v3};
ParameterMetric metric = new ParameterMetric();
ParameterMetricStorage.getMetricsMap().put(resourceWrapper.getName(), metric);
metric.getRuleTimeCounterMap().put(rule, new ConcurrentLinkedHashMapWrapper<Object, AtomicLong>(4000));
assertTrue(ParamFlowChecker.passCheck(resourceWrapper, rule, 1, arr));
assertFalse(ParamFlowChecker.passCheck(resourceWrapper, rule, 1, arr));
}
@Test
public void testPassLocalCheckForComplexParam() throws InterruptedException {
class User implements ParamFlowArgument {
Integer id;
String name;
String address;
public User(Integer id, String name, String address) {
this.id = id;
this.name = name;
this.address = address;
}
@Override
public Object paramFlowKey() {
return name;
}
}
final String resourceName = "testPassLocalCheckForComplexParam";
final ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN);
int paramIdx = 0;
double globalThreshold = 1;
ParamFlowRule rule = new ParamFlowRule(resourceName).setParamIdx(paramIdx).setCount(globalThreshold);
Object[] args = new Object[]{new User(1, "Bob", "Hangzhou"), 10, "Demo"};
ParameterMetric metric = new ParameterMetric();
ParameterMetricStorage.getMetricsMap().put(resourceWrapper.getName(), metric);
metric.getRuleTimeCounterMap().put(rule, new ConcurrentLinkedHashMapWrapper<Object, AtomicLong>(4000));
metric.getRuleTokenCounterMap().put(rule, new ConcurrentLinkedHashMapWrapper<Object, AtomicLong>(4000));
assertTrue(ParamFlowChecker.passCheck(resourceWrapper, rule, 1, args));
assertFalse(ParamFlowChecker.passCheck(resourceWrapper, rule, 1, args));
}
@Before
public void setUp() throws Exception {
ParameterMetricStorage.getMetricsMap().clear();
}
@After
public void tearDown() throws Exception {
ParameterMetricStorage.getMetricsMap().clear();
}
}

View File

@@ -0,0 +1,335 @@
/*
* Copyright 1999-2019 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.slots.block.flow.param;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper;
import com.alibaba.csp.sentinel.slots.statistic.cache.ConcurrentLinkedHashMapWrapper;
import com.alibaba.csp.sentinel.test.AbstractTimeBasedTest;
import com.alibaba.csp.sentinel.util.TimeUtil;
/**
* @author jialiang.linjl
* @author Eric Zhao
*/
public class ParamFlowDefaultCheckerTest extends AbstractTimeBasedTest {
@Test
public void testCheckQpsWithLongIntervalAndHighThreshold() {
// This test case is intended to avoid number overflow.
final String resourceName = "testCheckQpsWithLongIntervalAndHighThreshold";
final ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN);
int paramIdx = 0;
// Set a large threshold.
long threshold = 25000L;
ParamFlowRule rule = new ParamFlowRule(resourceName)
.setCount(threshold)
.setParamIdx(paramIdx);
String valueA = "valueA";
ParameterMetric metric = new ParameterMetric();
ParameterMetricStorage.getMetricsMap().put(resourceWrapper.getName(), metric);
metric.getRuleTimeCounterMap().put(rule, new ConcurrentLinkedHashMapWrapper<Object, AtomicLong>(4000));
metric.getRuleTokenCounterMap().put(rule,
new ConcurrentLinkedHashMapWrapper<Object, AtomicLong>(4000));
// We mock the time directly to avoid unstable behaviour.
setCurrentMillis(System.currentTimeMillis());
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
// 24 hours passed.
// This can make `toAddCount` larger that Integer.MAX_VALUE.
sleep(1000 * 60 * 60 * 24);
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
// 48 hours passed.
sleep(1000 * 60 * 60 * 48);
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
}
@Test
public void testParamFlowDefaultCheckSingleQps() {
final String resourceName = "testParamFlowDefaultCheckSingleQps";
final ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN);
int paramIdx = 0;
long threshold = 5L;
ParamFlowRule rule = new ParamFlowRule();
rule.setResource(resourceName);
rule.setCount(threshold);
rule.setParamIdx(paramIdx);
String valueA = "valueA";
ParameterMetric metric = new ParameterMetric();
ParameterMetricStorage.getMetricsMap().put(resourceWrapper.getName(), metric);
metric.getRuleTimeCounterMap().put(rule, new ConcurrentLinkedHashMapWrapper<Object, AtomicLong>(4000));
metric.getRuleTokenCounterMap().put(rule,
new ConcurrentLinkedHashMapWrapper<Object, AtomicLong>(4000));
// We mock the time directly to avoid unstable behaviour.
setCurrentMillis(System.currentTimeMillis());
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
sleep(3000);
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
}
@Test
public void testParamFlowDefaultCheckSingleQpsWithBurst() throws InterruptedException {
final String resourceName = "testParamFlowDefaultCheckSingleQpsWithBurst";
final ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN);
int paramIdx = 0;
long threshold = 5L;
ParamFlowRule rule = new ParamFlowRule();
rule.setResource(resourceName);
rule.setCount(threshold);
rule.setParamIdx(paramIdx);
rule.setBurstCount(3);
String valueA = "valueA";
ParameterMetric metric = new ParameterMetric();
ParameterMetricStorage.getMetricsMap().put(resourceWrapper.getName(), metric);
metric.getRuleTimeCounterMap().put(rule, new ConcurrentLinkedHashMapWrapper<Object, AtomicLong>(4000));
metric.getRuleTokenCounterMap().put(rule,
new ConcurrentLinkedHashMapWrapper<Object, AtomicLong>(4000));
// We mock the time directly to avoid unstable behaviour.
setCurrentMillis(System.currentTimeMillis());
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
sleep(1002);
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
sleep(1002);
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
sleep(2000);
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
sleep(1002);
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
}
@Test
public void testParamFlowDefaultCheckQpsInDifferentDuration() throws InterruptedException {
final String resourceName = "testParamFlowDefaultCheckQpsInDifferentDuration";
final ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN);
int paramIdx = 0;
long threshold = 5L;
ParamFlowRule rule = new ParamFlowRule();
rule.setResource(resourceName);
rule.setCount(threshold);
rule.setParamIdx(paramIdx);
rule.setDurationInSec(60);
String valueA = "helloWorld";
ParameterMetric metric = new ParameterMetric();
ParameterMetricStorage.getMetricsMap().put(resourceWrapper.getName(), metric);
metric.getRuleTimeCounterMap().put(rule, new ConcurrentLinkedHashMapWrapper<Object, AtomicLong>(4000));
metric.getRuleTokenCounterMap().put(rule,
new ConcurrentLinkedHashMapWrapper<Object, AtomicLong>(4000));
// We mock the time directly to avoid unstable behaviour.
setCurrentMillis(System.currentTimeMillis());
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
sleepSecond(1);
assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
sleepSecond(10);
assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
sleepSecond(30);
assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
sleepSecond(30);
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
}
@Test
public void testParamFlowDefaultCheckSingleValueCheckQpsMultipleThreads() throws Exception {
// In this test case we use the actual time.
useActualTime();
final String resourceName = "testParamFlowDefaultCheckSingleValueCheckQpsMultipleThreads";
final ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN);
int paramIdx = 0;
long threshold = 5L;
final ParamFlowRule rule = new ParamFlowRule();
rule.setResource(resourceName);
rule.setCount(threshold);
rule.setParamIdx(paramIdx);
final String valueA = "valueA";
ParameterMetric metric = new ParameterMetric();
ParameterMetricStorage.getMetricsMap().put(resourceWrapper.getName(), metric);
metric.getRuleTimeCounterMap().put(rule, new ConcurrentLinkedHashMapWrapper<Object, AtomicLong>(4000));
metric.getRuleTokenCounterMap().put(rule,
new ConcurrentLinkedHashMapWrapper<Object, AtomicLong>(4000));
int threadCount = 40;
final CountDownLatch waitLatch = new CountDownLatch(threadCount);
final AtomicInteger successCount = new AtomicInteger();
for (int i = 0; i < threadCount; i++) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
if (ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)) {
successCount.incrementAndGet();
}
waitLatch.countDown();
}
});
t.setName("sentinel-simulate-traffic-task-" + i);
t.start();
}
waitLatch.await();
assertEquals(successCount.get(), threshold);
successCount.set(0);
System.out.println("testParamFlowDefaultCheckSingleValueCheckQpsMultipleThreads: sleep for 3 seconds");
TimeUnit.SECONDS.sleep(3);
successCount.set(0);
final CountDownLatch waitLatch1 = new CountDownLatch(threadCount);
final long currentTime = TimeUtil.currentTimeMillis();
final long endTime = currentTime + rule.getDurationInSec() * 1000 - 1;
for (int i = 0; i < threadCount; i++) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
long currentTime1 = currentTime;
while (currentTime1 <= endTime) {
if (ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)) {
successCount.incrementAndGet();
}
try {
TimeUnit.MILLISECONDS.sleep(ThreadLocalRandom.current().nextInt(20));
} catch (InterruptedException e) {
e.printStackTrace();
}
currentTime1 = TimeUtil.currentTimeMillis();
}
waitLatch1.countDown();
}
});
t.setName("sentinel-simulate-traffic-task-" + i);
t.start();
}
waitLatch1.await();
assertEquals(successCount.get(), threshold);
}
@Before
public void setUp() throws Exception {
ParameterMetricStorage.getMetricsMap().clear();
}
@After
public void tearDown() throws Exception {
ParameterMetricStorage.getMetricsMap().clear();
}
}

View File

@@ -0,0 +1,155 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.slots.block.flow.param;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Test cases for {@link ParamFlowRuleManager}.
*
* @author Eric Zhao
* @since 0.2.0
*/
public class ParamFlowRuleManagerTest {
@Before
public void setUp() {
ParamFlowRuleManager.loadRules(null);
ParameterMetricStorage.getMetricsMap().clear();
}
@After
public void tearDown() {
ParamFlowRuleManager.loadRules(null);
ParameterMetricStorage.getMetricsMap().clear();
}
@Test
public void testLoadParamRulesClearingUnusedMetrics() {
final String resA = "resA";
ParamFlowRule ruleA = new ParamFlowRule(resA)
.setCount(1)
.setParamIdx(0);
ParamFlowRuleManager.loadRules(Collections.singletonList(ruleA));
ParameterMetricStorage.getMetricsMap().put(resA, new ParameterMetric());
assertNotNull(ParameterMetricStorage.getParamMetricForResource(resA));
final String resB = "resB";
ParamFlowRule ruleB = new ParamFlowRule(resB)
.setCount(2)
.setParamIdx(1);
ParamFlowRuleManager.loadRules(Collections.singletonList(ruleB));
assertNull("The unused hot param metric should be cleared",
ParameterMetricStorage.getParamMetricForResource(resA));
}
@Test
public void testLoadParamRulesClearingUnusedMetricsForRule() {
final String resA = "resA";
ParamFlowRule ruleA1 = new ParamFlowRule(resA)
.setCount(1)
.setParamIdx(0);
ParamFlowRule ruleA2 = new ParamFlowRule(resA)
.setCount(2)
.setParamIdx(1);
ParamFlowRuleManager.loadRules(Arrays.asList(ruleA1, ruleA2));
ParameterMetric metric = new ParameterMetric();
metric.initialize(ruleA1);
metric.initialize(ruleA2);
ParameterMetricStorage.getMetricsMap().put(resA, metric);
ParameterMetric metric1 = ParameterMetricStorage.getParamMetricForResource(resA);
assertNotNull(metric1);
assertNotNull(metric1.getRuleTimeCounter(ruleA1));
assertNotNull(metric1.getRuleTimeCounter(ruleA2));
ParamFlowRuleManager.loadRules(Arrays.asList(ruleA1));
ParameterMetric metric2 = ParameterMetricStorage.getParamMetricForResource(resA);
assertNotNull(metric2);
assertNotNull(metric2.getRuleTimeCounter(ruleA1));
assertNull(metric2.getRuleTimeCounter(ruleA2));
}
@Test
public void testLoadParamRulesAndGet() {
final String resA = "abc";
final String resB = "foo";
final String resC = "baz";
// Rule A to C is for resource A.
// Rule A is invalid.
ParamFlowRule ruleA = new ParamFlowRule(resA).setCount(10);
ParamFlowRule ruleB = new ParamFlowRule(resA)
.setCount(28)
.setParamIdx(1);
ParamFlowRule ruleC = new ParamFlowRule(resA)
.setCount(8)
.setParamIdx(1)
.setGrade(RuleConstant.FLOW_GRADE_THREAD);
// Rule D is for resource B.
ParamFlowRule ruleD = new ParamFlowRule(resB)
.setCount(9)
.setParamIdx(0)
.setParamFlowItemList(Arrays.asList(ParamFlowItem.newItem(7L, 6), ParamFlowItem.newItem(9L, 4)));
ParamFlowRuleManager.loadRules(Arrays.asList(ruleA, ruleB, ruleC, ruleD));
// Test for ParamFlowRuleManager#hasRules
assertTrue(ParamFlowRuleManager.hasRules(resA));
assertTrue(ParamFlowRuleManager.hasRules(resB));
assertFalse(ParamFlowRuleManager.hasRules(resC));
// Test for ParamFlowRuleManager#getRulesOfResource
List<ParamFlowRule> rulesForResA = ParamFlowRuleManager.getRulesOfResource(resA);
assertEquals(2, rulesForResA.size());
assertFalse(rulesForResA.contains(ruleA));
assertTrue(rulesForResA.contains(ruleB));
assertTrue(rulesForResA.contains(ruleC));
List<ParamFlowRule> rulesForResB = ParamFlowRuleManager.getRulesOfResource(resB);
assertEquals(1, rulesForResB.size());
assertEquals(ruleD, rulesForResB.get(0));
// Test for ParamFlowRuleManager#getRules
List<ParamFlowRule> allRules = ParamFlowRuleManager.getRules();
assertFalse(allRules.contains(ruleA));
assertTrue(allRules.contains(ruleB));
assertTrue(allRules.contains(ruleC));
assertTrue(allRules.contains(ruleD));
}
@Test
public void testLoadParamRulesWithNoMetric() {
String resource = "test";
ParamFlowRule paramFlowRule = new ParamFlowRule(resource)
.setDurationInSec(1).setParamIdx(1);
ParamFlowRuleManager.loadRules(Collections.singletonList(paramFlowRule));
ParamFlowRule newParamFlowRule = new ParamFlowRule(resource)
.setDurationInSec(2).setParamIdx(1);
ParamFlowRuleManager.loadRules(Collections.singletonList(newParamFlowRule));
List<ParamFlowRule> result = ParamFlowRuleManager.getRulesOfResource(resource);
assertEquals(1, result.size());
assertEquals(2, result.get(0).getDurationInSec());
}
}

View File

@@ -0,0 +1,95 @@
package com.alibaba.csp.sentinel.slots.block.flow.param;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* @author Eric Zhao
*/
public class ParamFlowRuleUtilTest {
@Test
public void testCheckValidHotParamRule() {
// Null or empty resource;
ParamFlowRule rule1 = new ParamFlowRule();
ParamFlowRule rule2 = new ParamFlowRule("");
assertFalse(ParamFlowRuleUtil.isValidRule(null));
assertFalse(ParamFlowRuleUtil.isValidRule(rule1));
assertFalse(ParamFlowRuleUtil.isValidRule(rule2));
// Invalid threshold count.
ParamFlowRule rule3 = new ParamFlowRule("abc")
.setCount(-1)
.setParamIdx(1);
assertFalse(ParamFlowRuleUtil.isValidRule(rule3));
// Parameter index not set or invalid.
ParamFlowRule rule4 = new ParamFlowRule("abc")
.setCount(1);
ParamFlowRule rule5 = new ParamFlowRule("abc")
.setCount(1)
.setParamIdx(-1);
assertFalse(ParamFlowRuleUtil.isValidRule(rule4));
assertTrue(ParamFlowRuleUtil.isValidRule(rule5));
ParamFlowRule goodRule = new ParamFlowRule("abc")
.setCount(10)
.setParamIdx(1);
assertTrue(ParamFlowRuleUtil.isValidRule(goodRule));
}
@Test
public void testParseHotParamExceptionItemsFailure() {
String valueB = "Sentinel";
Integer valueC = 6;
char valueD = 6;
float valueE = 11.11f;
// Null object will not be parsed.
ParamFlowItem itemA = new ParamFlowItem(null, 1, double.class.getName());
// Hot item with empty class type will be treated as string.
ParamFlowItem itemB = new ParamFlowItem(valueB, 3, null);
ParamFlowItem itemE = new ParamFlowItem(String.valueOf(valueE), 3, "");
// Bad count will not be parsed.
ParamFlowItem itemC = ParamFlowItem.newItem(valueC, -5);
ParamFlowItem itemD = new ParamFlowItem(String.valueOf(valueD), null, char.class.getName());
List<ParamFlowItem> badItems = Arrays.asList(itemA, itemB, itemC, itemD, itemE);
Map<Object, Integer> parsedItems = ParamFlowRuleUtil.parseHotItems(badItems);
// Value B and E will be parsed, but ignoring the type.
assertEquals(2, parsedItems.size());
assertEquals(itemB.getCount(), parsedItems.get(valueB));
assertFalse(parsedItems.containsKey(valueE));
assertEquals(itemE.getCount(), parsedItems.get(String.valueOf(valueE)));
}
@Test
public void testParseHotParamExceptionItemsSuccess() {
// Test for empty list.
assertEquals(0, ParamFlowRuleUtil.parseHotItems(null).size());
assertEquals(0, ParamFlowRuleUtil.parseHotItems(new ArrayList<ParamFlowItem>()).size());
// Test for boxing objects and primitive types.
Double valueA = 1.1d;
String valueB = "Sentinel";
Integer valueC = 6;
char valueD = 'c';
ParamFlowItem itemA = ParamFlowItem.newItem(valueA, 1);
ParamFlowItem itemB = ParamFlowItem.newItem(valueB, 3);
ParamFlowItem itemC = ParamFlowItem.newItem(valueC, 5);
ParamFlowItem itemD = new ParamFlowItem().setObject(String.valueOf(valueD))
.setClassType(char.class.getName())
.setCount(7);
List<ParamFlowItem> items = Arrays.asList(itemA, itemB, itemC, itemD);
Map<Object, Integer> parsedItems = ParamFlowRuleUtil.parseHotItems(items);
assertEquals(itemA.getCount(), parsedItems.get(valueA));
assertEquals(itemB.getCount(), parsedItems.get(valueB));
assertEquals(itemC.getCount(), parsedItems.get(valueC));
assertEquals(itemD.getCount(), parsedItems.get(valueD));
}
}

View File

@@ -0,0 +1,136 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.slots.block.flow.param;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper;
import com.alibaba.csp.sentinel.slots.statistic.cache.CacheMap;
import com.alibaba.csp.sentinel.slots.statistic.cache.ConcurrentLinkedHashMapWrapper;
import com.alibaba.csp.sentinel.util.TimeUtil;
/**
* Test cases for {@link ParamFlowSlot}.
*
* @author Eric Zhao
* @since 0.2.0
*/
public class ParamFlowSlotTest {
private final ParamFlowSlot paramFlowSlot = new ParamFlowSlot();
@Test
public void testNegativeParamIdx() throws Throwable {
String resourceName = "testNegativeParamIdx";
ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN);
ParamFlowRule rule = new ParamFlowRule(resourceName)
.setCount(1)
.setParamIdx(-1);
ParamFlowRuleManager.loadRules(Collections.singletonList(rule));
paramFlowSlot.entry(null, resourceWrapper, null, 1, false, "abc", "def", "ghi");
assertEquals(2, rule.getParamIdx().longValue());
rule.setParamIdx(-1);
ParamFlowRuleManager.loadRules(Collections.singletonList(rule));
paramFlowSlot.entry(null, resourceWrapper, null, 1, false, null);
// Null args will not trigger conversion.
assertEquals(-1, rule.getParamIdx().intValue());
rule.setParamIdx(-100);
ParamFlowRuleManager.loadRules(Collections.singletonList(rule));
paramFlowSlot.entry(null, resourceWrapper, null, 1, false, "abc", "def", "ghi");
assertEquals(100, rule.getParamIdx().longValue());
rule.setParamIdx(0);
ParamFlowRuleManager.loadRules(Collections.singletonList(rule));
paramFlowSlot.entry(null, resourceWrapper, null, 1, false, "abc", "def", "ghi");
assertEquals(0, rule.getParamIdx().longValue());
}
@Test
public void testEntryWhenParamFlowRuleNotExists() throws Throwable {
String resourceName = "testEntryWhenParamFlowRuleNotExists";
ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN);
paramFlowSlot.entry(null, resourceWrapper, null, 1, false, "abc");
// The parameter metric instance will not be created.
assertNull(ParameterMetricStorage.getParamMetric(resourceWrapper));
}
@Test
public void testEntryWhenParamFlowExists() throws Throwable {
String resourceName = "testEntryWhenParamFlowExists";
ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN);
long argToGo = 1L;
double count = 1;
ParamFlowRule rule = new ParamFlowRule(resourceName)
.setCount(count)
.setBurstCount(0)
.setParamIdx(0);
ParamFlowRuleManager.loadRules(Collections.singletonList(rule));
ParameterMetric metric = mock(ParameterMetric.class);
CacheMap<Object, AtomicLong> map = new ConcurrentLinkedHashMapWrapper<>(4000);
CacheMap<Object, AtomicLong> map2 = new ConcurrentLinkedHashMapWrapper<>(4000);
when(metric.getRuleTimeCounter(rule)).thenReturn(map);
when(metric.getRuleTokenCounter(rule)).thenReturn(map2);
map.put(argToGo, new AtomicLong(TimeUtil.currentTimeMillis()));
// Insert the mock metric to control pass or block.
ParameterMetricStorage.getMetricsMap().put(resourceWrapper.getName(), metric);
// The first entry will pass.
paramFlowSlot.entry(null, resourceWrapper, null, 1, false, argToGo);
// The second entry will be blocked.
try {
paramFlowSlot.entry(null, resourceWrapper, null, 1, false, argToGo);
} catch (ParamFlowException ex) {
assertEquals(String.valueOf(argToGo), ex.getMessage());
assertEquals(resourceName, ex.getResourceName());
return;
}
fail("The second entry should be blocked");
}
@Before
public void setUp() {
ParamFlowRuleManager.loadRules(null);
ParameterMetricStorage.getMetricsMap().clear();
}
@After
public void tearDown() {
// Clean the metrics map.
ParamFlowRuleManager.loadRules(null);
ParameterMetricStorage.getMetricsMap().clear();
}
}

View File

@@ -0,0 +1,179 @@
/*
* Copyright 1999-2019 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.slots.block.flow.param;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.statistic.cache.ConcurrentLinkedHashMapWrapper;
import com.alibaba.csp.sentinel.util.TimeUtil;
import static org.junit.Assert.assertEquals;
/**
* @author jialiang.linjl
*/
public class ParamFlowThrottleRateLimitingCheckerTest {
@Test
public void testSingleValueThrottleCheckQps() throws Exception {
final String resourceName = "testSingleValueThrottleCheckQps";
final ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN);
int paramIdx = 0;
TimeUtil.currentTimeMillis();
long threshold = 5L;
ParamFlowRule rule = new ParamFlowRule();
rule.setResource(resourceName);
rule.setCount(threshold);
rule.setParamIdx(paramIdx);
rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER);
String valueA = "valueA";
ParameterMetric metric = new ParameterMetric();
ParameterMetricStorage.getMetricsMap().put(resourceWrapper.getName(), metric);
metric.getRuleTimeCounterMap().put(rule, new ConcurrentLinkedHashMapWrapper<Object, AtomicLong>(4000));
long currentTime = TimeUtil.currentTimeMillis();
long endTime = currentTime + rule.getDurationInSec() * 1000;
int successCount = 0;
while (currentTime <= endTime - 10) {
if (ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)) {
successCount++;
}
currentTime = TimeUtil.currentTimeMillis();
}
assertEquals(successCount, threshold);
System.out.println("testSingleValueThrottleCheckQps: sleep for 3 seconds");
TimeUnit.SECONDS.sleep(3);
currentTime = TimeUtil.currentTimeMillis();
endTime = currentTime + rule.getDurationInSec() * 1000;
successCount = 0;
while (currentTime <= endTime - 10) {
if (ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)) {
successCount++;
}
currentTime = TimeUtil.currentTimeMillis();
}
assertEquals(successCount, threshold);
}
@Test
public void testSingleValueThrottleCheckQpsMultipleThreads() throws Exception {
final String resourceName = "testSingleValueThrottleCheckQpsMultipleThreads";
final ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN);
int paramIdx = 0;
long threshold = 5L;
final ParamFlowRule rule = new ParamFlowRule(resourceName)
.setCount(threshold)
.setParamIdx(paramIdx)
.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER);
final String valueA = "valueA";
ParameterMetric metric = new ParameterMetric();
ParameterMetricStorage.getMetricsMap().put(resourceWrapper.getName(), metric);
metric.getRuleTimeCounterMap().put(rule, new ConcurrentLinkedHashMapWrapper<Object, AtomicLong>(4000));
int threadCount = 40;
System.out.println(metric.getRuleTimeCounter(rule));
final CountDownLatch waitLatch = new CountDownLatch(threadCount);
final AtomicInteger successCount = new AtomicInteger();
for (int i = 0; i < threadCount; i++) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
if (ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)) {
successCount.incrementAndGet();
}
waitLatch.countDown();
}
});
t.setName("sentinel-simulate-traffic-task-" + i);
t.start();
}
waitLatch.await();
assertEquals(successCount.get(), 1);
System.out.println(threadCount);
successCount.set(0);
System.out.println("testSingleValueThrottleCheckQpsMultipleThreads: sleep for 3 seconds");
TimeUnit.SECONDS.sleep(3);
successCount.set(0);
final CountDownLatch waitLatch1 = new CountDownLatch(threadCount);
final long currentTime = TimeUtil.currentTimeMillis();
final long endTime = currentTime + rule.getDurationInSec() * 1000 - 1;
for (int i = 0; i < threadCount; i++) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
long currentTime1 = currentTime;
while (currentTime1 <= endTime) {
if (ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)) {
successCount.incrementAndGet();
}
Random random = new Random();
try {
TimeUnit.MILLISECONDS.sleep(random.nextInt(20));
} catch (InterruptedException e) {
e.printStackTrace();
}
currentTime1 = TimeUtil.currentTimeMillis();
}
waitLatch1.countDown();
}
});
t.setName("sentinel-simulate-traffic-task-" + i);
t.start();
}
waitLatch1.await();
assertEquals(successCount.get(), threshold);
}
@Before
public void setUp() throws Exception {
ParameterMetricStorage.getMetricsMap().clear();
}
@After
public void tearDown() throws Exception {
ParameterMetricStorage.getMetricsMap().clear();
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 1999-2019 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.slots.block.flow.param;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* @author Eric Zhao
*/
public class ParameterMetricStorageTest {
@Test
public void testGetNullParamMetric() {
assertNull(ParameterMetricStorage.getParamMetric(null));
}
@Test
public void testInitParamMetrics() {
ParamFlowRule rule = new ParamFlowRule();
rule.setParamIdx(1);
int index = 1;
String resourceName = "res-" + System.currentTimeMillis();
ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN);
assertNull(ParameterMetricStorage.getParamMetric(resourceWrapper));
ParameterMetricStorage.initParamMetricsFor(resourceWrapper, rule);
ParameterMetric metric = ParameterMetricStorage.getParamMetric(resourceWrapper);
assertNotNull(metric);
assertNotNull(metric.getRuleTimeCounterMap().get(rule));
assertNotNull(metric.getThreadCountMap().get(index));
// Duplicate init.
ParameterMetricStorage.initParamMetricsFor(resourceWrapper, rule);
assertSame(metric, ParameterMetricStorage.getParamMetric(resourceWrapper));
ParamFlowRule rule2 = new ParamFlowRule();
rule2.setParamIdx(1);
assertSame(metric, ParameterMetricStorage.getParamMetric(resourceWrapper));
}
@Before
public void setUp() {
ParameterMetricStorage.getMetricsMap().clear();
}
@After
public void tearDown() {
ParameterMetricStorage.getMetricsMap().clear();
}
}

View File

@@ -0,0 +1,187 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.slots.block.flow.param;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.junit.Test;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.statistic.cache.CacheMap;
/**
* Test cases for {@link ParameterMetric}.
*
* @author Eric Zhao
* @since 0.2.0
*/
public class ParameterMetricTest {
@Test
public void testInitAndClearParameterMetric() {
// Create a parameter metric for resource "abc".
ParameterMetric metric = new ParameterMetric();
ParamFlowRule rule = new ParamFlowRule("abc")
.setParamIdx(1);
metric.initialize(rule);
CacheMap<Object, AtomicInteger> threadCountMap = metric.getThreadCountMap().get(rule.getParamIdx());
assertNotNull(threadCountMap);
CacheMap<Object, AtomicLong> timeRecordMap = metric.getRuleTimeCounter(rule);
assertNotNull(timeRecordMap);
metric.initialize(rule);
assertSame(threadCountMap, metric.getThreadCountMap().get(rule.getParamIdx()));
assertSame(timeRecordMap, metric.getRuleTimeCounter(rule));
ParamFlowRule rule2 = new ParamFlowRule("abc")
.setParamIdx(1);
metric.initialize(rule2);
CacheMap<Object, AtomicLong> timeRecordMap2 = metric.getRuleTimeCounter(rule2);
assertSame(timeRecordMap, timeRecordMap2);
rule2.setParamIdx(2);
metric.initialize(rule2);
assertNotSame(timeRecordMap2, metric.getRuleTimeCounter(rule2));
ParamFlowRule rule3 = new ParamFlowRule("abc")
.setParamIdx(1)
.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER);
metric.initialize(rule3);
assertNotSame(timeRecordMap, metric.getRuleTimeCounter(rule3));
metric.clear();
assertEquals(0, metric.getThreadCountMap().size());
assertEquals(0, metric.getRuleTimeCounterMap().size());
assertEquals(0, metric.getRuleTokenCounterMap().size());
}
@Test
public void testAddAndDecreaseThreadCountCommon() {
testAddAndDecreaseThreadCount(PARAM_TYPE_NORMAL);
testAddAndDecreaseThreadCount(PARAM_TYPE_ARRAY);
testAddAndDecreaseThreadCount(PARAM_TYPE_COLLECTION);
}
private void testAddAndDecreaseThreadCount(int paramType) {
ParamFlowRule rule = new ParamFlowRule();
rule.setParamIdx(0);
int n = 3;
long[] v = new long[] {19L, 3L, 8L};
ParameterMetric metric = new ParameterMetric();
metric.initialize(rule);
assertTrue(metric.getThreadCountMap().containsKey(rule.getParamIdx()));
switch (paramType) {
case PARAM_TYPE_ARRAY:
metric.addThreadCount((Object)v);
break;
case PARAM_TYPE_COLLECTION:
metric.addThreadCount(Arrays.asList(v[0], v[1], v[2]));
break;
case PARAM_TYPE_NORMAL:
default:
metric.addThreadCount(v[0]);
metric.addThreadCount(v[1]);
metric.addThreadCount(v[2]);
break;
}
assertEquals(1, metric.getThreadCountMap().size());
CacheMap<Object, AtomicInteger> threadCountMap = metric.getThreadCountMap().get(rule.getParamIdx());
assertEquals(v.length, threadCountMap.size());
for (long vs : v) {
assertEquals(1, threadCountMap.get(vs).get());
}
for (int i = 1; i < n; i++) {
switch (paramType) {
case PARAM_TYPE_ARRAY:
metric.addThreadCount((Object)v);
break;
case PARAM_TYPE_COLLECTION:
metric.addThreadCount(Arrays.asList(v[0], v[1], v[2]));
break;
case PARAM_TYPE_NORMAL:
default:
metric.addThreadCount(v[0]);
metric.addThreadCount(v[1]);
metric.addThreadCount(v[2]);
break;
}
}
assertEquals(1, metric.getThreadCountMap().size());
threadCountMap = metric.getThreadCountMap().get(rule.getParamIdx());
assertEquals(v.length, threadCountMap.size());
for (long vs : v) {
assertEquals(n, threadCountMap.get(vs).get());
}
for (int i = 1; i < n; i++) {
switch (paramType) {
case PARAM_TYPE_ARRAY:
metric.decreaseThreadCount((Object)v);
break;
case PARAM_TYPE_COLLECTION:
metric.decreaseThreadCount(Arrays.asList(v[0], v[1], v[2]));
break;
case PARAM_TYPE_NORMAL:
default:
metric.decreaseThreadCount(v[0]);
metric.decreaseThreadCount(v[1]);
metric.decreaseThreadCount(v[2]);
break;
}
}
assertEquals(1, metric.getThreadCountMap().size());
threadCountMap = metric.getThreadCountMap().get(rule.getParamIdx());
assertEquals(v.length, threadCountMap.size());
for (long vs : v) {
assertEquals(1, threadCountMap.get(vs).get());
}
switch (paramType) {
case PARAM_TYPE_ARRAY:
metric.decreaseThreadCount((Object)v);
break;
case PARAM_TYPE_COLLECTION:
metric.decreaseThreadCount(Arrays.asList(v[0], v[1], v[2]));
break;
case PARAM_TYPE_NORMAL:
default:
metric.decreaseThreadCount(v[0]);
metric.decreaseThreadCount(v[1]);
metric.decreaseThreadCount(v[2]);
break;
}
assertEquals(1, metric.getThreadCountMap().size());
threadCountMap = metric.getThreadCountMap().get(rule.getParamIdx());
assertEquals(0, threadCountMap.size());
}
private static final int PARAM_TYPE_NORMAL = 0;
private static final int PARAM_TYPE_ARRAY = 1;
private static final int PARAM_TYPE_COLLECTION = 2;
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.slots.statistic.data;
import com.alibaba.csp.sentinel.slots.block.flow.param.RollingParamEvent;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Test cases for {@link ParamMapBucket}.
*
* @author Eric Zhao
* @since 0.2.0
*/
public class ParamMapBucketTest {
@Test
public void testAddEviction() {
ParamMapBucket bucket = new ParamMapBucket();
for (int i = 0; i < ParamMapBucket.DEFAULT_MAX_CAPACITY; i++) {
bucket.add(RollingParamEvent.REQUEST_PASSED, 1, "param-" + i);
}
String lastParam = "param-end";
bucket.add(RollingParamEvent.REQUEST_PASSED, 1, lastParam);
assertEquals(0, bucket.get(RollingParamEvent.REQUEST_PASSED, "param-0"));
assertEquals(1, bucket.get(RollingParamEvent.REQUEST_PASSED, "param-1"));
assertEquals(1, bucket.get(RollingParamEvent.REQUEST_PASSED, lastParam));
}
@Test
public void testAddGetResetCommon() {
ParamMapBucket bucket = new ParamMapBucket();
double paramA = 1.1d;
double paramB = 2.2d;
double paramC = -19.7d;
// Block: A 5 | B 1 | C 6
// Pass: A 0 | B 1 | C 7
bucket.add(RollingParamEvent.REQUEST_BLOCKED, 3, paramA);
bucket.add(RollingParamEvent.REQUEST_PASSED, 1, paramB);
bucket.add(RollingParamEvent.REQUEST_BLOCKED, 1, paramB);
bucket.add(RollingParamEvent.REQUEST_BLOCKED, 2, paramA);
bucket.add(RollingParamEvent.REQUEST_PASSED, 6, paramC);
bucket.add(RollingParamEvent.REQUEST_BLOCKED, 4, paramC);
bucket.add(RollingParamEvent.REQUEST_PASSED, 1, paramC);
bucket.add(RollingParamEvent.REQUEST_BLOCKED, 2, paramC);
assertEquals(5, bucket.get(RollingParamEvent.REQUEST_BLOCKED, paramA));
assertEquals(1, bucket.get(RollingParamEvent.REQUEST_BLOCKED, paramB));
assertEquals(6, bucket.get(RollingParamEvent.REQUEST_BLOCKED, paramC));
assertEquals(0, bucket.get(RollingParamEvent.REQUEST_PASSED, paramA));
assertEquals(1, bucket.get(RollingParamEvent.REQUEST_PASSED, paramB));
assertEquals(7, bucket.get(RollingParamEvent.REQUEST_PASSED, paramC));
bucket.reset();
assertEquals(0, bucket.get(RollingParamEvent.REQUEST_BLOCKED, paramA));
assertEquals(0, bucket.get(RollingParamEvent.REQUEST_BLOCKED, paramB));
assertEquals(0, bucket.get(RollingParamEvent.REQUEST_BLOCKED, paramC));
assertEquals(0, bucket.get(RollingParamEvent.REQUEST_PASSED, paramA));
assertEquals(0, bucket.get(RollingParamEvent.REQUEST_PASSED, paramB));
assertEquals(0, bucket.get(RollingParamEvent.REQUEST_PASSED, paramC));
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.test;
import com.alibaba.csp.sentinel.util.TimeUtil;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
/**
* Mock support for {@link TimeUtil}
*
* @author jason
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest({TimeUtil.class})
public abstract class AbstractTimeBasedTest {
private long currentMillis = 0;
{
PowerMockito.mockStatic(TimeUtil.class);
PowerMockito.when(TimeUtil.currentTimeMillis()).thenReturn(currentMillis);
}
protected final void useActualTime() {
PowerMockito.when(TimeUtil.currentTimeMillis()).thenCallRealMethod();
}
protected final void setCurrentMillis(long cur) {
currentMillis = cur;
PowerMockito.when(TimeUtil.currentTimeMillis()).thenReturn(currentMillis);
}
protected final void sleep(int t) {
currentMillis += t;
PowerMockito.when(TimeUtil.currentTimeMillis()).thenReturn(currentMillis);
}
protected final void sleepSecond(int timeSec) {
sleep(timeSec * 1000);
}
}