Skip to content

Commit c5b08d4

Browse files
authored
Add system adaptive rule implementation (alibaba#33)
Signed-off-by: Eric Zhao <[email protected]>
1 parent d2f6b9a commit c5b08d4

File tree

6 files changed

+338
-0
lines changed

6 files changed

+338
-0
lines changed

core/system/rule.go

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package system
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
)
7+
8+
type MetricType uint32
9+
10+
const (
11+
// Load represents system load1 in Linux/Unix.
12+
Load MetricType = iota
13+
// AvgRT represents the average response time of all inbound requests.
14+
AvgRT
15+
// Concurrency represents the concurrency of all inbound requests.
16+
Concurrency
17+
InboundQPS
18+
CpuUsage
19+
// MetricTypeSize indicates the enum size of MetricType.
20+
MetricTypeSize
21+
)
22+
23+
func (t MetricType) String() string {
24+
switch t {
25+
case Load:
26+
return "load"
27+
case AvgRT:
28+
return "avgRT"
29+
case Concurrency:
30+
return "concurrency"
31+
case InboundQPS:
32+
return "inboundQPS"
33+
case CpuUsage:
34+
return "cpuUsage"
35+
default:
36+
return fmt.Sprintf("unknown(%d)", t)
37+
}
38+
}
39+
40+
type AdaptiveStrategy int32
41+
42+
const (
43+
NoAdaptive AdaptiveStrategy = -1
44+
BBR AdaptiveStrategy = iota
45+
)
46+
47+
func (t AdaptiveStrategy) String() string {
48+
switch t {
49+
case NoAdaptive:
50+
return "none"
51+
case BBR:
52+
return "bbr"
53+
default:
54+
return fmt.Sprintf("unknown(%d)", t)
55+
}
56+
}
57+
58+
type SystemRule struct {
59+
ID uint64 `json:"id,omitempty"`
60+
61+
MetricType MetricType `json:"metricType"`
62+
TriggerCount float64 `json:"count"`
63+
Strategy AdaptiveStrategy `json:"adaptiveStrategy"`
64+
}
65+
66+
func (r *SystemRule) String() string {
67+
b, err := json.Marshal(r)
68+
if err != nil {
69+
// Return the fallback string
70+
return fmt.Sprintf("SystemRule{metricType=%s, triggerCount=%.2f, adaptiveStrategy=%s}",
71+
r.MetricType, r.TriggerCount, r.Strategy)
72+
}
73+
return string(b)
74+
}
75+
76+
func (r *SystemRule) ResourceName() string {
77+
return r.MetricType.String()
78+
}

core/system/rule_manager.go

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package system
2+
3+
import (
4+
"github.com/sentinel-group/sentinel-golang/logging"
5+
"github.com/sentinel-group/sentinel-golang/util"
6+
"sync"
7+
)
8+
9+
type RuleMap map[MetricType][]*SystemRule
10+
11+
// const
12+
var (
13+
logger = logging.GetDefaultLogger()
14+
15+
ruleMap = make(RuleMap, 0)
16+
ruleMapMux = new(sync.RWMutex)
17+
18+
ruleChan = make(chan []*SystemRule, 10)
19+
propertyInit sync.Once
20+
)
21+
22+
func init() {
23+
propertyInit.Do(func() {
24+
initRuleRecvTask()
25+
})
26+
}
27+
28+
func initRuleRecvTask() {
29+
go util.RunWithRecover(func() {
30+
for {
31+
select {
32+
case rules := <-ruleChan:
33+
err := onRuleUpdate(rules)
34+
if err != nil {
35+
logger.Errorf("Failed to update system rules: %+v", err)
36+
}
37+
}
38+
}
39+
}, logger)
40+
}
41+
42+
func GetRules() []*SystemRule {
43+
ruleMapMux.RLock()
44+
defer ruleMapMux.RUnlock()
45+
46+
rules := make([]*SystemRule, 0)
47+
for _, rs := range ruleMap {
48+
rules = append(rules, rs...)
49+
}
50+
return rules
51+
}
52+
53+
// LoadRules loads given system rules to the rule manager, while all previous rules will be replaced.
54+
func LoadRules(rules []*SystemRule) (bool, error) {
55+
ruleChan <- rules
56+
return true, nil
57+
}
58+
59+
func onRuleUpdate(rules []*SystemRule) error {
60+
m := buildRuleMap(rules)
61+
62+
ruleMapMux.Lock()
63+
defer ruleMapMux.Unlock()
64+
65+
ruleMap = m
66+
return nil
67+
}
68+
69+
func buildRuleMap(rules []*SystemRule) RuleMap {
70+
if len(rules) == 0 {
71+
return make(RuleMap, 0)
72+
}
73+
m := make(RuleMap, 0)
74+
for _, rule := range rules {
75+
if !IsValidSystemRule(rule) {
76+
logger.Warnf("Ignoring invalid system rule: %v", rule)
77+
continue
78+
}
79+
rulesOfRes, exists := m[rule.MetricType]
80+
if !exists {
81+
m[rule.MetricType] = []*SystemRule{rule}
82+
} else {
83+
m[rule.MetricType] = append(rulesOfRes, rule)
84+
}
85+
}
86+
return m
87+
}
88+
89+
func IsValidSystemRule(rule *SystemRule) bool {
90+
if rule == nil || rule.TriggerCount < 0 || rule.MetricType >= MetricTypeSize {
91+
return false
92+
}
93+
if rule.MetricType == CpuUsage && rule.TriggerCount > 1 {
94+
return false
95+
}
96+
return true
97+
}

core/system/slot.go

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package system
2+
3+
import (
4+
"github.com/sentinel-group/sentinel-golang/core/base"
5+
"github.com/sentinel-group/sentinel-golang/core/stat"
6+
)
7+
8+
const SlotName = "SystemAdaptiveSlot"
9+
10+
type SystemAdaptiveSlot struct {
11+
}
12+
13+
func (s *SystemAdaptiveSlot) Check(ctx *base.EntryContext) *base.TokenResult {
14+
if ctx == nil || ctx.Resource == nil || ctx.Resource.FlowType() != base.Inbound {
15+
return base.NewTokenResultPass()
16+
}
17+
rules := GetRules()
18+
for _, rule := range rules {
19+
passed, m := s.doCheckRule(rule)
20+
if passed {
21+
continue
22+
}
23+
return base.NewTokenResultBlockedWithCause(base.BlockTypeSystemFlow, base.BlockTypeSystemFlow.String(), rule, m)
24+
}
25+
return base.NewTokenResultPass()
26+
}
27+
28+
func (s *SystemAdaptiveSlot) doCheckRule(rule *SystemRule) (bool, float64) {
29+
threshold := rule.TriggerCount
30+
switch rule.MetricType {
31+
case InboundQPS:
32+
qps := stat.InboundNode().GetQPS(base.MetricEventPass)
33+
res := qps < threshold
34+
return res, qps
35+
case Concurrency:
36+
n := float64(stat.InboundNode().CurrentGoroutineNum())
37+
res := n < threshold
38+
return res, n
39+
case AvgRT:
40+
rt := stat.InboundNode().AvgRT()
41+
res := rt < threshold
42+
return res, rt
43+
case Load:
44+
l := CurrentLoad()
45+
if l > threshold {
46+
if rule.Strategy != BBR || !checkBbrSimple() {
47+
return false, l
48+
}
49+
}
50+
return true, l
51+
case CpuUsage:
52+
c := CurrentCpuUsage()
53+
if c > threshold {
54+
if rule.Strategy != BBR || !checkBbrSimple() {
55+
return false, c
56+
}
57+
}
58+
return true, c
59+
default:
60+
return true, 0
61+
}
62+
}
63+
64+
func checkBbrSimple() bool {
65+
concurrency := stat.InboundNode().CurrentGoroutineNum()
66+
minRt := stat.InboundNode().MinRT()
67+
maxComplete := stat.InboundNode().GetMaxAvg(base.MetricEventComplete)
68+
if concurrency > 1 && float64(concurrency) > maxComplete*minRt/1000 {
69+
return false
70+
}
71+
return true
72+
}
73+
74+
func (s *SystemAdaptiveSlot) String() string {
75+
return SlotName
76+
}

core/system/sys_stat.go

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package system
2+
3+
import (
4+
"github.com/sentinel-group/sentinel-golang/util"
5+
"sync/atomic"
6+
"time"
7+
8+
"github.com/shirou/gopsutil/cpu"
9+
"github.com/shirou/gopsutil/load"
10+
)
11+
12+
const (
13+
notRetrievedValue float64 = -1
14+
)
15+
16+
var (
17+
currentLoad atomic.Value
18+
currentCpuUsage atomic.Value
19+
20+
ssStopChan = make(chan struct{})
21+
)
22+
23+
func init() {
24+
currentLoad.Store(notRetrievedValue)
25+
currentCpuUsage.Store(notRetrievedValue)
26+
27+
ticker := time.NewTicker(1 * time.Second)
28+
go util.RunWithRecover(func() {
29+
for {
30+
select {
31+
case <-ticker.C:
32+
retrieveAndUpdateSystemStat()
33+
case <-ssStopChan:
34+
ticker.Stop()
35+
return
36+
}
37+
}
38+
}, logger)
39+
}
40+
41+
func retrieveAndUpdateSystemStat() {
42+
cpuStats, err := cpu.Times(false)
43+
if err != nil {
44+
logger.Warnf("Failed to retrieve current CPU usage: %+v", err)
45+
}
46+
loadStat, err := load.Avg()
47+
if err != nil {
48+
logger.Warnf("Failed to retrieve current system load: %+v", err)
49+
}
50+
if len(cpuStats) > 0 {
51+
// TODO: calculate the real CPU usage
52+
// cpuStat := cpuStats[0]
53+
// currentCpuUsage.Store(cpuStat.User)
54+
}
55+
if loadStat != nil {
56+
currentLoad.Store(loadStat.Load1)
57+
}
58+
}
59+
60+
func CurrentLoad() float64 {
61+
r, ok := currentLoad.Load().(float64)
62+
if !ok {
63+
return notRetrievedValue
64+
}
65+
return r
66+
}
67+
68+
func CurrentCpuUsage() float64 {
69+
r, ok := currentCpuUsage.Load().(float64)
70+
if !ok {
71+
return notRetrievedValue
72+
}
73+
return r
74+
}

go.mod

+5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ module github.com/sentinel-group/sentinel-golang
33
go 1.13
44

55
require (
6+
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
7+
github.com/go-ole/go-ole v1.2.4 // indirect
68
github.com/pkg/errors v0.8.1
9+
github.com/shirou/gopsutil v2.19.12+incompatible
710
github.com/stretchr/testify v1.4.0
11+
golang.org/x/sys v0.0.0-20200107162124-548cf772de50 // indirect
12+
gopkg.in/yaml.v2 v2.2.2
813
)

go.sum

+8
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
1+
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
2+
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
13
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
24
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5+
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
6+
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
37
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
48
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
59
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
610
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
11+
github.com/shirou/gopsutil v2.19.12+incompatible h1:WRstheAymn1WOPesh+24+bZKFkqrdCR8JOc77v4xV3Q=
12+
github.com/shirou/gopsutil v2.19.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
713
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
814
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
915
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
1016
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
17+
golang.org/x/sys v0.0.0-20200107162124-548cf772de50 h1:YvQ10rzcqWXLlJZ3XCUoO25savxmscf4+SC+ZqiCHhA=
18+
golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1119
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
1220
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
1321
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=

0 commit comments

Comments
 (0)