core/isolation/rule_manager.go (180 lines of code) (raw):
// Copyright 1999-2020 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 isolation
import (
"reflect"
"sync"
"github.com/alibaba/sentinel-golang/logging"
"github.com/alibaba/sentinel-golang/util"
"github.com/pkg/errors"
)
var (
ruleMap = make(map[string][]*Rule)
rwMux = &sync.RWMutex{}
currentRules = make(map[string][]*Rule, 0)
updateRuleMux = new(sync.Mutex)
)
// LoadRules loads the given isolation rules to the rule manager, while all previous rules will be replaced.
// the first returned value indicates whether do real load operation, if the rules is the same with previous rules, return false
func LoadRules(rules []*Rule) (bool, error) {
resRulesMap := make(map[string][]*Rule, 16)
for _, rule := range rules {
resRules, exist := resRulesMap[rule.Resource]
if !exist {
resRules = make([]*Rule, 0, 1)
}
resRulesMap[rule.Resource] = append(resRules, rule)
}
updateRuleMux.Lock()
defer updateRuleMux.Unlock()
isEqual := reflect.DeepEqual(currentRules, resRulesMap)
if isEqual {
logging.Info("[Isolation] Load rules is the same with current rules, so ignore load operation.")
return false, nil
}
err := onRuleUpdate(resRulesMap)
return true, err
}
func onRuleUpdate(rawResRulesMap map[string][]*Rule) (err error) {
validResRulesMap := make(map[string][]*Rule, len(rawResRulesMap))
for res, rules := range rawResRulesMap {
validResRules := make([]*Rule, 0, len(rules))
for _, rule := range rules {
if err := IsValidRule(rule); err != nil {
logging.Warn("[Isolation onRuleUpdate] Ignoring invalid isolation rule", "rule", rule, "reason", err.Error())
continue
}
validResRules = append(validResRules, rule)
}
if len(validResRules) > 0 {
validResRulesMap[res] = validResRules
}
}
start := util.CurrentTimeNano()
rwMux.Lock()
ruleMap = validResRulesMap
rwMux.Unlock()
currentRules = rawResRulesMap
logging.Debug("[Isolation onRuleUpdate] Time statistic(ns) for updating isolation rule", "timeCost", util.CurrentTimeNano()-start)
logRuleUpdate(validResRulesMap)
return
}
// LoadRulesOfResource loads the given resource's isolation rules to the rule manager, while all previous resource's rules will be replaced.
// the first returned value indicates whether do real load operation, if the rules is the same with previous resource's rules, return false
func LoadRulesOfResource(res string, rules []*Rule) (bool, error) {
if len(res) == 0 {
return false, errors.New("empty resource")
}
updateRuleMux.Lock()
defer updateRuleMux.Unlock()
// clear resource rules
if len(rules) == 0 {
// clear resource's currentRules
delete(currentRules, res)
// clear ruleMap
rwMux.Lock()
delete(ruleMap, res)
rwMux.Unlock()
logging.Info("[Isolation] clear resource level rules", "resource", res)
return true, nil
}
// load resource level rules
isEqual := reflect.DeepEqual(currentRules[res], rules)
if isEqual {
logging.Info("[Isolation] Load resource level rules is the same with current resource level rules, so ignore load operation.")
return false, nil
}
err := onResourceRuleUpdate(res, rules)
return true, err
}
func onResourceRuleUpdate(res string, rawResRules []*Rule) (err error) {
validResRules := make([]*Rule, 0, len(rawResRules))
for _, rule := range rawResRules {
if err := IsValidRule(rule); err != nil {
logging.Warn("[Isolation onResourceRuleUpdate] Ignoring invalid isolation rule", "rule", rule, "reason", err.Error())
continue
}
validResRules = append(validResRules, rule)
}
start := util.CurrentTimeNano()
rwMux.Lock()
if len(validResRules) == 0 {
delete(ruleMap, res)
} else {
ruleMap[res] = validResRules
}
rwMux.Unlock()
currentRules[res] = rawResRules
logging.Debug("[Isolation onResourceRuleUpdate] Time statistic(ns) for updating isolation rule", "timeCost", util.CurrentTimeNano()-start)
logging.Info("[Isolation] load resource level rules", "resource", res, "validResRules", validResRules)
return nil
}
// ClearRules clears all the rules in isolation module.
func ClearRules() error {
_, err := LoadRules(nil)
return err
}
// ClearRulesOfResource clears resource level rules in isolation module.
func ClearRulesOfResource(res string) error {
_, err := LoadRulesOfResource(res, nil)
return err
}
// GetRules returns all the rules based on copy.
// It doesn't take effect for isolation module if user changes the rule.
func GetRules() []Rule {
rules := getRules()
ret := make([]Rule, 0, len(rules))
for _, rule := range rules {
ret = append(ret, *rule)
}
return ret
}
// GetRulesOfResource returns specific resource's rules based on copy.
// It doesn't take effect for isolation module if user changes the rule.
func GetRulesOfResource(res string) []Rule {
rules := getRulesOfResource(res)
ret := make([]Rule, 0, len(rules))
for _, rule := range rules {
ret = append(ret, *rule)
}
return ret
}
// getRules returns all the rules。Any changes of rules take effect for isolation module
// getRules is an internal interface.
func getRules() []*Rule {
rwMux.RLock()
defer rwMux.RUnlock()
return rulesFrom(ruleMap)
}
// getRulesOfResource returns specific resource's rules。Any changes of rules take effect for isolation module
// getRulesOfResource is an internal interface.
func getRulesOfResource(res string) []*Rule {
rwMux.RLock()
defer rwMux.RUnlock()
resRules, exist := ruleMap[res]
if !exist {
return nil
}
ret := make([]*Rule, 0, len(resRules))
for _, r := range resRules {
ret = append(ret, r)
}
return ret
}
func rulesFrom(m map[string][]*Rule) []*Rule {
rules := make([]*Rule, 0, 8)
if len(m) == 0 {
return rules
}
for _, rs := range m {
for _, r := range rs {
if r != nil {
rules = append(rules, r)
}
}
}
return rules
}
func logRuleUpdate(m map[string][]*Rule) {
rs := rulesFrom(m)
if len(rs) == 0 {
logging.Info("[IsolationRuleManager] Isolation rules were cleared")
} else {
logging.Info("[IsolationRuleManager] Isolation rules were loaded", "rules", rs)
}
}
// IsValidRule checks whether the given Rule is valid.
func IsValidRule(r *Rule) error {
if r == nil {
return errors.New("nil isolation rule")
}
if len(r.Resource) == 0 {
return errors.New("empty resource of isolation rule")
}
if r.MetricType != Concurrency {
return errors.Errorf("unsupported metric type: %d", r.MetricType)
}
if r.Threshold == 0 {
return errors.New("zero threshold")
}
return nil
}