Skip to content

Commit

Permalink
Resource rules (flow/degrade/param/authority) support regex matching (a…
Browse files Browse the repository at this point in the history
  • Loading branch information
LiYangSir authored and z-soulx committed May 27, 2024
1 parent 5ab0f1a commit 2a3a00a
Show file tree
Hide file tree
Showing 16 changed files with 641 additions and 104 deletions.
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;
}
}
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

0 comments on commit 2a3a00a

Please sign in to comment.