-
Notifications
You must be signed in to change notification settings - Fork 469
/
circuit.go
executable file
·234 lines (197 loc) · 6.17 KB
/
circuit.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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
package hystrix
// Forked from github.com/afex/hystrix-go/hystrix
// Some parts of this file have been modified to make it functional in this package
import (
"errors"
"fmt"
"log"
"sync"
"sync/atomic"
"time"
"github.com/go-chassis/openlog"
)
// CircuitBreaker is created for each ExecutorPool to track whether requests
// should be attempted, or rejected if the Health of the circuit is too low.
type CircuitBreaker struct {
Name string
open bool
enabled bool
forceOpen bool
forceClosed bool
mutex *sync.RWMutex
openedOrLastTestedTime int64
executorPool *executorPool
Metrics *metricExchange
}
var (
// ErrCBNotExist occurs when no CircuitBreaker exists
ErrCBNotExist = errors.New("circuit breaker not exist")
)
var (
circuitBreakersMutex *sync.RWMutex
circuitBreakers map[string]*CircuitBreaker
)
func init() {
circuitBreakersMutex = &sync.RWMutex{}
circuitBreakers = make(map[string]*CircuitBreaker)
}
// IsCircuitBreakerOpen returns whether a circuitBreaker is open for an interface
func IsCircuitBreakerOpen(name string) (bool, error) {
circuitBreakersMutex.Lock()
defer circuitBreakersMutex.Unlock()
if c, ok := circuitBreakers[name]; ok {
return c.IsOpen(), nil
} else {
return false, ErrCBNotExist
}
}
// GetCircuit returns the circuit for the given command and whether this call created it.
func GetCircuit(name string) (*CircuitBreaker, bool, error) {
circuitBreakersMutex.RLock()
_, ok := circuitBreakers[name]
if !ok {
circuitBreakersMutex.RUnlock()
circuitBreakersMutex.Lock()
defer circuitBreakersMutex.Unlock()
// because we released the rlock before we obtained the exclusive lock,
// we need to double check that some other thread didn't beat us to
// creation.
if cb, ok := circuitBreakers[name]; ok {
return cb, false, nil
}
openlog.Info(fmt.Sprintf("new circuit [%s] is protecting your service", name))
circuitBreakers[name] = newCircuitBreaker(name)
} else {
defer circuitBreakersMutex.RUnlock()
}
return circuitBreakers[name], !ok, nil
}
func FlushByName(name string) {
circuitBreakersMutex.Lock()
defer circuitBreakersMutex.Unlock()
cb, ok := circuitBreakers[name]
if ok {
log.Println("Delete Circuit Breaker:", name)
cb.Metrics.Reset()
cb.executorPool.Metrics.Reset()
delete(circuitBreakers, name)
}
}
// Flush purges all circuit and metric information from memory.
func Flush() {
circuitBreakersMutex.Lock()
defer circuitBreakersMutex.Unlock()
for name, cb := range circuitBreakers {
cb.Metrics.Reset()
cb.executorPool.Metrics.Reset()
delete(circuitBreakers, name)
}
}
// newCircuitBreaker creates a CircuitBreaker with associated Health
func newCircuitBreaker(name string) *CircuitBreaker {
c := &CircuitBreaker{}
c.Name = name
c.Metrics = newMetricExchange(name, getSettings(name).MetricsConsumerNum)
c.executorPool = newExecutorPool(name)
c.mutex = &sync.RWMutex{}
//定制治理选项forceClosed
c.forceOpen = getSettings(name).ForceOpen
c.forceClosed = getSettings(name).ForceClose
c.enabled = getSettings(name).CircuitBreakerEnabled
return c
}
// toggleForceOpen allows manually causing the fallback logic for all instances
// of a given command.
func (circuit *CircuitBreaker) ToggleForceOpen(toggle bool) error {
circuit, _, err := GetCircuit(circuit.Name)
if err != nil {
return err
}
circuit.forceOpen = toggle
return nil
}
// IsOpen is called before any Command execution to check whether or
// not it should be attempted. An "open" circuit means it is disabled.
func (circuit *CircuitBreaker) IsOpen() bool {
circuit.mutex.RLock()
o := circuit.forceOpen || circuit.open
circuit.mutex.RUnlock()
if o {
return true
}
if uint64(circuit.Metrics.Requests().Sum(time.Now())) < getSettings(circuit.Name).RequestVolumeThreshold {
return false
}
if !circuit.Metrics.IsHealthy(time.Now()) {
// too many failures, open the circuit
circuit.setOpen()
return true
}
return false
}
// AllowRequest is checked before a command executes, ensuring that circuit state and metric health allow it.
// When the circuit is open, this call will occasionally return true to measure whether the external service
// has recovered.
func (circuit *CircuitBreaker) AllowRequest() bool {
if circuit.forceOpen {
return false
}
//如果不允许熔断,直接返回
if circuit.forceClosed {
return true
}
return !circuit.IsOpen() || circuit.allowSingleTest()
}
func (circuit *CircuitBreaker) allowSingleTest() bool {
circuit.mutex.RLock()
defer circuit.mutex.RUnlock()
now := time.Now().UnixNano()
openedOrLastTestedTime := atomic.LoadInt64(&circuit.openedOrLastTestedTime)
if circuit.open && now > openedOrLastTestedTime+getSettings(circuit.Name).SleepWindow.Nanoseconds() {
swapped := atomic.CompareAndSwapInt64(&circuit.openedOrLastTestedTime, openedOrLastTestedTime, now)
if swapped {
openlog.Warn(fmt.Sprintf("hystrix-go: allowing single test to possibly close circuit %v", circuit.Name))
}
return swapped
}
return false
}
func (circuit *CircuitBreaker) setOpen() {
circuit.mutex.Lock()
defer circuit.mutex.Unlock()
if circuit.open {
return
}
openlog.Info(fmt.Sprintf("hystrix-go: opening circuit %v", circuit.Name))
circuit.openedOrLastTestedTime = time.Now().UnixNano()
circuit.open = true
}
func (circuit *CircuitBreaker) setClose() {
circuit.mutex.Lock()
defer circuit.mutex.Unlock()
if !circuit.open {
return
}
openlog.Warn(fmt.Sprintf("hystrix-go: closing circuit %v", circuit.Name))
circuit.open = false
circuit.Metrics.Reset()
}
// ReportEvent records command Metrics for tracking recent error rates and exposing data to the dashboard.
func (circuit *CircuitBreaker) ReportEvent(eventTypes []string, start time.Time, runDuration time.Duration) error {
if len(eventTypes) == 0 {
return fmt.Errorf("no event types sent for Metrics")
}
if eventTypes[0] == "success" && circuit.open {
circuit.setClose()
}
select {
case circuit.Metrics.Updates <- &commandExecution{
Types: eventTypes,
Start: start,
RunDuration: runDuration,
}:
default:
return CircuitError{Message: fmt.Sprintf("Metrics channel (%v) is at capacity", circuit.Name)}
}
return nil
}