Skip to content

Commit a524ab3

Browse files
authored
Resource rules (flow/degrade/param/authority) support regex matching (#3251)
1 parent fb7d85d commit a524ab3

File tree

16 files changed

+641
-104
lines changed

16 files changed

+641
-104
lines changed

sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/AbstractRule.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package com.alibaba.csp.sentinel.slots.block;
1717

18+
import java.util.Objects;
19+
1820
/**
1921
* Abstract rule entity.
2022
*
@@ -44,6 +46,11 @@ public abstract class AbstractRule implements Rule {
4446
*/
4547
private String limitApp;
4648

49+
/**
50+
* Whether to match resource names according to regular rules
51+
*/
52+
private boolean regex;
53+
4754
public Long getId() {
4855
return id;
4956
}
@@ -72,6 +79,15 @@ public AbstractRule setLimitApp(String limitApp) {
7279
return this;
7380
}
7481

82+
public boolean isRegex() {
83+
return regex;
84+
}
85+
86+
public AbstractRule setRegex(boolean regex) {
87+
this.regex = regex;
88+
return this;
89+
}
90+
7591
@Override
7692
public boolean equals(Object o) {
7793
if (this == o) {
@@ -83,7 +99,10 @@ public boolean equals(Object o) {
8399

84100
AbstractRule that = (AbstractRule)o;
85101

86-
if (resource != null ? !resource.equals(that.resource) : that.resource != null) {
102+
if (!Objects.equals(resource, that.resource)) {
103+
return false;
104+
}
105+
if (regex != that.regex) {
87106
return false;
88107
}
89108
if (!limitAppEquals(limitApp, that.limitApp)) {
@@ -114,6 +133,7 @@ public int hashCode() {
114133
if (!("".equals(limitApp) || RuleConstant.LIMIT_APP_DEFAULT.equals(limitApp) || limitApp == null)) {
115134
result = 31 * result + limitApp.hashCode();
116135
}
136+
result = 31 * result + (regex ? 1 : 0);
117137
return result;
118138
}
119139
}
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
/*
2+
* Copyright 1999-2018 Alibaba Group Holding Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.alibaba.csp.sentinel.slots.block;
17+
18+
import com.alibaba.csp.sentinel.util.function.Function;
19+
import com.alibaba.csp.sentinel.util.function.Predicate;
20+
21+
import java.util.*;
22+
import java.util.regex.Pattern;
23+
24+
/**
25+
* Unified rule management tool, mainly used for matching and caching of regular rules and simple rules.
26+
* @author quguai
27+
* @date 2023/10/9 20:35
28+
*/
29+
public class RuleManager<R> {
30+
31+
private Map<String, List<R>> originalRules = new HashMap<>();
32+
private Map<Pattern, List<R>> regexRules = new HashMap<>();
33+
private Map<String, List<R>> regexCacheRules = new HashMap<>();
34+
private Map<String, List<R>> simpleRules = new HashMap<>();
35+
private Function<List<R>, List<R>> generator = Function.identity();
36+
37+
private final Predicate<R> predicate;
38+
39+
public RuleManager() {
40+
predicate = r -> r instanceof AbstractRule && ((AbstractRule) r).isRegex();
41+
}
42+
43+
public RuleManager(Function<List<R>, List<R>> generator, Predicate<R> predicate) {
44+
this.generator = generator;
45+
this.predicate = predicate;
46+
}
47+
48+
/**
49+
* Update rules from datasource, split rules map by regex,
50+
* rebuild the regex rule cache to reduce the performance loss caused by publish rules.
51+
*
52+
* @param rulesMap origin rules map
53+
*/
54+
public void updateRules(Map<String, List<R>> rulesMap) {
55+
originalRules = rulesMap;
56+
Map<Pattern, List<R>> regexRules = new HashMap<>();
57+
Map<String, List<R>> simpleRules = new HashMap<>();
58+
for (Map.Entry<String, List<R>> entry : rulesMap.entrySet()) {
59+
String resource = entry.getKey();
60+
List<R> rules = entry.getValue();
61+
62+
List<R> rulesOfSimple = new ArrayList<>();
63+
List<R> rulesOfRegex = new ArrayList<>();
64+
for (R rule : rules) {
65+
if (predicate.test(rule)) {
66+
rulesOfRegex.add(rule);
67+
} else {
68+
rulesOfSimple.add(rule);
69+
}
70+
}
71+
if (!rulesOfRegex.isEmpty()) {
72+
regexRules.put(Pattern.compile(resource), rulesOfRegex);
73+
}
74+
if (!rulesOfSimple.isEmpty()) {
75+
simpleRules.put(resource, rulesOfSimple);
76+
}
77+
}
78+
// rebuild regex cache rules
79+
setRules(regexRules, simpleRules);
80+
}
81+
82+
/**
83+
* Get rules by resource name, save the rule list after regular matching to improve performance
84+
* @param resource resource name
85+
* @return matching rule list
86+
*/
87+
public List<R> getRules(String resource) {
88+
List<R> result = new ArrayList<>(simpleRules.getOrDefault(resource, Collections.emptyList()));
89+
if (regexRules.isEmpty()) {
90+
return result;
91+
}
92+
if (regexCacheRules.containsKey(resource)) {
93+
result.addAll(regexCacheRules.get(resource));
94+
return result;
95+
}
96+
synchronized (this) {
97+
if (regexCacheRules.containsKey(resource)) {
98+
result.addAll(regexCacheRules.get(resource));
99+
return result;
100+
}
101+
List<R> compilers = matcherFromRegexRules(resource);
102+
regexCacheRules.put(resource, compilers);
103+
result.addAll(compilers);
104+
return result;
105+
}
106+
}
107+
108+
/**
109+
* Get rules from regex rules and simple rules
110+
* @return rule list
111+
*/
112+
public List<R> getRules() {
113+
List<R> rules = new ArrayList<>();
114+
for (Map.Entry<Pattern, List<R>> entry : regexRules.entrySet()) {
115+
rules.addAll(entry.getValue());
116+
}
117+
for (Map.Entry<String, List<R>> entry : simpleRules.entrySet()) {
118+
rules.addAll(entry.getValue());
119+
}
120+
return rules;
121+
}
122+
123+
/**
124+
* Get origin rules, includes regex and simple rules
125+
* @return original rules
126+
*/
127+
public Map<String, List<R>> getOriginalRules() {
128+
return originalRules;
129+
}
130+
131+
/**
132+
* Determine whether has rule based on the resource name
133+
* @param resource resource name
134+
* @return whether
135+
*/
136+
137+
public boolean hasConfig(String resource) {
138+
if (resource == null) {
139+
return false;
140+
}
141+
return !getRules(resource).isEmpty();
142+
}
143+
144+
/**
145+
* Is valid regex rules
146+
* @param rule rule
147+
* @return weather valid regex rule
148+
*/
149+
public static boolean checkRegexResourceField(AbstractRule rule) {
150+
if (!rule.isRegex()) {
151+
return true;
152+
}
153+
String resourceName = rule.getResource();
154+
try {
155+
Pattern.compile(resourceName);
156+
return true;
157+
} catch (Exception e) {
158+
return false;
159+
}
160+
}
161+
162+
private List<R> matcherFromRegexRules(String resource) {
163+
List<R> compilers = new ArrayList<>();
164+
for (Map.Entry<Pattern, List<R>> entry : regexRules.entrySet()) {
165+
if (entry.getKey().matcher(resource).matches()) {
166+
compilers.addAll(generator.apply(entry.getValue()));
167+
}
168+
}
169+
return compilers;
170+
}
171+
172+
private synchronized void setRules(Map<Pattern, List<R>> regexRules, Map<String, List<R>> simpleRules) {
173+
this.regexRules = regexRules;
174+
this.simpleRules = simpleRules;
175+
if (regexRules.isEmpty()) {
176+
this.regexCacheRules = Collections.emptyMap();
177+
return;
178+
}
179+
// rebuild from regex cache rules
180+
Map<String, List<R>> rebuildCacheRule = new HashMap<>(regexCacheRules.size());
181+
for (String resource : regexCacheRules.keySet()) {
182+
rebuildCacheRule.put(resource, matcherFromRegexRules(resource));
183+
}
184+
this.regexCacheRules = rebuildCacheRule;
185+
}
186+
}

sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/authority/AuthorityRuleManager.java

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
import com.alibaba.csp.sentinel.log.RecordLog;
2626
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
27+
import com.alibaba.csp.sentinel.slots.block.RuleManager;
2728
import com.alibaba.csp.sentinel.util.AssertUtil;
2829
import com.alibaba.csp.sentinel.util.StringUtil;
2930
import com.alibaba.csp.sentinel.property.DynamicSentinelProperty;
@@ -39,7 +40,7 @@
3940
*/
4041
public final class AuthorityRuleManager {
4142

42-
private static volatile Map<String, Set<AuthorityRule>> authorityRules = new ConcurrentHashMap<>();
43+
private static volatile RuleManager<AuthorityRule> authorityRules = new RuleManager<>();
4344

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

7273
public static boolean hasConfig(String resource) {
73-
return authorityRules.containsKey(resource);
74+
return authorityRules.hasConfig(resource);
7475
}
7576

7677
/**
@@ -79,34 +80,27 @@ public static boolean hasConfig(String resource) {
7980
* @return a new copy of the rules.
8081
*/
8182
public static List<AuthorityRule> getRules() {
82-
List<AuthorityRule> rules = new ArrayList<>();
83-
if (authorityRules == null) {
84-
return rules;
85-
}
86-
for (Map.Entry<String, Set<AuthorityRule>> entry : authorityRules.entrySet()) {
87-
rules.addAll(entry.getValue());
88-
}
89-
return rules;
83+
return authorityRules.getRules();
9084
}
9185

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

9488
@Override
9589
public synchronized void configLoad(List<AuthorityRule> value) {
96-
authorityRules = loadAuthorityConf(value);
90+
authorityRules.updateRules(loadAuthorityConf(value));
9791

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

10195
@Override
10296
public synchronized void configUpdate(List<AuthorityRule> conf) {
103-
authorityRules = loadAuthorityConf(conf);
104-
97+
authorityRules.updateRules(loadAuthorityConf(conf));
98+
10599
RecordLog.info("[AuthorityRuleManager] Authority rules received: {}", authorityRules);
106100
}
107101

108-
private Map<String, Set<AuthorityRule>> loadAuthorityConf(List<AuthorityRule> list) {
109-
Map<String, Set<AuthorityRule>> newRuleMap = new ConcurrentHashMap<>();
102+
private Map<String, List<AuthorityRule>> loadAuthorityConf(List<AuthorityRule> list) {
103+
Map<String, List<AuthorityRule>> newRuleMap = new ConcurrentHashMap<>();
110104

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

125119
String identity = rule.getResource();
126-
Set<AuthorityRule> ruleSet = newRuleMap.get(identity);
120+
List<AuthorityRule> ruleSet = newRuleMap.get(identity);
127121
// putIfAbsent
128122
if (ruleSet == null) {
129-
ruleSet = new HashSet<>();
123+
ruleSet = new ArrayList<>();
130124
ruleSet.add(rule);
131125
newRuleMap.put(identity, ruleSet);
132126
} else {
@@ -140,12 +134,12 @@ private Map<String, Set<AuthorityRule>> loadAuthorityConf(List<AuthorityRule> li
140134

141135
}
142136

143-
static Map<String, Set<AuthorityRule>> getAuthorityRules() {
144-
return authorityRules;
137+
static List<AuthorityRule> getRules(String resource) {
138+
return authorityRules.getRules(resource);
145139
}
146140

147141
public static boolean isValidRule(AuthorityRule rule) {
148142
return rule != null && !StringUtil.isBlank(rule.getResource())
149-
&& rule.getStrategy() >= 0 && StringUtil.isNotBlank(rule.getLimitApp());
143+
&& rule.getStrategy() >= 0 && StringUtil.isNotBlank(rule.getLimitApp()) && RuleManager.checkRegexResourceField(rule);
150144
}
151145
}

sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/authority/AuthoritySlot.java

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@
1515
*/
1616
package com.alibaba.csp.sentinel.slots.block.authority;
1717

18-
import java.util.Map;
19-
import java.util.Set;
18+
import java.util.List;
2019

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

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

53-
if (authorityRules == null) {
54-
return;
55-
}
56-
57-
Set<AuthorityRule> rules = authorityRules.get(resource.getName());
51+
List<AuthorityRule> rules = AuthorityRuleManager.getRules(resource.getName());
5852
if (rules == null) {
5953
return;
6054
}

0 commit comments

Comments
 (0)