-
Notifications
You must be signed in to change notification settings - Fork 8
/
breaker.go
138 lines (118 loc) · 2.85 KB
/
breaker.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package breaker
import (
"strings"
"sync"
"github.com/cuigh/auxo/apm/breaker"
"github.com/cuigh/auxo/data"
"github.com/cuigh/auxo/errors"
"github.com/cuigh/auxo/log"
"github.com/cuigh/auxo/net/web"
"github.com/cuigh/auxo/util/cast"
)
const PkgName = "auxo.net.web.filter.breaker"
type SimpleBreaker struct {
Breaker *breaker.Breaker
Fallback web.HandlerFunc
}
func (sb *SimpleBreaker) Try(handler web.HandlerFunc, ctx web.Context, logger log.Logger) error {
if sb.Fallback == nil {
return sb.Breaker.Try(func() error {
return handler(ctx)
})
}
return sb.Breaker.Try(func() error {
return handler(ctx)
}, func(err error) error {
log.Get(PkgName).Debug("breaker > fallback for error: ", err)
return sb.Fallback(ctx)
})
}
func (sb *SimpleBreaker) Apply(next web.HandlerFunc) web.HandlerFunc {
logger := log.Get(PkgName)
return func(ctx web.Context) error {
return sb.Try(next, ctx, logger)
}
}
type AutoBreaker struct {
locker sync.Mutex
breakers map[string]*SimpleBreaker
}
func NewAuto() *AutoBreaker {
return &AutoBreaker{
breakers: make(map[string]*SimpleBreaker),
}
}
func (ab *AutoBreaker) Apply(next web.HandlerFunc) web.HandlerFunc {
logger := log.Get(PkgName)
return func(ctx web.Context) error {
// `breaker:"c=100,fallback"`
// `breaker:"f=100,fallback"`
// `breaker:"r=0.2,fallback"`
// `breaker:",fallback"`
// `breaker:"r=0.2"`
tag := ctx.Handler().Option("breaker")
if tag == "" {
return next(ctx)
}
sb, err := ab.getBreaker(ctx, tag)
if err != nil {
return err
}
return sb.Try(next, ctx, logger)
}
}
func (ab *AutoBreaker) getBreaker(ctx web.Context, tag string) (sb *SimpleBreaker, err error) {
name := ctx.Handler().Name()
sb = ab.breakers[name]
if sb != nil {
return
}
ab.locker.Lock()
defer ab.locker.Unlock()
sb = ab.breakers[name]
if sb != nil {
return
}
var (
args = strings.Split(tag, ",")
cond breaker.Condition
fallback string
)
switch len(args) {
case 1:
cond, err = ab.parseCondition(args[0])
case 2:
cond, err = ab.parseCondition(args[0])
fallback = args[1]
default:
return nil, errors.New("invalid breaker tag: " + tag)
}
if err != nil {
return nil, err
}
sb = &SimpleBreaker{
Breaker: breaker.NewBreaker(name, cond, breaker.Options{}),
}
if fallback != "" {
sb.Fallback = ctx.Server().Handler(fallback).Action()
}
ab.breakers[name] = sb
return sb, err
}
func (ab *AutoBreaker) parseCondition(s string) (cond breaker.Condition, err error) {
if s == "" {
return breaker.ErrorRate(0.5, 10), nil
}
// todo: validate condition
// c=100, f=100, r=0.2
option := data.ParseOption(s, "=")
switch option.Name {
case "r":
cond = breaker.ErrorRate(cast.ToFloat32(option.Value), 10)
case "f":
cond = breaker.ErrorCount(cast.ToUint32(option.Value))
case "c":
cond = breaker.ConsecutiveErrorCount(cast.ToUint32(option.Value))
}
return
}