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

resource rules (flow/degrade/param/authority) support regex matching #3251

Merged
merged 1 commit into from
Dec 27, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package com.alibaba.csp.sentinel.slots.block;

import java.util.Objects;

/**
* Abstract rule entity.
*
Expand Down Expand Up @@ -44,6 +46,11 @@ public abstract class AbstractRule implements Rule {
*/
private String limitApp;

/**
* Whether to match resource names according to regular rules
*/
private boolean regex;

public Long getId() {
return id;
}
Expand Down Expand Up @@ -72,6 +79,15 @@ public AbstractRule setLimitApp(String limitApp) {
return this;
}

public boolean isRegex() {
return regex;
}

public AbstractRule setRegex(boolean regex) {
this.regex = regex;
return this;
}

@Override
public boolean equals(Object o) {
if (this == o) {
Expand All @@ -83,7 +99,10 @@ public boolean equals(Object o) {

AbstractRule that = (AbstractRule)o;

if (resource != null ? !resource.equals(that.resource) : that.resource != null) {
if (!Objects.equals(resource, that.resource)) {
return false;
}
if (regex != that.regex) {
return false;
}
if (!limitAppEquals(limitApp, that.limitApp)) {
Expand Down Expand Up @@ -114,6 +133,7 @@ public int hashCode() {
if (!("".equals(limitApp) || RuleConstant.LIMIT_APP_DEFAULT.equals(limitApp) || limitApp == null)) {
result = 31 * result + limitApp.hashCode();
}
result = 31 * result + (regex ? 1 : 0);
return result;
}
}
LearningGp marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
/*
* 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;

import com.alibaba.csp.sentinel.util.function.Function;
import com.alibaba.csp.sentinel.util.function.Predicate;

import java.util.*;
import java.util.regex.Pattern;

/**
* Unified rule management tool, mainly used for matching and caching of regular rules and simple rules.
* @author quguai
* @date 2023/10/9 20:35
*/
public class RuleManager<R> {

private Map<String, List<R>> originalRules = new HashMap<>();
private Map<Pattern, List<R>> regexRules = new HashMap<>();
private Map<String, List<R>> regexCacheRules = new HashMap<>();
private Map<String, List<R>> simpleRules = new HashMap<>();
private Function<List<R>, List<R>> generator = Function.identity();

private final Predicate<R> predicate;

public RuleManager() {
predicate = r -> r instanceof AbstractRule && ((AbstractRule) r).isRegex();
}

public RuleManager(Function<List<R>, List<R>> generator, Predicate<R> predicate) {
this.generator = generator;
this.predicate = predicate;
}

/**
* Update rules from datasource, split rules map by regex,
* rebuild the regex rule cache to reduce the performance loss caused by publish rules.
*
* @param rulesMap origin rules map
*/
public void updateRules(Map<String, List<R>> rulesMap) {
originalRules = rulesMap;
Map<Pattern, List<R>> regexRules = new HashMap<>();
Map<String, List<R>> simpleRules = new HashMap<>();
for (Map.Entry<String, List<R>> entry : rulesMap.entrySet()) {
String resource = entry.getKey();
List<R> rules = entry.getValue();

List<R> rulesOfSimple = new ArrayList<>();
List<R> rulesOfRegex = new ArrayList<>();
for (R rule : rules) {
if (predicate.test(rule)) {
rulesOfRegex.add(rule);
} else {
rulesOfSimple.add(rule);
}
}
if (!rulesOfRegex.isEmpty()) {
regexRules.put(Pattern.compile(resource), rulesOfRegex);
}
if (!rulesOfSimple.isEmpty()) {
simpleRules.put(resource, rulesOfSimple);
}
}
// rebuild regex cache rules
setRules(regexRules, simpleRules);
}

/**
* Get rules by resource name, save the rule list after regular matching to improve performance
* @param resource resource name
* @return matching rule list
*/
public List<R> getRules(String resource) {
List<R> result = new ArrayList<>(simpleRules.getOrDefault(resource, Collections.emptyList()));
if (regexRules.isEmpty()) {
return result;
}
if (regexCacheRules.containsKey(resource)) {
result.addAll(regexCacheRules.get(resource));
return result;
}
synchronized (this) {
if (regexCacheRules.containsKey(resource)) {
result.addAll(regexCacheRules.get(resource));
return result;
}
List<R> compilers = matcherFromRegexRules(resource);
regexCacheRules.put(resource, compilers);
result.addAll(compilers);
return result;
}
}

/**
* Get rules from regex rules and simple rules
* @return rule list
*/
public List<R> getRules() {
List<R> rules = new ArrayList<>();
for (Map.Entry<Pattern, List<R>> entry : regexRules.entrySet()) {
rules.addAll(entry.getValue());
}
for (Map.Entry<String, List<R>> entry : simpleRules.entrySet()) {
rules.addAll(entry.getValue());
}
return rules;
}

/**
* Get origin rules, includes regex and simple rules
* @return original rules
*/
public Map<String, List<R>> getOriginalRules() {
return originalRules;
}

/**
* Determine whether has rule based on the resource name
* @param resource resource name
* @return whether
*/

public boolean hasConfig(String resource) {
if (resource == null) {
return false;
}
return !getRules(resource).isEmpty();
}

/**
* Is valid regex rules
* @param rule rule
* @return weather valid regex rule
*/
public static boolean checkRegexResourceField(AbstractRule rule) {
if (!rule.isRegex()) {
return true;
}
String resourceName = rule.getResource();
try {
Pattern.compile(resourceName);
return true;
} catch (Exception e) {
return false;
}
}

private List<R> matcherFromRegexRules(String resource) {
List<R> compilers = new ArrayList<>();
for (Map.Entry<Pattern, List<R>> entry : regexRules.entrySet()) {
if (entry.getKey().matcher(resource).matches()) {
compilers.addAll(generator.apply(entry.getValue()));
}
}
return compilers;
}

private synchronized void setRules(Map<Pattern, List<R>> regexRules, Map<String, List<R>> simpleRules) {
this.regexRules = regexRules;
this.simpleRules = simpleRules;
if (regexRules.isEmpty()) {
this.regexCacheRules = Collections.emptyMap();
return;
}
// rebuild from regex cache rules
Map<String, List<R>> rebuildCacheRule = new HashMap<>(regexCacheRules.size());
for (String resource : regexCacheRules.keySet()) {
rebuildCacheRule.put(resource, matcherFromRegexRules(resource));
}
this.regexCacheRules = rebuildCacheRule;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.RuleManager;
import com.alibaba.csp.sentinel.util.AssertUtil;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.csp.sentinel.property.DynamicSentinelProperty;
Expand All @@ -39,7 +40,7 @@
*/
public final class AuthorityRuleManager {

private static volatile Map<String, Set<AuthorityRule>> authorityRules = new ConcurrentHashMap<>();
private static volatile RuleManager<AuthorityRule> authorityRules = new RuleManager<>();

private static final RulePropertyListener LISTENER = new RulePropertyListener();
private static SentinelProperty<List<AuthorityRule>> currentProperty = new DynamicSentinelProperty<>();
Expand Down Expand Up @@ -70,7 +71,7 @@ public static void loadRules(List<AuthorityRule> rules) {
}

public static boolean hasConfig(String resource) {
return authorityRules.containsKey(resource);
return authorityRules.hasConfig(resource);
}

/**
Expand All @@ -79,34 +80,27 @@ public static boolean hasConfig(String resource) {
* @return a new copy of the rules.
*/
public static List<AuthorityRule> getRules() {
List<AuthorityRule> rules = new ArrayList<>();
if (authorityRules == null) {
return rules;
}
for (Map.Entry<String, Set<AuthorityRule>> entry : authorityRules.entrySet()) {
rules.addAll(entry.getValue());
}
return rules;
return authorityRules.getRules();
}

private static class RulePropertyListener implements PropertyListener<List<AuthorityRule>> {

@Override
public synchronized void configLoad(List<AuthorityRule> value) {
authorityRules = loadAuthorityConf(value);
authorityRules.updateRules(loadAuthorityConf(value));

RecordLog.info("[AuthorityRuleManager] Authority rules loaded: {}", authorityRules);
}

@Override
public synchronized void configUpdate(List<AuthorityRule> conf) {
authorityRules = loadAuthorityConf(conf);
authorityRules.updateRules(loadAuthorityConf(conf));

RecordLog.info("[AuthorityRuleManager] Authority rules received: {}", authorityRules);
}

private Map<String, Set<AuthorityRule>> loadAuthorityConf(List<AuthorityRule> list) {
Map<String, Set<AuthorityRule>> newRuleMap = new ConcurrentHashMap<>();
private Map<String, List<AuthorityRule>> loadAuthorityConf(List<AuthorityRule> list) {
Map<String, List<AuthorityRule>> newRuleMap = new ConcurrentHashMap<>();

if (list == null || list.isEmpty()) {
return newRuleMap;
Expand All @@ -123,10 +117,10 @@ private Map<String, Set<AuthorityRule>> loadAuthorityConf(List<AuthorityRule> li
}

String identity = rule.getResource();
Set<AuthorityRule> ruleSet = newRuleMap.get(identity);
List<AuthorityRule> ruleSet = newRuleMap.get(identity);
// putIfAbsent
if (ruleSet == null) {
ruleSet = new HashSet<>();
ruleSet = new ArrayList<>();
ruleSet.add(rule);
newRuleMap.put(identity, ruleSet);
} else {
Expand All @@ -140,12 +134,12 @@ private Map<String, Set<AuthorityRule>> loadAuthorityConf(List<AuthorityRule> li

}

static Map<String, Set<AuthorityRule>> getAuthorityRules() {
return authorityRules;
static List<AuthorityRule> getRules(String resource) {
return authorityRules.getRules(resource);
}

public static boolean isValidRule(AuthorityRule rule) {
return rule != null && !StringUtil.isBlank(rule.getResource())
&& rule.getStrategy() >= 0 && StringUtil.isNotBlank(rule.getLimitApp());
&& rule.getStrategy() >= 0 && StringUtil.isNotBlank(rule.getLimitApp()) && RuleManager.checkRegexResourceField(rule);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@
*/
package com.alibaba.csp.sentinel.slots.block.authority;

import java.util.Map;
import java.util.Set;
import java.util.List;

import com.alibaba.csp.sentinel.Constants;
import com.alibaba.csp.sentinel.context.Context;
Expand Down Expand Up @@ -48,13 +47,8 @@ public void exit(Context context, ResourceWrapper resourceWrapper, int count, Ob
}

void checkBlackWhiteAuthority(ResourceWrapper resource, Context context) throws AuthorityException {
Map<String, Set<AuthorityRule>> authorityRules = AuthorityRuleManager.getAuthorityRules();

if (authorityRules == null) {
return;
}

Set<AuthorityRule> rules = authorityRules.get(resource.getName());
List<AuthorityRule> rules = AuthorityRuleManager.getRules(resource.getName());
if (rules == null) {
return;
}
Expand Down
Loading