Add flow control by frequent (hot spot) parameters (#156)
- Add callback registry for statistic slot for extensions. - Add a new module `sentinel-parameter-flow-control` under `sentinel-extension`. - Add a `CacheMap` interface to provide abstraction for cache. We use ConcurrentLinkedHashMap as the default implementation (LRU strategy).. - Add a `ParameterMetric` class as frequent parameter metrics for a specific resource. The metric map is located in `ParamFlowSlot` rather than `ClusterNode`. - Implement `ParameterLeapArray` as statistic data structure for frequent parameters in a period of time window. - Add `ParamFlowSlot` as the checker slot; Add `ParamFlowChecker` to do rule checking; Add `ParamFlowRuleManager` to do rule managing. - The statistic metrics for frequent parameters is enabled only if the related resource has configured parameter flow rule; Parameter metrics for removed rules will be cleared automatically. - Leverage extensible `SlotChainBuilder` to provide a `HotParamSlotChainBuilder`. - Add command handlers for hot param rules. - Add test cases and demo.
This commit is contained in:
parent
8c2cceca5e
commit
88a02623ac
10
pom.xml
10
pom.xml
|
|
@ -87,6 +87,11 @@
|
|||
<artifactId>sentinel-annotation-aspectj</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-parameter-flow-control</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-datasource-extension</artifactId>
|
||||
|
|
@ -112,6 +117,11 @@
|
|||
<artifactId>sentinel-transport-simple-http</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-transport-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-adapter</artifactId>
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ public interface ProcessorSlot<T> {
|
|||
* @param param Generics parameter, usually is a {@link com.alibaba.csp.sentinel.node.Node}
|
||||
* @param count tokens needed
|
||||
* @param args parameters of the original call
|
||||
* @throws Throwable
|
||||
* @throws Throwable blocked exception or unexpected error
|
||||
*/
|
||||
void entry(Context context, ResourceWrapper resourceWrapper, T param, int count, Object... args)
|
||||
throws Throwable;
|
||||
|
|
@ -44,10 +44,10 @@ public interface ProcessorSlot<T> {
|
|||
*
|
||||
* @param context current {@link Context}
|
||||
* @param resourceWrapper current resource
|
||||
* @param obj
|
||||
* @param obj relevant object (e.g. Node)
|
||||
* @param count tokens needed
|
||||
* @param args parameters of the original call
|
||||
* @throws Throwable
|
||||
* @throws Throwable blocked exception or unexpected error
|
||||
*/
|
||||
void fireEntry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, Object... args)
|
||||
throws Throwable;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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.slotchain;
|
||||
|
||||
import com.alibaba.csp.sentinel.context.Context;
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
|
||||
/**
|
||||
* Callback for entering {@link com.alibaba.csp.sentinel.slots.statistic.StatisticSlot} (passed and blocked).
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public interface ProcessorSlotEntryCallback<T> {
|
||||
|
||||
void onPass(Context context, ResourceWrapper resourceWrapper, T param, int count, Object... args) throws Exception;
|
||||
|
||||
void onBlocked(BlockException ex, Context context, ResourceWrapper resourceWrapper, T param, int count, Object... args);
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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.slotchain;
|
||||
|
||||
import com.alibaba.csp.sentinel.context.Context;
|
||||
|
||||
/**
|
||||
* Callback for exiting {@link com.alibaba.csp.sentinel.slots.statistic.StatisticSlot} (passed and blocked).
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public interface ProcessorSlotExitCallback {
|
||||
|
||||
void onExit(Context context, ResourceWrapper resourceWrapper, int count, Object... args);
|
||||
}
|
||||
|
|
@ -15,6 +15,10 @@
|
|||
*/
|
||||
package com.alibaba.csp.sentinel.slots.statistic;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import com.alibaba.csp.sentinel.slotchain.ProcessorSlotEntryCallback;
|
||||
import com.alibaba.csp.sentinel.slotchain.ProcessorSlotExitCallback;
|
||||
import com.alibaba.csp.sentinel.util.TimeUtil;
|
||||
import com.alibaba.csp.sentinel.Constants;
|
||||
import com.alibaba.csp.sentinel.EntryType;
|
||||
|
|
@ -39,13 +43,13 @@ import com.alibaba.csp.sentinel.slots.block.BlockException;
|
|||
* </p>
|
||||
*
|
||||
* @author jialiang.linjl
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public class StatisticSlot extends AbstractLinkedProcessorSlot<DefaultNode> {
|
||||
|
||||
@Override
|
||||
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, Object... args)
|
||||
throws Throwable {
|
||||
|
||||
try {
|
||||
fireEntry(context, resourceWrapper, node, count, args);
|
||||
node.increaseThreadNum();
|
||||
|
|
@ -61,6 +65,9 @@ public class StatisticSlot extends AbstractLinkedProcessorSlot<DefaultNode> {
|
|||
Constants.ENTRY_NODE.addPassRequest();
|
||||
}
|
||||
|
||||
for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {
|
||||
handler.onPass(context, resourceWrapper, node, count, args);
|
||||
}
|
||||
} catch (BlockException e) {
|
||||
context.getCurEntry().setError(e);
|
||||
|
||||
|
|
@ -74,6 +81,10 @@ public class StatisticSlot extends AbstractLinkedProcessorSlot<DefaultNode> {
|
|||
Constants.ENTRY_NODE.increaseBlockedQps();
|
||||
}
|
||||
|
||||
for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {
|
||||
handler.onBlocked(e, context, resourceWrapper, node, count, args);
|
||||
}
|
||||
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
context.getCurEntry().setError(e);
|
||||
|
|
@ -117,11 +128,14 @@ public class StatisticSlot extends AbstractLinkedProcessorSlot<DefaultNode> {
|
|||
Constants.ENTRY_NODE.decreaseThreadNum();
|
||||
}
|
||||
} else {
|
||||
// error may happen
|
||||
// node.rt(-2);
|
||||
// Error may happen.
|
||||
}
|
||||
|
||||
Collection<ProcessorSlotExitCallback> exitCallbacks = StatisticSlotCallbackRegistry.getExitCallbacks();
|
||||
for (ProcessorSlotExitCallback handler : exitCallbacks) {
|
||||
handler.onExit(context, resourceWrapper, count, args);
|
||||
}
|
||||
|
||||
fireExit(context, resourceWrapper, count);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* 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 java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.alibaba.csp.sentinel.node.DefaultNode;
|
||||
import com.alibaba.csp.sentinel.slotchain.ProcessorSlotEntryCallback;
|
||||
import com.alibaba.csp.sentinel.slotchain.ProcessorSlotExitCallback;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Callback registry for {@link StatisticSlot}. Now two kind of callbacks are supported:
|
||||
* <ul>
|
||||
* <li>{@link ProcessorSlotEntryCallback}: callback for entry (passed and blocked)</li>
|
||||
* <li>{@link ProcessorSlotExitCallback}: callback for exiting {@link StatisticSlot}</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public final class StatisticSlotCallbackRegistry {
|
||||
|
||||
private static final Map<String, ProcessorSlotEntryCallback<DefaultNode>> entryCallbackMap
|
||||
= new ConcurrentHashMap<String, ProcessorSlotEntryCallback<DefaultNode>>();
|
||||
|
||||
private static final Map<String, ProcessorSlotExitCallback> exitCallbackMap
|
||||
= new ConcurrentHashMap<String, ProcessorSlotExitCallback>();
|
||||
|
||||
public static void clearEntryCallback() {
|
||||
entryCallbackMap.clear();
|
||||
}
|
||||
|
||||
public static void clearExitCallback() {
|
||||
exitCallbackMap.clear();
|
||||
}
|
||||
|
||||
public static void addEntryCallback(String key, ProcessorSlotEntryCallback<DefaultNode> callback) {
|
||||
entryCallbackMap.put(key, callback);
|
||||
}
|
||||
|
||||
public static void addExitCallback(String key, ProcessorSlotExitCallback callback) {
|
||||
exitCallbackMap.put(key, callback);
|
||||
}
|
||||
|
||||
public static ProcessorSlotEntryCallback<DefaultNode> removeEntryCallback(String key) {
|
||||
if (key == null) {
|
||||
return null;
|
||||
}
|
||||
return entryCallbackMap.remove(key);
|
||||
}
|
||||
|
||||
public static ProcessorSlotExitCallback removeExitCallback(String key) {
|
||||
if (key == null) {
|
||||
return null;
|
||||
}
|
||||
return exitCallbackMap.remove(key);
|
||||
}
|
||||
|
||||
public static Collection<ProcessorSlotEntryCallback<DefaultNode>> getEntryCallbacks() {
|
||||
return entryCallbackMap.values();
|
||||
}
|
||||
|
||||
public static Collection<ProcessorSlotExitCallback> getExitCallbacks() {
|
||||
return exitCallbackMap.values();
|
||||
}
|
||||
|
||||
private StatisticSlotCallbackRegistry() {}
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@
|
|||
<module>sentinel-demo-zookeeper-datasource</module>
|
||||
<module>sentinel-demo-apollo-datasource</module>
|
||||
<module>sentinel-demo-annotation-spring-aop</module>
|
||||
<module>sentinel-demo-parameter-flow-control</module>
|
||||
</modules>
|
||||
|
||||
<dependencies>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
<?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-demo</artifactId>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<version>0.2.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>sentinel-demo-parameter-flow-control</artifactId>
|
||||
|
||||
<properties>
|
||||
<java.source.version>1.8</java.source.version>
|
||||
<java.target.version>1.8</java.target.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-parameter-flow-control</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-transport-simple-http</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* 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.demo.flow.param;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowItem;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager;
|
||||
|
||||
/**
|
||||
* This demo demonstrates flow control by frequent ("hot spot") parameters.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public class ParamFlowQpsDemo {
|
||||
|
||||
private static final int PARAM_A = 1;
|
||||
private static final int PARAM_B = 2;
|
||||
private static final int PARAM_C = 3;
|
||||
private static final int PARAM_D = 4;
|
||||
|
||||
/**
|
||||
* Here we prepare different parameters to validate flow control by parameters.
|
||||
*/
|
||||
private static final Integer[] PARAMS = new Integer[] {PARAM_A, PARAM_B, PARAM_C, PARAM_D};
|
||||
|
||||
private static final String RESOURCE_KEY = "resA";
|
||||
|
||||
public static void main(String[] args) {
|
||||
initHotParamFlowRules();
|
||||
|
||||
final int threadCount = 8;
|
||||
ParamFlowQpsRunner<Integer> runner = new ParamFlowQpsRunner<>(PARAMS, RESOURCE_KEY, threadCount, 120);
|
||||
runner.simulateTraffic();
|
||||
runner.tick();
|
||||
}
|
||||
|
||||
private static void initHotParamFlowRules() {
|
||||
// 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)
|
||||
.setBlockGrade(RuleConstant.FLOW_GRADE_QPS)
|
||||
.setCount(5);
|
||||
// We can set threshold count for specific parameter value individually.
|
||||
// Here we add an exception item. That means: QPS threshold of entries with parameter `PARAM_B` (type: int)
|
||||
// in index 0 will be 10, rather than the global threshold (5).
|
||||
ParamFlowItem item = new ParamFlowItem().setObject(String.valueOf(PARAM_B))
|
||||
.setClassType(int.class.getName())
|
||||
.setCount(10);
|
||||
rule.setParamFlowItemList(Collections.singletonList(item));
|
||||
ParamFlowRuleManager.loadRules(Collections.singletonList(rule));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* 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.demo.flow.param;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import com.alibaba.csp.sentinel.Entry;
|
||||
import com.alibaba.csp.sentinel.EntryType;
|
||||
import com.alibaba.csp.sentinel.SphU;
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
import com.alibaba.csp.sentinel.util.TimeUtil;
|
||||
|
||||
/**
|
||||
* A traffic runner to simulate flow for different parameters.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 0.2.0
|
||||
*/
|
||||
class ParamFlowQpsRunner<T> {
|
||||
|
||||
private final T[] params;
|
||||
private final String resourceName;
|
||||
private int seconds;
|
||||
private final int threadCount;
|
||||
|
||||
private final Map<T, AtomicLong> passCountMap = new ConcurrentHashMap<>();
|
||||
|
||||
private volatile boolean stop = false;
|
||||
|
||||
public ParamFlowQpsRunner(T[] params, String resourceName, int threadCount, int seconds) {
|
||||
assertTrue(params != null && params.length > 0, "Parameter array should not be empty");
|
||||
assertTrue(StringUtil.isNotBlank(resourceName), "Resource name cannot be empty");
|
||||
assertTrue(seconds > 0, "Time period should be positive");
|
||||
assertTrue(threadCount > 0 && threadCount <= 1000, "Invalid thread count");
|
||||
this.params = params;
|
||||
this.resourceName = resourceName;
|
||||
this.seconds = seconds;
|
||||
this.threadCount = threadCount;
|
||||
|
||||
for (T param : params) {
|
||||
assertTrue(param != null, "Parameters should not be null");
|
||||
passCountMap.putIfAbsent(param, new AtomicLong());
|
||||
}
|
||||
}
|
||||
|
||||
private void assertTrue(boolean b, String message) {
|
||||
if (!b) {
|
||||
throw new IllegalArgumentException(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick one of provided parameters randomly.
|
||||
*
|
||||
* @return picked parameter
|
||||
*/
|
||||
private T generateParam() {
|
||||
int i = ThreadLocalRandom.current().nextInt(0, params.length);
|
||||
return params[i];
|
||||
}
|
||||
|
||||
void simulateTraffic() {
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
Thread t = new Thread(new RunTask());
|
||||
t.setName("sentinel-simulate-traffic-task-" + i);
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
|
||||
void tick() {
|
||||
Thread timer = new Thread(new TimerTask());
|
||||
timer.setName("sentinel-timer-task");
|
||||
timer.start();
|
||||
}
|
||||
|
||||
private void passFor(T param) {
|
||||
passCountMap.get(param).incrementAndGet();
|
||||
}
|
||||
|
||||
final class RunTask implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
while (!stop) {
|
||||
Entry entry = null;
|
||||
|
||||
try {
|
||||
T param = generateParam();
|
||||
entry = SphU.entry(resourceName, EntryType.IN, 1, param);
|
||||
// Add pass for parameter.
|
||||
passFor(param);
|
||||
} catch (BlockException e1) {
|
||||
// block.incrementAndGet();
|
||||
} catch (Exception e2) {
|
||||
// biz exception
|
||||
} finally {
|
||||
// total.incrementAndGet();
|
||||
if (entry != null) {
|
||||
entry.exit();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
TimeUnit.MILLISECONDS.sleep(ThreadLocalRandom.current().nextInt(0, 10));
|
||||
} catch (InterruptedException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class TimerTask implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
long start = System.currentTimeMillis();
|
||||
System.out.println("Begin to run! Go go go!");
|
||||
System.out.println("See corresponding metrics.log for accurate statistic data");
|
||||
|
||||
Map<T, Long> map = new HashMap<>(params.length);
|
||||
for (T param : params) {
|
||||
map.putIfAbsent(param, 0L);
|
||||
}
|
||||
while (!stop) {
|
||||
try {
|
||||
TimeUnit.SECONDS.sleep(1);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
// There may be a mismatch for time window of internal sliding window.
|
||||
// See corresponding `metrics.log` for accurate statistic log.
|
||||
for (T param : params) {
|
||||
long globalPass = passCountMap.get(param).get();
|
||||
long oldPass = map.get(param);
|
||||
long oneSecondPass = globalPass - oldPass;
|
||||
map.put(param, globalPass);
|
||||
System.out.println(String.format("[%d][%d] Hot param metrics for resource %s: "
|
||||
+ "pass count for param <%s> is %d",
|
||||
seconds, TimeUtil.currentTimeMillis(), resourceName, param, oneSecondPass));
|
||||
}
|
||||
if (seconds-- <= 0) {
|
||||
stop = true;
|
||||
}
|
||||
}
|
||||
|
||||
long cost = System.currentTimeMillis() - start;
|
||||
System.out.println("Time cost: " + cost + " ms");
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@
|
|||
<module>sentinel-datasource-apollo</module>
|
||||
<module>sentinel-datasource-redis</module>
|
||||
<module>sentinel-annotation-aspectj</module>
|
||||
<module>sentinel-parameter-flow-control</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
# 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**) ||
|
||||
| blockGrade | flow control mode (only QPS mode is supported) | 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.
|
||||
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
<?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>0.2.0-SNAPSHOT</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>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* 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.util.Map;
|
||||
|
||||
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.ParamFlowSlot;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParameterMetric;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 0.2.0
|
||||
*/
|
||||
@CommandMapping(name = "topParams")
|
||||
public class FetchTopParamsCommandHandler implements CommandHandler<String> {
|
||||
|
||||
@Override
|
||||
public CommandResponse<String> handle(CommandRequest request) {
|
||||
String resourceName = request.getParam("res");
|
||||
if (StringUtil.isBlank(resourceName)) {
|
||||
return CommandResponse.ofFailure(new IllegalArgumentException("Invalid parameter: res"));
|
||||
}
|
||||
String idx = request.getParam("idx");
|
||||
int index;
|
||||
try {
|
||||
index = Integer.valueOf(idx);
|
||||
} catch (Exception ex) {
|
||||
return CommandResponse.ofFailure(ex, "Invalid parameter: idx");
|
||||
}
|
||||
String n = request.getParam("n");
|
||||
int amount;
|
||||
try {
|
||||
amount = Integer.valueOf(n);
|
||||
} catch (Exception ex) {
|
||||
return CommandResponse.ofFailure(ex, "Invalid parameter: n");
|
||||
}
|
||||
ParameterMetric metric = ParamFlowSlot.getHotParamMetricForName(resourceName);
|
||||
if (metric == null) {
|
||||
return CommandResponse.ofSuccess("{}");
|
||||
}
|
||||
Map<Object, Double> values = metric.getTopPassParamCount(index, amount);
|
||||
|
||||
return CommandResponse.ofSuccess(JSON.toJSONString(values));
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
public class GetParamFlowRulesCommandHandler implements CommandHandler<String> {
|
||||
|
||||
@Override
|
||||
public CommandResponse<String> handle(CommandRequest request) {
|
||||
return CommandResponse.ofSuccess(JSON.toJSONString(ParamFlowRuleManager.getRules()));
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
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(String.format("[API Server] Receiving rule change (type:parameter flow rule): %s", 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)";
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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.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());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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.slotchain.DefaultProcessorSlotChain;
|
||||
import com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain;
|
||||
import com.alibaba.csp.sentinel.slotchain.SlotChainBuilder;
|
||||
import com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot;
|
||||
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeSlot;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowSlot;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowSlot;
|
||||
import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot;
|
||||
import com.alibaba.csp.sentinel.slots.logger.LogSlot;
|
||||
import com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot;
|
||||
import com.alibaba.csp.sentinel.slots.statistic.StatisticSlot;
|
||||
import com.alibaba.csp.sentinel.slots.system.SystemSlot;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public class HotParamSlotChainBuilder implements SlotChainBuilder {
|
||||
|
||||
@Override
|
||||
public ProcessorSlotChain build() {
|
||||
ProcessorSlotChain chain = new DefaultProcessorSlotChain();
|
||||
chain.addLast(new NodeSelectorSlot());
|
||||
chain.addLast(new ClusterBuilderSlot());
|
||||
chain.addLast(new LogSlot());
|
||||
chain.addLast(new StatisticSlot());
|
||||
chain.addLast(new ParamFlowSlot());
|
||||
chain.addLast(new SystemSlot());
|
||||
chain.addLast(new AuthoritySlot());
|
||||
chain.addLast(new FlowSlot());
|
||||
chain.addLast(new DegradeSlot());
|
||||
|
||||
return chain;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* 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.Set;
|
||||
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
|
||||
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 0.2.0
|
||||
*/
|
||||
final class ParamFlowChecker {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Object value = args[paramIdx];
|
||||
|
||||
return passLocalCheck(resourceWrapper, rule, count, value);
|
||||
}
|
||||
|
||||
private static ParameterMetric getHotParameters(ResourceWrapper resourceWrapper) {
|
||||
// Should not be null.
|
||||
return ParamFlowSlot.getParamMetric(resourceWrapper);
|
||||
}
|
||||
|
||||
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.info("[ParamFlowChecker] Unexpected error", e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static boolean passSingleValueCheck(ResourceWrapper resourceWrapper, ParamFlowRule rule, int count, Object value) {
|
||||
Set<Object> exclusionItems = rule.getParsedHotItems().keySet();
|
||||
if (rule.getBlockGrade() == RuleConstant.FLOW_GRADE_QPS) {
|
||||
double curCount = getHotParameters(resourceWrapper).getPassParamQps(rule.getParamIdx(), value);
|
||||
|
||||
if (exclusionItems.contains(value)) {
|
||||
// Pass check for exclusion items.
|
||||
int itemQps = rule.getParsedHotItems().get(value);
|
||||
return curCount + count <= itemQps;
|
||||
} else if (curCount + count > rule.getCount()) {
|
||||
if ((curCount - rule.getCount()) < 1 && (curCount - rule.getCount()) > 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private ParamFlowChecker() {}
|
||||
}
|
||||
|
|
@ -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.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 message) {
|
||||
super(message, message);
|
||||
this.resourceName = resourceName;
|
||||
}
|
||||
|
||||
public String getResourceName() {
|
||||
return resourceName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Throwable fillInStackTrace() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
* 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 com.alibaba.csp.sentinel.context.Context;
|
||||
import com.alibaba.csp.sentinel.node.DefaultNode;
|
||||
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 (1: QPS).
|
||||
*/
|
||||
private int blockGrade = RuleConstant.FLOW_GRADE_QPS;
|
||||
|
||||
/**
|
||||
* Parameter index.
|
||||
*/
|
||||
private Integer paramIdx;
|
||||
|
||||
/**
|
||||
* The threshold count.
|
||||
*/
|
||||
private double count;
|
||||
|
||||
/**
|
||||
* Original exclusion items of parameters.
|
||||
*/
|
||||
private List<ParamFlowItem> paramFlowItemList = new ArrayList<ParamFlowItem>();
|
||||
|
||||
/**
|
||||
* Parsed exclusion items of parameters. Only for internal use.
|
||||
*/
|
||||
private Map<Object, Integer> hotItems = new HashMap<Object, Integer>();
|
||||
|
||||
public int getBlockGrade() {
|
||||
return blockGrade;
|
||||
}
|
||||
|
||||
public ParamFlowRule setBlockGrade(int blockGrade) {
|
||||
this.blockGrade = blockGrade;
|
||||
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;
|
||||
}
|
||||
|
||||
Map<Object, Integer> getParsedHotItems() {
|
||||
return hotItems;
|
||||
}
|
||||
|
||||
ParamFlowRule setParsedHotItems(Map<Object, Integer> hotItems) {
|
||||
this.hotItems = hotItems;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public boolean passCheck(Context context, DefaultNode node, int count, Object... args) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@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 rule = (ParamFlowRule)o;
|
||||
|
||||
if (blockGrade != rule.blockGrade) { return false; }
|
||||
if (Double.compare(rule.count, count) != 0) { return false; }
|
||||
if (paramIdx != null ? !paramIdx.equals(rule.paramIdx) : rule.paramIdx != null) { return false; }
|
||||
return paramFlowItemList != null ? paramFlowItemList.equals(rule.paramFlowItemList) : rule.paramFlowItemList == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = super.hashCode();
|
||||
long temp;
|
||||
result = 31 * result + blockGrade;
|
||||
result = 31 * result + (paramIdx != null ? paramIdx.hashCode() : 0);
|
||||
temp = Double.doubleToLongBits(count);
|
||||
result = 31 * result + (int)(temp ^ (temp >>> 32));
|
||||
result = 31 * result + (paramFlowItemList != null ? paramFlowItemList.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ParamFlowRule{" +
|
||||
"resource=" + getResource() +
|
||||
", limitApp=" + getLimitApp() +
|
||||
", blockGrade=" + blockGrade +
|
||||
", paramIdx=" + paramIdx +
|
||||
", count=" + count +
|
||||
", paramFlowItemList=" + paramFlowItemList +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
@ -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 java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
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.slots.block.flow.FlowRule;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
|
||||
/**
|
||||
* 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>> paramFlowRules
|
||||
= new ConcurrentHashMap<String, List<ParamFlowRule>>();
|
||||
|
||||
private final static RulePropertyListener PROPERTY_LISTENER = new RulePropertyListener();
|
||||
private static SentinelProperty<List<ParamFlowRule>> currentProperty
|
||||
= new DynamicSentinelProperty<List<ParamFlowRule>>();
|
||||
|
||||
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) {
|
||||
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 paramFlowRules.get(resourceName);
|
||||
}
|
||||
|
||||
public static boolean hasRules(String resourceName) {
|
||||
List<ParamFlowRule> rules = paramFlowRules.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<ParamFlowRule>();
|
||||
for (Map.Entry<String, List<ParamFlowRule>> entry : paramFlowRules.entrySet()) {
|
||||
rules.addAll(entry.getValue());
|
||||
}
|
||||
return rules;
|
||||
}
|
||||
|
||||
private static Object parseValue(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;
|
||||
}
|
||||
|
||||
static class RulePropertyListener implements PropertyListener<List<ParamFlowRule>> {
|
||||
|
||||
@Override
|
||||
public void configUpdate(List<ParamFlowRule> list) {
|
||||
Map<String, List<ParamFlowRule>> rules = aggregateHotParamRules(list);
|
||||
if (rules != null) {
|
||||
paramFlowRules.clear();
|
||||
paramFlowRules.putAll(rules);
|
||||
}
|
||||
RecordLog.info("[ParamFlowRuleManager] Hot spot parameter flow rules received: " + paramFlowRules);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configLoad(List<ParamFlowRule> list) {
|
||||
Map<String, List<ParamFlowRule>> rules = aggregateHotParamRules(list);
|
||||
if (rules != null) {
|
||||
paramFlowRules.clear();
|
||||
paramFlowRules.putAll(rules);
|
||||
}
|
||||
RecordLog.info("[ParamFlowRuleManager] Hot spot parameter flow rules received: " + paramFlowRules);
|
||||
}
|
||||
|
||||
private Map<String, List<ParamFlowRule>> aggregateHotParamRules(List<ParamFlowRule> list) {
|
||||
Map<String, List<ParamFlowRule>> newRuleMap = new ConcurrentHashMap<String, List<ParamFlowRule>>();
|
||||
|
||||
if (list == null || list.isEmpty()) {
|
||||
// No parameter flow rules, so clear all the metrics.
|
||||
ParamFlowSlot.getMetricsMap().clear();
|
||||
RecordLog.info("[ParamFlowRuleManager] No parameter flow rules, clearing all parameter metrics");
|
||||
return newRuleMap;
|
||||
}
|
||||
|
||||
for (ParamFlowRule rule : list) {
|
||||
if (!isValidRule(rule)) {
|
||||
RecordLog.warn("[ParamFlowRuleManager] Ignoring invalid rule when loading new rules: " + rule);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (StringUtil.isBlank(rule.getLimitApp())) {
|
||||
rule.setLimitApp(FlowRule.LIMIT_APP_DEFAULT);
|
||||
}
|
||||
|
||||
if (rule.getParamFlowItemList() == null) {
|
||||
rule.setParamFlowItemList(new ArrayList<ParamFlowItem>());
|
||||
}
|
||||
|
||||
Map<Object, Integer> itemMap = parseHotItems(rule.getParamFlowItemList());
|
||||
rule.setParsedHotItems(itemMap);
|
||||
|
||||
String resourceName = rule.getResource();
|
||||
List<ParamFlowRule> ruleList = newRuleMap.get(resourceName);
|
||||
if (ruleList == null) {
|
||||
ruleList = new ArrayList<ParamFlowRule>();
|
||||
newRuleMap.put(resourceName, ruleList);
|
||||
}
|
||||
ruleList.add(rule);
|
||||
}
|
||||
|
||||
// Clear unused hot param metrics.
|
||||
Set<String> previousResources = paramFlowRules.keySet();
|
||||
for (String resource : previousResources) {
|
||||
if (!newRuleMap.containsKey(resource)) {
|
||||
ParamFlowSlot.clearHotParamMetricForName(resource);
|
||||
}
|
||||
}
|
||||
|
||||
return newRuleMap;
|
||||
}
|
||||
}
|
||||
|
||||
static Map<Object, Integer> parseHotItems(List<ParamFlowItem> items) {
|
||||
Map<Object, Integer> itemMap = new HashMap<Object, Integer>();
|
||||
if (items == null || items.isEmpty()) {
|
||||
return itemMap;
|
||||
}
|
||||
for (ParamFlowItem item : items) {
|
||||
// Value should not be null.
|
||||
Object value;
|
||||
try {
|
||||
value = parseValue(item.getObject(), item.getClassType());
|
||||
} catch (Exception ex) {
|
||||
RecordLog.warn("[ParamFlowRuleManager] Failed to parse value for item: " + item, ex);
|
||||
continue;
|
||||
}
|
||||
if (item.getCount() == null || item.getCount() < 0 || value == null) {
|
||||
RecordLog.warn("[ParamFlowRuleManager] Ignoring invalid exclusion parameter item: " + item);
|
||||
continue;
|
||||
}
|
||||
itemMap.put(value, item.getCount());
|
||||
}
|
||||
return itemMap;
|
||||
}
|
||||
|
||||
static boolean isValidRule(ParamFlowRule rule) {
|
||||
return rule != null && !StringUtil.isBlank(rule.getResource()) && rule.getCount() >= 0
|
||||
&& rule.getParamIdx() != null && rule.getParamIdx() >= 0;
|
||||
}
|
||||
|
||||
private ParamFlowRuleManager() {}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
/*
|
||||
* 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.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.alibaba.csp.sentinel.EntryType;
|
||||
import com.alibaba.csp.sentinel.context.Context;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
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.slotchain.StringResourceWrapper;
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
|
||||
/**
|
||||
* A processor slot that is responsible for flow control by frequent ("hot spot") parameters.
|
||||
*
|
||||
* @author jialiang.linjl
|
||||
* @author Eric Zhao
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public class ParamFlowSlot extends AbstractLinkedProcessorSlot<DefaultNode> {
|
||||
|
||||
private static final Map<ResourceWrapper, ParameterMetric> metricsMap
|
||||
= new ConcurrentHashMap<ResourceWrapper, ParameterMetric>();
|
||||
|
||||
/**
|
||||
* Lock for a specific resource.
|
||||
*/
|
||||
private final Object LOCK = new Object();
|
||||
|
||||
@Override
|
||||
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, Object... args)
|
||||
throws Throwable {
|
||||
|
||||
if (!ParamFlowRuleManager.hasRules(resourceWrapper.getName())) {
|
||||
fireEntry(context, resourceWrapper, node, count, args);
|
||||
return;
|
||||
}
|
||||
|
||||
checkFlow(resourceWrapper, count, args);
|
||||
fireEntry(context, resourceWrapper, node, count, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
|
||||
fireExit(context, resourceWrapper, count, args);
|
||||
}
|
||||
|
||||
void checkFlow(ResourceWrapper resourceWrapper, int count, Object... args)
|
||||
throws BlockException {
|
||||
if (ParamFlowRuleManager.hasRules(resourceWrapper.getName())) {
|
||||
List<ParamFlowRule> rules = ParamFlowRuleManager.getRulesOfResource(resourceWrapper.getName());
|
||||
if (rules == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (ParamFlowRule rule : rules) {
|
||||
// Initialize the parameter metrics.
|
||||
initHotParamMetricsFor(resourceWrapper, rule.getParamIdx());
|
||||
|
||||
if (!ParamFlowChecker.passCheck(resourceWrapper, rule, count, args)) {
|
||||
|
||||
// Here we add the block count.
|
||||
addBlockCount(resourceWrapper, count, args);
|
||||
|
||||
String message = "";
|
||||
if (args.length > rule.getParamIdx()) {
|
||||
Object value = args[rule.getParamIdx()];
|
||||
message = String.valueOf(value);
|
||||
}
|
||||
throw new ParamFlowException(resourceWrapper.getName(), message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addBlockCount(ResourceWrapper resourceWrapper, int count, Object... args) {
|
||||
ParameterMetric parameterMetric = ParamFlowSlot.getParamMetric(resourceWrapper);
|
||||
|
||||
if (parameterMetric != null) {
|
||||
parameterMetric.addBlock(count, args);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the parameter metric and index map for given resource.
|
||||
* Package-private for test.
|
||||
*
|
||||
* @param resourceWrapper resource to init
|
||||
* @param index index to initialize, which must be valid
|
||||
*/
|
||||
void initHotParamMetricsFor(ResourceWrapper resourceWrapper, /*@Valid*/ int index) {
|
||||
ParameterMetric metric;
|
||||
// Assume that the resource is valid.
|
||||
if ((metric = metricsMap.get(resourceWrapper)) == null) {
|
||||
synchronized (LOCK) {
|
||||
if ((metric = metricsMap.get(resourceWrapper)) == null) {
|
||||
metric = new ParameterMetric();
|
||||
metricsMap.put(resourceWrapper, metric);
|
||||
RecordLog.info("[ParamFlowSlot] Creating parameter metric for: " + resourceWrapper.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
metric.initializeForIndex(index);
|
||||
}
|
||||
|
||||
public static ParameterMetric getParamMetric(ResourceWrapper resourceWrapper) {
|
||||
if (resourceWrapper == null || resourceWrapper.getName() == null) {
|
||||
return null;
|
||||
}
|
||||
return metricsMap.get(resourceWrapper);
|
||||
}
|
||||
|
||||
public static ParameterMetric getHotParamMetricForName(String resourceName) {
|
||||
if (StringUtil.isBlank(resourceName)) {
|
||||
return null;
|
||||
}
|
||||
for (EntryType nodeType : EntryType.values()) {
|
||||
ParameterMetric metric = metricsMap.get(new StringResourceWrapper(resourceName, nodeType));
|
||||
if (metric != null) {
|
||||
return metric;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static void clearHotParamMetricForName(String resourceName) {
|
||||
if (StringUtil.isBlank(resourceName)) {
|
||||
return;
|
||||
}
|
||||
for (EntryType nodeType : EntryType.values()) {
|
||||
metricsMap.remove(new StringResourceWrapper(resourceName, nodeType));
|
||||
}
|
||||
RecordLog.info("[ParamFlowSlot] Clearing parameter metric for: " + resourceName);
|
||||
}
|
||||
|
||||
public static Map<ResourceWrapper, ParameterMetric> getMetricsMap() {
|
||||
return metricsMap;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* 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.ConcurrentHashMap;
|
||||
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.node.IntervalProperty;
|
||||
import com.alibaba.csp.sentinel.node.SampleCountProperty;
|
||||
import com.alibaba.csp.sentinel.slots.statistic.metric.HotParameterLeapArray;
|
||||
|
||||
/**
|
||||
* Metrics for frequent ("hot spot") parameters.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public class ParameterMetric {
|
||||
|
||||
private Map<Integer, HotParameterLeapArray> rollingParameters =
|
||||
new ConcurrentHashMap<Integer, HotParameterLeapArray>();
|
||||
|
||||
public Map<Integer, HotParameterLeapArray> getRollingParameters() {
|
||||
return rollingParameters;
|
||||
}
|
||||
|
||||
public synchronized void clear() {
|
||||
rollingParameters.clear();
|
||||
}
|
||||
|
||||
public void initializeForIndex(int index) {
|
||||
if (!rollingParameters.containsKey(index)) {
|
||||
synchronized (this) {
|
||||
// putIfAbsent
|
||||
if (rollingParameters.get(index) == null) {
|
||||
rollingParameters.put(index, new HotParameterLeapArray(
|
||||
1000 / 2, IntervalProperty.INTERVAL));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addPass(int count, Object... args) {
|
||||
add(RollingParamEvent.REQUEST_PASSED, count, args);
|
||||
}
|
||||
|
||||
public void addBlock(int count, Object... args) {
|
||||
add(RollingParamEvent.REQUEST_BLOCKED, count, args);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private void add(RollingParamEvent event, int count, Object... args) {
|
||||
if (args == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
for (int index = 0; index < args.length; index++) {
|
||||
HotParameterLeapArray param = rollingParameters.get(index);
|
||||
if (param == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object arg = args[index];
|
||||
if (arg == null) {
|
||||
continue;
|
||||
}
|
||||
if (Collection.class.isAssignableFrom(arg.getClass())) {
|
||||
for (Object value : ((Collection)arg)) {
|
||||
param.addValue(event, count, value);
|
||||
}
|
||||
} else if (arg.getClass().isArray()) {
|
||||
int length = Array.getLength(arg);
|
||||
for (int i = 0; i < length; i++) {
|
||||
Object value = Array.get(arg, i);
|
||||
param.addValue(event, count, value);
|
||||
}
|
||||
} else {
|
||||
param.addValue(event, count, arg);
|
||||
}
|
||||
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
RecordLog.warn("[ParameterMetric] Param exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
public double getPassParamQps(int index, Object value) {
|
||||
try {
|
||||
HotParameterLeapArray parameter = rollingParameters.get(index);
|
||||
if (parameter == null || value == null) {
|
||||
return -1;
|
||||
}
|
||||
return parameter.getRollingAvg(RollingParamEvent.REQUEST_PASSED, value);
|
||||
} catch (Throwable e) {
|
||||
RecordLog.info(e.getMessage(), e);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public long getBlockParamQps(int index, Object value) {
|
||||
try {
|
||||
HotParameterLeapArray parameter = rollingParameters.get(index);
|
||||
if (parameter == null || value == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return (long)rollingParameters.get(index).getRollingAvg(RollingParamEvent.REQUEST_BLOCKED, value);
|
||||
} catch (Throwable e) {
|
||||
RecordLog.info(e.getMessage(), e);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public Map<Object, Double> getTopPassParamCount(int index, int number) {
|
||||
try {
|
||||
HotParameterLeapArray parameter = rollingParameters.get(index);
|
||||
if (parameter == null) {
|
||||
return new HashMap<Object, Double>();
|
||||
}
|
||||
|
||||
return parameter.getTopValues(RollingParamEvent.REQUEST_PASSED, number);
|
||||
} catch (Throwable e) {
|
||||
RecordLog.info(e.getMessage(), e);
|
||||
}
|
||||
|
||||
return new HashMap<Object, Double>();
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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.ParamFlowSlot;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParameterMetric;
|
||||
|
||||
/**
|
||||
* @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)
|
||||
throws Exception {
|
||||
// The "hot spot" parameter metric is present only if parameter flow rules for the resource exist.
|
||||
ParameterMetric parameterMetric = ParamFlowSlot.getParamMetric(resourceWrapper);
|
||||
|
||||
if (parameterMetric != null) {
|
||||
parameterMetric.addPass(count, 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.
|
||||
}
|
||||
}
|
||||
|
|
@ -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> ascendingKeySet();
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* 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> ascendingKeySet() {
|
||||
return map.ascendingKeySet();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public ParamMapBucket() {
|
||||
RollingParamEvent[] events = RollingParamEvent.values();
|
||||
this.data = new CacheMap[events.length];
|
||||
for (RollingParamEvent event : events) {
|
||||
data[event.ordinal()] = new ConcurrentLinkedHashMapWrapper<Object, AtomicInteger>(DEFAULT_MAX_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) {
|
||||
data[event.ordinal()].putIfAbsent(value, new AtomicInteger());
|
||||
AtomicInteger counter = data[event.ordinal()].get(value);
|
||||
counter.addAndGet(count);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Set<Object> ascendingKeySet(RollingParamEvent type) {
|
||||
return data[type.ordinal()].ascendingKeySet();
|
||||
}
|
||||
|
||||
public static final int DEFAULT_MAX_CAPACITY = 200;
|
||||
}
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* 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.metric;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.param.RollingParamEvent;
|
||||
import com.alibaba.csp.sentinel.slots.statistic.base.LeapArray;
|
||||
import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap;
|
||||
import com.alibaba.csp.sentinel.slots.statistic.data.ParamMapBucket;
|
||||
|
||||
/**
|
||||
* The fundamental data structure for frequent parameters statistics in a time window.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public class HotParameterLeapArray extends LeapArray<ParamMapBucket> {
|
||||
|
||||
private int intervalInSec;
|
||||
|
||||
public HotParameterLeapArray(int windowLengthInMs, int intervalInSec) {
|
||||
super(windowLengthInMs, intervalInSec);
|
||||
this.intervalInSec = intervalInSec;
|
||||
}
|
||||
|
||||
public int getIntervalInSec() {
|
||||
return intervalInSec;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParamMapBucket newEmptyBucket() {
|
||||
return new ParamMapBucket();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected WindowWrap<ParamMapBucket> resetWindowTo(WindowWrap<ParamMapBucket> w, long startTime) {
|
||||
w.resetTo(startTime);
|
||||
w.value().reset();
|
||||
return w;
|
||||
}
|
||||
|
||||
public void addValue(RollingParamEvent event, int count, Object value) {
|
||||
currentWindow().value().add(event, count, value);
|
||||
}
|
||||
|
||||
public Map<Object, Double> getTopValues(RollingParamEvent event, int number) {
|
||||
currentWindow();
|
||||
List<ParamMapBucket> buckets = this.values();
|
||||
|
||||
Map<Object, Integer> result = new HashMap<Object, Integer>();
|
||||
|
||||
for (ParamMapBucket b : buckets) {
|
||||
Set<Object> subSet = b.ascendingKeySet(event);
|
||||
for (Object o : subSet) {
|
||||
Integer count = result.get(o);
|
||||
if (count == null) {
|
||||
count = b.get(event, o);
|
||||
} else {
|
||||
count += b.get(event, o);
|
||||
}
|
||||
result.put(o, count);
|
||||
}
|
||||
}
|
||||
|
||||
// After merge, get the top set one.
|
||||
Set<Entry<Object, Integer>> set = result.entrySet();
|
||||
List<Entry<Object, Integer>> list = new ArrayList<Entry<Object, Integer>>(set);
|
||||
Collections.sort(list, new Comparator<Entry<Object, Integer>>() {
|
||||
@Override
|
||||
public int compare(Entry<Object, Integer> a,
|
||||
Entry<Object, Integer> b) {
|
||||
return (b.getValue() == null ? 0 : b.getValue()) - (a.getValue() == null ? 0 : a.getValue());
|
||||
}
|
||||
});
|
||||
|
||||
Map<Object, Double> doubleResult = new HashMap<Object, Double>();
|
||||
|
||||
int size = list.size() > number ? number : list.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
Map.Entry<Object, Integer> x = list.get(i);
|
||||
if (x.getValue() == 0) {
|
||||
break;
|
||||
}
|
||||
doubleResult.put(x.getKey(), ((double)x.getValue()) / getIntervalInSec());
|
||||
}
|
||||
|
||||
return doubleResult;
|
||||
}
|
||||
|
||||
public long getRollingSum(RollingParamEvent event, Object value) {
|
||||
currentWindow();
|
||||
|
||||
long sum = 0;
|
||||
|
||||
List<ParamMapBucket> buckets = this.values();
|
||||
for (ParamMapBucket b : buckets) {
|
||||
sum += b.get(event, value);
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
public double getRollingAvg(RollingParamEvent event, Object value) {
|
||||
return ((double) getRollingSum(event, value)) / getIntervalInSec();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
com.alibaba.csp.sentinel.command.handler.GetParamFlowRulesCommandHandler
|
||||
com.alibaba.csp.sentinel.command.handler.ModifyParamFlowRulesCommandHandler
|
||||
com.alibaba.csp.sentinel.command.handler.FetchTopParamsCommandHandler
|
||||
|
|
@ -0,0 +1 @@
|
|||
com.alibaba.csp.sentinel.init.ParamFlowStatisticSlotCallbackInit
|
||||
|
|
@ -0,0 +1 @@
|
|||
com.alibaba.csp.sentinel.slots.HotParamSlotChainBuilder
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
/*
|
||||
* 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.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
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.*;
|
||||
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 testSingleValueCheckQpsWithoutExceptionItems() {
|
||||
final String resourceName = "testSingleValueCheckQpsWithoutExceptionItems";
|
||||
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";
|
||||
String valueB = "valueB";
|
||||
ParameterMetric metric = mock(ParameterMetric.class);
|
||||
when(metric.getPassParamQps(paramIdx, valueA)).thenReturn((double)threshold - 1);
|
||||
when(metric.getPassParamQps(paramIdx, valueB)).thenReturn((double)threshold + 1);
|
||||
ParamFlowSlot.getMetricsMap().put(resourceWrapper, metric);
|
||||
|
||||
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA));
|
||||
assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueB));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleValueCheckQpsWithExceptionItems() {
|
||||
final String resourceName = "testSingleValueCheckQpsWithExceptionItems";
|
||||
final ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN);
|
||||
int paramIdx = 0;
|
||||
|
||||
long globalThreshold = 5L;
|
||||
int thresholdB = 3;
|
||||
int thresholdD = 7;
|
||||
|
||||
ParamFlowRule rule = new ParamFlowRule();
|
||||
rule.setResource(resourceName);
|
||||
rule.setCount(globalThreshold);
|
||||
rule.setParamIdx(paramIdx);
|
||||
|
||||
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.getPassParamQps(paramIdx, valueA)).thenReturn((double)globalThreshold - 1);
|
||||
when(metric.getPassParamQps(paramIdx, valueB)).thenReturn((double)globalThreshold - 1);
|
||||
when(metric.getPassParamQps(paramIdx, valueC)).thenReturn((double)globalThreshold - 1);
|
||||
when(metric.getPassParamQps(paramIdx, valueD)).thenReturn((double)globalThreshold + 1);
|
||||
ParamFlowSlot.getMetricsMap().put(resourceWrapper, 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.getPassParamQps(paramIdx, valueA)).thenReturn((double)globalThreshold);
|
||||
when(metric.getPassParamQps(paramIdx, valueB)).thenReturn((double)thresholdB - 1L);
|
||||
when(metric.getPassParamQps(paramIdx, valueC)).thenReturn((double)globalThreshold + 1);
|
||||
when(metric.getPassParamQps(paramIdx, valueD)).thenReturn((double)globalThreshold - 1)
|
||||
.thenReturn((double)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() {
|
||||
final String resourceName = "testPassLocalCheckForCollection";
|
||||
final ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN);
|
||||
int paramIdx = 0;
|
||||
double globalThreshold = 10;
|
||||
|
||||
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 = mock(ParameterMetric.class);
|
||||
when(metric.getPassParamQps(paramIdx, v1)).thenReturn(globalThreshold - 2)
|
||||
.thenReturn(globalThreshold - 1);
|
||||
when(metric.getPassParamQps(paramIdx, v2)).thenReturn(globalThreshold - 2)
|
||||
.thenReturn(globalThreshold - 1);
|
||||
when(metric.getPassParamQps(paramIdx, v3)).thenReturn(globalThreshold - 1)
|
||||
.thenReturn(globalThreshold);
|
||||
ParamFlowSlot.getMetricsMap().put(resourceWrapper, metric);
|
||||
|
||||
assertTrue(ParamFlowChecker.passCheck(resourceWrapper, rule, 1, list));
|
||||
assertFalse(ParamFlowChecker.passCheck(resourceWrapper, rule, 1, list));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPassLocalCheckForArray() {
|
||||
final String resourceName = "testPassLocalCheckForArray";
|
||||
final ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN);
|
||||
int paramIdx = 0;
|
||||
double globalThreshold = 10;
|
||||
|
||||
ParamFlowRule rule = new ParamFlowRule(resourceName)
|
||||
.setParamIdx(paramIdx)
|
||||
.setCount(globalThreshold);
|
||||
|
||||
String v1 = "a", v2 = "B", v3 = "Cc";
|
||||
Object arr = new String[] {v1, v2, v3};
|
||||
ParameterMetric metric = mock(ParameterMetric.class);
|
||||
when(metric.getPassParamQps(paramIdx, v1)).thenReturn(globalThreshold - 2)
|
||||
.thenReturn(globalThreshold - 1);
|
||||
when(metric.getPassParamQps(paramIdx, v2)).thenReturn(globalThreshold - 2)
|
||||
.thenReturn(globalThreshold - 1);
|
||||
when(metric.getPassParamQps(paramIdx, v3)).thenReturn(globalThreshold - 1)
|
||||
.thenReturn(globalThreshold);
|
||||
ParamFlowSlot.getMetricsMap().put(resourceWrapper, metric);
|
||||
|
||||
assertTrue(ParamFlowChecker.passCheck(resourceWrapper, rule, 1, arr));
|
||||
assertFalse(ParamFlowChecker.passCheck(resourceWrapper, rule, 1, arr));
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
ParamFlowSlot.getMetricsMap().clear();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
ParamFlowSlot.getMetricsMap().clear();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,192 @@
|
|||
/*
|
||||
* 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.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.csp.sentinel.EntryType;
|
||||
import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper;
|
||||
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);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
ParamFlowRuleManager.loadRules(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadHotParamRulesClearingUnusedMetrics() {
|
||||
final String resA = "resA";
|
||||
ParamFlowRule ruleA = new ParamFlowRule(resA)
|
||||
.setCount(1)
|
||||
.setParamIdx(0);
|
||||
ParamFlowRuleManager.loadRules(Collections.singletonList(ruleA));
|
||||
ParamFlowSlot.getMetricsMap().put(new StringResourceWrapper(resA, EntryType.IN), new ParameterMetric());
|
||||
assertNotNull(ParamFlowSlot.getHotParamMetricForName(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", ParamFlowSlot.getHotParamMetricForName(resA));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadHotParamRulesAndGet() {
|
||||
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)
|
||||
.setBlockGrade(RuleConstant.FLOW_GRADE_QPS);
|
||||
// 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 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 = ParamFlowRuleManager.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, ParamFlowRuleManager.parseHotItems(null).size());
|
||||
assertEquals(0, ParamFlowRuleManager.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 = ParamFlowRuleManager.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));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckValidHotParamRule() {
|
||||
// Null or empty resource;
|
||||
ParamFlowRule rule1 = new ParamFlowRule();
|
||||
ParamFlowRule rule2 = new ParamFlowRule("");
|
||||
assertFalse(ParamFlowRuleManager.isValidRule(null));
|
||||
assertFalse(ParamFlowRuleManager.isValidRule(rule1));
|
||||
assertFalse(ParamFlowRuleManager.isValidRule(rule2));
|
||||
|
||||
// Invalid threshold count.
|
||||
ParamFlowRule rule3 = new ParamFlowRule("abc")
|
||||
.setCount(-1)
|
||||
.setParamIdx(1);
|
||||
assertFalse(ParamFlowRuleManager.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(ParamFlowRuleManager.isValidRule(rule4));
|
||||
assertFalse(ParamFlowRuleManager.isValidRule(rule5));
|
||||
|
||||
ParamFlowRule goodRule = new ParamFlowRule("abc")
|
||||
.setCount(10)
|
||||
.setParamIdx(1);
|
||||
assertTrue(ParamFlowRuleManager.isValidRule(goodRule));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* 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.Collections;
|
||||
|
||||
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.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Test cases for {@link ParamFlowSlot}.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public class ParamFlowSlotTest {
|
||||
|
||||
private final ParamFlowSlot paramFlowSlot = new ParamFlowSlot();
|
||||
|
||||
@Test
|
||||
public void testEntryWhenParamFlowRuleNotExists() throws Throwable {
|
||||
String resourceName = "testEntryWhenParamFlowRuleNotExists";
|
||||
ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN);
|
||||
paramFlowSlot.entry(null, resourceWrapper, null, 1, "abc");
|
||||
// The parameter metric instance will not be created.
|
||||
assertNull(ParamFlowSlot.getParamMetric(resourceWrapper));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEntryWhenParamFlowExists() throws Throwable {
|
||||
String resourceName = "testEntryWhenParamFlowExists";
|
||||
ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN);
|
||||
long argToGo = 1L;
|
||||
double count = 10;
|
||||
ParamFlowRule rule = new ParamFlowRule(resourceName)
|
||||
.setCount(count)
|
||||
.setParamIdx(0);
|
||||
ParamFlowRuleManager.loadRules(Collections.singletonList(rule));
|
||||
|
||||
ParameterMetric metric = mock(ParameterMetric.class);
|
||||
// First pass, then blocked.
|
||||
when(metric.getPassParamQps(rule.getParamIdx(), argToGo))
|
||||
.thenReturn(count - 1)
|
||||
.thenReturn(count);
|
||||
// Insert the mock metric to control pass or block.
|
||||
ParamFlowSlot.getMetricsMap().put(resourceWrapper, metric);
|
||||
|
||||
// The first entry will pass.
|
||||
paramFlowSlot.entry(null, resourceWrapper, null, 1, argToGo);
|
||||
// The second entry will be blocked.
|
||||
try {
|
||||
paramFlowSlot.entry(null, resourceWrapper, null, 1, argToGo);
|
||||
} catch (ParamFlowException ex) {
|
||||
assertEquals(String.valueOf(argToGo), ex.getMessage());
|
||||
assertEquals(resourceName, ex.getResourceName());
|
||||
return;
|
||||
}
|
||||
fail("The second entry should be blocked");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetNullParamMetric() {
|
||||
assertNull(ParamFlowSlot.getParamMetric(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitParamMetrics() {
|
||||
int index = 1;
|
||||
String resourceName = "res-" + System.currentTimeMillis();
|
||||
ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN);
|
||||
|
||||
assertNull(ParamFlowSlot.getParamMetric(resourceWrapper));
|
||||
|
||||
paramFlowSlot.initHotParamMetricsFor(resourceWrapper, index);
|
||||
ParameterMetric metric = ParamFlowSlot.getParamMetric(resourceWrapper);
|
||||
assertNotNull(metric);
|
||||
assertNotNull(metric.getRollingParameters().get(index));
|
||||
|
||||
// Duplicate init.
|
||||
paramFlowSlot.initHotParamMetricsFor(resourceWrapper, index);
|
||||
assertSame(metric, ParamFlowSlot.getParamMetric(resourceWrapper));
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
ParamFlowRuleManager.loadRules(null);
|
||||
ParamFlowSlot.getMetricsMap().clear();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
// Clean the metrics map.
|
||||
ParamFlowSlot.getMetricsMap().clear();
|
||||
ParamFlowRuleManager.loadRules(null);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* 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.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.csp.sentinel.slots.statistic.metric.HotParameterLeapArray;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Test cases for {@link ParameterMetric}.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public class ParameterMetricTest {
|
||||
|
||||
@Test
|
||||
public void testGetTopParamCount() {
|
||||
ParameterMetric metric = new ParameterMetric();
|
||||
int index = 1;
|
||||
int n = 10;
|
||||
RollingParamEvent event = RollingParamEvent.REQUEST_PASSED;
|
||||
HotParameterLeapArray leapArray = mock(HotParameterLeapArray.class);
|
||||
Map<Object, Double> topValues = new HashMap<Object, Double>() {{
|
||||
put("a", 3d);
|
||||
put("b", 7d);
|
||||
}};
|
||||
when(leapArray.getTopValues(event, n)).thenReturn(topValues);
|
||||
|
||||
// Get when not initialized.
|
||||
assertEquals(0, metric.getTopPassParamCount(index, n).size());
|
||||
|
||||
metric.getRollingParameters().put(index, leapArray);
|
||||
assertEquals(topValues, metric.getTopPassParamCount(index, n));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitAndClearHotParameterMetric() {
|
||||
ParameterMetric metric = new ParameterMetric();
|
||||
int index = 1;
|
||||
metric.initializeForIndex(index);
|
||||
HotParameterLeapArray leapArray = metric.getRollingParameters().get(index);
|
||||
assertNotNull(leapArray);
|
||||
|
||||
metric.initializeForIndex(index);
|
||||
assertSame(leapArray, metric.getRollingParameters().get(index));
|
||||
|
||||
metric.clear();
|
||||
assertEquals(0, metric.getRollingParameters().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;
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* 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.metric;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.param.RollingParamEvent;
|
||||
import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap;
|
||||
import com.alibaba.csp.sentinel.slots.statistic.data.ParamMapBucket;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* Test cases for {@link HotParameterLeapArray}.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public class HotParameterLeapArrayTest {
|
||||
|
||||
@Test
|
||||
public void testAddValueToBucket() {
|
||||
HotParameterLeapArray leapArray = mock(HotParameterLeapArray.class);
|
||||
String paramA = "paramA";
|
||||
int initialCountA = 3;
|
||||
RollingParamEvent passEvent = RollingParamEvent.REQUEST_PASSED;
|
||||
final ParamMapBucket bucket = new ParamMapBucket();
|
||||
bucket.add(passEvent, initialCountA, paramA);
|
||||
|
||||
doCallRealMethod().when(leapArray).addValue(any(RollingParamEvent.class), anyInt(), any(Object.class));
|
||||
when(leapArray.currentWindow()).thenReturn(new WindowWrap<ParamMapBucket>(0, 0, bucket));
|
||||
assertEquals(initialCountA, leapArray.currentWindow().value().get(passEvent, paramA));
|
||||
|
||||
int delta = 2;
|
||||
leapArray.addValue(passEvent, delta, paramA);
|
||||
assertEquals(initialCountA + delta, leapArray.currentWindow().value().get(passEvent, paramA));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTopValues() {
|
||||
int intervalInSec = 2;
|
||||
int a1 = 3, a2 = 5;
|
||||
String paramPrefix = "param-";
|
||||
HotParameterLeapArray leapArray = mock(HotParameterLeapArray.class);
|
||||
when(leapArray.getIntervalInSec()).thenReturn(intervalInSec);
|
||||
|
||||
final ParamMapBucket b1 = generateBucket(a1, paramPrefix);
|
||||
final ParamMapBucket b2 = generateBucket(a2, paramPrefix);
|
||||
List<ParamMapBucket> buckets = new ArrayList<ParamMapBucket>() {{
|
||||
add(b1);
|
||||
add(b2);
|
||||
}};
|
||||
when(leapArray.values()).thenReturn(buckets);
|
||||
when(leapArray.getTopValues(any(RollingParamEvent.class), any(int.class))).thenCallRealMethod();
|
||||
|
||||
Map<Object, Double> top2Values = leapArray.getTopValues(RollingParamEvent.REQUEST_PASSED, a1 - 1);
|
||||
// Top 2 should be 5 and 3
|
||||
assertEquals((double)5 * 10 / intervalInSec, top2Values.get(paramPrefix + 5), 0.01);
|
||||
assertEquals((double)3 * 20 / intervalInSec, top2Values.get(paramPrefix + 3), 0.01);
|
||||
|
||||
Map<Object, Double> top4Values = leapArray.getTopValues(RollingParamEvent.REQUEST_PASSED, a2 - 1);
|
||||
assertEquals(a2 - 1, top4Values.size());
|
||||
assertFalse(top4Values.containsKey(paramPrefix + 1));
|
||||
|
||||
Map<Object, Double> topMoreValues = leapArray.getTopValues(RollingParamEvent.REQUEST_PASSED, a2 + 1);
|
||||
assertEquals("This should contain all parameters but no more than " + a2, a2, topMoreValues.size());
|
||||
}
|
||||
|
||||
private ParamMapBucket generateBucket(int amount, String prefix) {
|
||||
if (amount <= 0) {
|
||||
throw new IllegalArgumentException("Bad amount");
|
||||
}
|
||||
ParamMapBucket bucket = new ParamMapBucket();
|
||||
for (int i = 1; i <= amount; i++) {
|
||||
bucket.add(RollingParamEvent.REQUEST_PASSED, i * 10, prefix + i);
|
||||
bucket.add(RollingParamEvent.REQUEST_BLOCKED, i, prefix + i);
|
||||
}
|
||||
return bucket;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRollingSum() {
|
||||
HotParameterLeapArray leapArray = mock(HotParameterLeapArray.class);
|
||||
String v1 = "a", v2 = "B", v3 = "Cc";
|
||||
int p1a = 19, p1b = 3;
|
||||
int p2a = 6, p2c = 17;
|
||||
RollingParamEvent passEvent = RollingParamEvent.REQUEST_PASSED;
|
||||
final ParamMapBucket b1 = new ParamMapBucket()
|
||||
.add(passEvent, p1a, v1)
|
||||
.add(passEvent, p1b, v2);
|
||||
final ParamMapBucket b2 = new ParamMapBucket()
|
||||
.add(passEvent, p2a, v1)
|
||||
.add(passEvent, p2c, v3);
|
||||
List<ParamMapBucket> buckets = new ArrayList<ParamMapBucket>() {{ add(b1); add(b2); }};
|
||||
when(leapArray.values()).thenReturn(buckets);
|
||||
when(leapArray.getRollingSum(any(RollingParamEvent.class), any(Object.class))).thenCallRealMethod();
|
||||
|
||||
assertEquals(p1a + p2a, leapArray.getRollingSum(passEvent, v1));
|
||||
assertEquals(p1b, leapArray.getRollingSum(passEvent, v2));
|
||||
assertEquals(p2c, leapArray.getRollingSum(passEvent, v3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRollingAvg() {
|
||||
HotParameterLeapArray leapArray = mock(HotParameterLeapArray.class);
|
||||
when(leapArray.getRollingSum(any(RollingParamEvent.class), any(Object.class))).thenReturn(15L);
|
||||
when(leapArray.getIntervalInSec()).thenReturn(1)
|
||||
.thenReturn(2);
|
||||
when(leapArray.getRollingAvg(any(RollingParamEvent.class), any(Object.class))).thenCallRealMethod();
|
||||
|
||||
assertEquals(15.0d, leapArray.getRollingAvg(RollingParamEvent.REQUEST_PASSED, "abc"), 0.001);
|
||||
assertEquals(15.0d / 2, leapArray.getRollingAvg(RollingParamEvent.REQUEST_PASSED, "abc"), 0.001);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue