Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve the parameter flow module and refactor API gateway adapter common module #758

Merged
merged 4 commits into from
May 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions sentinel-adapter/sentinel-api-gateway-adapter-common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,15 @@
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public final class SentinelGatewayConstants {
public static final String GATEWAY_CONTEXT_ROUTE_PREFIX = "sentinel_gateway_context$$route$$";

public static final String GATEWAY_NOT_MATCH_PARAM = "$$not_match";
public static final String GATEWAY_DEFAULT_PARAM = "$D";

private SentinelGatewayConstants() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,25 +53,32 @@ public Object[] parseParameterFor(String resource, T request, Predicate<GatewayF
}
Set<GatewayFlowRule> gatewayRules = new HashSet<>();
Set<Boolean> predSet = new HashSet<>();
boolean hasNonParamRule = false;
for (GatewayFlowRule rule : GatewayRuleManager.getRulesForResource(resource)) {
if (rule.getParamItem() != null) {
gatewayRules.add(rule);
predSet.add(rulePredicate.test(rule));
} else {
hasNonParamRule = true;
}
}
if (gatewayRules.isEmpty()) {
if (!hasNonParamRule && gatewayRules.isEmpty()) {
return new Object[0];
}
if (predSet.size() != 1 || predSet.contains(false)) {
if (predSet.size() > 1 || predSet.contains(false)) {
return new Object[0];
}
Object[] arr = new Object[gatewayRules.size()];
int size = hasNonParamRule ? gatewayRules.size() + 1 : gatewayRules.size();
Object[] arr = new Object[size];
for (GatewayFlowRule rule : gatewayRules) {
GatewayParamFlowItem paramItem = rule.getParamItem();
int idx = paramItem.getIndex();
String param = parseInternal(paramItem, request);
arr[idx] = param;
}
if (hasNonParamRule) {
arr[size - 1] = SentinelGatewayConstants.GATEWAY_DEFAULT_PARAM;
}
return arr;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@ static FlowRule toFlowRule(/*@Valid*/ GatewayFlowRule rule) {
.setMaxQueueingTimeMs(rule.getMaxQueueingTimeoutMs());
}

static ParamFlowRule applyNonParamToParamRule(/*@Valid*/ GatewayFlowRule gatewayRule, int idx) {
return new ParamFlowRule(gatewayRule.getResource())
.setCount(gatewayRule.getCount())
.setGrade(gatewayRule.getGrade())
.setDurationInSec(gatewayRule.getIntervalSec())
.setBurstCount(gatewayRule.getBurst())
.setControlBehavior(gatewayRule.getControlBehavior())
.setMaxQueueingTimeMs(gatewayRule.getMaxQueueingTimeoutMs())
.setParamIdx(idx);
}

/**
* Convert a gateway rule to parameter flow rule, then apply the generated
* parameter index to {@link GatewayParamFlowItem} of the rule.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,9 @@
import com.alibaba.csp.sentinel.property.PropertyListener;
import com.alibaba.csp.sentinel.property.SentinelProperty;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
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.slots.block.flow.param.ParamFlowRuleUtil;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParameterMetricStorage;
import com.alibaba.csp.sentinel.util.AssertUtil;
import com.alibaba.csp.sentinel.util.StringUtil;

Expand All @@ -42,7 +41,12 @@
*/
public final class GatewayRuleManager {

private static final Map<String, Set<GatewayFlowRule>> RULE_MAP = new ConcurrentHashMap<>();
/**
* Gateway flow rule map: (resource, [rules...])
*/
private static final Map<String, Set<GatewayFlowRule>> GATEWAY_RULE_MAP = new ConcurrentHashMap<>();

private static final Map<String, List<ParamFlowRule>> CONVERTED_PARAM_RULE_MAP = new ConcurrentHashMap<>();

private static final GatewayRulePropertyListener LISTENER = new GatewayRulePropertyListener();
private static SentinelProperty<Set<GatewayFlowRule>> currentProperty = new DynamicSentinelProperty<>();
Expand Down Expand Up @@ -74,46 +78,69 @@ public static boolean loadRules(Set<GatewayFlowRule> rules) {

public static Set<GatewayFlowRule> getRules() {
Set<GatewayFlowRule> rules = new HashSet<>();
for (Set<GatewayFlowRule> ruleSet : RULE_MAP.values()) {
for (Set<GatewayFlowRule> ruleSet : GATEWAY_RULE_MAP.values()) {
rules.addAll(ruleSet);
}
return rules;
}

public static Set<GatewayFlowRule> getRulesForResource(String resourceName) {
AssertUtil.assertNotBlank(resourceName, "resourceName cannot be blank");
Set<GatewayFlowRule> set = RULE_MAP.get(resourceName);
if (StringUtil.isBlank(resourceName)) {
return new HashSet<>();
}
Set<GatewayFlowRule> set = GATEWAY_RULE_MAP.get(resourceName);
if (set == null) {
return new HashSet<>();
}
return new HashSet<>(set);
}

/**
* <p>Get all converted parameter rules.</p>
* <p>Note: caller SHOULD NOT modify the list and rules.</p>
*
* @param resourceName valid resource name
* @return converted parameter rules
*/
public static List<ParamFlowRule> getConvertedParamRules(String resourceName) {
if (StringUtil.isBlank(resourceName)) {
return new ArrayList<>();
}
return CONVERTED_PARAM_RULE_MAP.get(resourceName);
}

private static final class GatewayRulePropertyListener implements PropertyListener<Set<GatewayFlowRule>> {

@Override
public void configUpdate(Set<GatewayFlowRule> conf) {
applyGatewayRuleInternal(conf);
RecordLog.info("[GatewayRuleManager] Gateway flow rules received: " + RULE_MAP);
RecordLog.info("[GatewayRuleManager] Gateway flow rules received: " + GATEWAY_RULE_MAP);
}

@Override
public void configLoad(Set<GatewayFlowRule> conf) {
applyGatewayRuleInternal(conf);
RecordLog.info("[GatewayRuleManager] Gateway flow rules loaded: " + RULE_MAP);
RecordLog.info("[GatewayRuleManager] Gateway flow rules loaded: " + GATEWAY_RULE_MAP);
}

private int getIdxInternal(Map<String, Integer> idxMap, String resourceName) {
// Prepare index map.
if (!idxMap.containsKey(resourceName)) {
idxMap.put(resourceName, 0);
}
return idxMap.get(resourceName);
}

private synchronized void applyGatewayRuleInternal(Set<GatewayFlowRule> conf) {
if (conf == null || conf.isEmpty()) {
FlowRuleManager.loadRules(new ArrayList<FlowRule>());
ParamFlowRuleManager.loadRules(new ArrayList<ParamFlowRule>());
RULE_MAP.clear();
applyToConvertedParamMap(new HashSet<ParamFlowRule>());
GATEWAY_RULE_MAP.clear();
return;
}
Map<String, Set<GatewayFlowRule>> gatewayRuleMap = new ConcurrentHashMap<>();
Map<String, Integer> idxMap = new HashMap<>();
List<FlowRule> flowRules = new ArrayList<>();
Set<ParamFlowRule> paramFlowRules = new HashSet<>();
Map<String, List<GatewayFlowRule>> noParamMap = new HashMap<>();

for (GatewayFlowRule rule : conf) {
if (!isValidRule(rule)) {
Expand All @@ -122,14 +149,15 @@ private synchronized void applyGatewayRuleInternal(Set<GatewayFlowRule> conf) {
}
String resourceName = rule.getResource();
if (rule.getParamItem() == null) {
// If param item is absent, it will be converted to normal flow rule.
flowRules.add(GatewayRuleConverter.toFlowRule(rule));
} else {
// Prepare index map.
if (!idxMap.containsKey(resourceName)) {
idxMap.put(resourceName, 0);
// Cache the rules with no parameter config, then skip.
List<GatewayFlowRule> noParamList = noParamMap.get(resourceName);
if (noParamList == null) {
noParamList = new ArrayList<>();
noParamMap.put(resourceName, noParamList);
}
int idx = idxMap.get(resourceName);
noParamList.add(rule);
} else {
int idx = getIdxInternal(idxMap, resourceName);
// Convert to parameter flow rule.
if (paramFlowRules.add(GatewayRuleConverter.applyToParamRule(rule, idx))) {
idxMap.put(rule.getResource(), idx + 1);
Expand All @@ -143,11 +171,51 @@ private synchronized void applyGatewayRuleInternal(Set<GatewayFlowRule> conf) {
}
ruleSet.add(rule);
}
FlowRuleManager.loadRules(flowRules);
ParamFlowRuleManager.loadRules(new ArrayList<>(paramFlowRules));
// Handle non-param mode rules.
for (Map.Entry<String, List<GatewayFlowRule>> e : noParamMap.entrySet()) {
List<GatewayFlowRule> rules = e.getValue();
if (rules == null || rules.isEmpty()) {
continue;
}
for (GatewayFlowRule rule : rules) {
int idx = getIdxInternal(idxMap, e.getKey());
// Always use the same index (the last position).
paramFlowRules.add(GatewayRuleConverter.applyNonParamToParamRule(rule, idx));
}
}

applyToConvertedParamMap(paramFlowRules);

GATEWAY_RULE_MAP.clear();
GATEWAY_RULE_MAP.putAll(gatewayRuleMap);
}

private void applyToConvertedParamMap(Set<ParamFlowRule> paramFlowRules) {
Map<String, List<ParamFlowRule>> newRuleMap = ParamFlowRuleUtil.buildParamRuleMap(
new ArrayList<>(paramFlowRules));
if (newRuleMap == null || newRuleMap.isEmpty()) {
// No parameter flow rules, so clear all the metrics.
for (String resource : CONVERTED_PARAM_RULE_MAP.keySet()) {
ParameterMetricStorage.clearParamMetricForResource(resource);
}
RecordLog.info("[GatewayRuleManager] No gateway rules, clearing parameter metrics of previous rules");
CONVERTED_PARAM_RULE_MAP.clear();
return;
}

// Clear unused parameter metrics.
Set<String> previousResources = CONVERTED_PARAM_RULE_MAP.keySet();
for (String resource : previousResources) {
if (!newRuleMap.containsKey(resource)) {
ParameterMetricStorage.clearParamMetricForResource(resource);
}
}

// Apply to converted rule map.
CONVERTED_PARAM_RULE_MAP.clear();
CONVERTED_PARAM_RULE_MAP.putAll(newRuleMap);

RULE_MAP.clear();
RULE_MAP.putAll(gatewayRuleMap);
RecordLog.info("[GatewayRuleManager] Converted internal param rules: " + CONVERTED_PARAM_RULE_MAP);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright 1999-2019 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.adapter.gateway.common.slot;

import java.util.List;

import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.context.Context;
import com.alibaba.csp.sentinel.node.DefaultNode;
import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot;
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowChecker;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParameterMetricStorage;

/**
* @author Eric Zhao
* @since 1.6.1
*/
public class GatewayFlowSlot extends AbstractLinkedProcessorSlot<DefaultNode> {

@Override
public void entry(Context context, ResourceWrapper resource, DefaultNode node, int count,
boolean prioritized, Object... args) throws Throwable {
checkGatewayParamFlow(resource, count, args);

fireEntry(context, resource, node, count, prioritized, args);
}

private void checkGatewayParamFlow(ResourceWrapper resourceWrapper, int count, Object... args)
throws BlockException {
if (args == null) {
return;
}

List<ParamFlowRule> rules = GatewayRuleManager.getConvertedParamRules(resourceWrapper.getName());
if (rules == null || rules.isEmpty()) {
return;
}

for (ParamFlowRule rule : rules) {
// Initialize the parameter metrics.
ParameterMetricStorage.initParamMetricsFor(resourceWrapper, rule);

if (!ParamFlowChecker.passCheck(resourceWrapper, rule, count, args)) {
String triggeredParam = "";
if (args.length > rule.getParamIdx()) {
Object value = args[rule.getParamIdx()];
triggeredParam = String.valueOf(value);
}
throw new ParamFlowException(resourceWrapper.getName(), triggeredParam, rule);
}
}
}

@Override
public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
fireExit(context, resourceWrapper, count, args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 1999-2019 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.adapter.gateway.common.slot;

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 1.6.1
*/
public class GatewaySlotChainBuilder implements SlotChainBuilder {

@Override
public ProcessorSlotChain build() {
ProcessorSlotChain chain = new DefaultProcessorSlotChain();
// Prepare slot
chain.addLast(new NodeSelectorSlot());
chain.addLast(new ClusterBuilderSlot());
// Stat slot
chain.addLast(new LogSlot());
chain.addLast(new StatisticSlot());
// Rule checking slot
chain.addLast(new AuthoritySlot());
chain.addLast(new SystemSlot());
chain.addLast(new GatewayFlowSlot());

chain.addLast(new ParamFlowSlot());
chain.addLast(new FlowSlot());
chain.addLast(new DegradeSlot());

return chain;
}
}
Loading