-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrateLimiter.go
148 lines (137 loc) · 4.15 KB
/
rateLimiter.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
package golangUtil
import (
"net/http"
"sync"
"sync/atomic"
"time"
)
const SmallWindows int64 = 1 << 10
const TimeShift = 1e9
const WindowsSize int64 = 3 * TimeShift
const MaxRequestPerWindows = WindowsSize / TimeShift * 1 << 12
const RateLimitingError = "server is busy,please wait"
type slideWindowsLimiter struct {
permitsPerWindows int64
windows map[int64]int64
totalCount int64
lock sync.Mutex
once sync.Once
timestamp int64
smallWindowsDistance int64
windowsSize int64
clearFlag int32
subWindowsSize int64
cond *sync.Cond
options rateLimiterOptions
}
type rateLimiterOptions func(limiter *slideWindowsLimiter)
type MiddleWare func(http.Handler) http.Handler
func WithMaxPassingPerWindows(numbers int64) rateLimiterOptions {
return func(limiter *slideWindowsLimiter) {
limiter.permitsPerWindows = numbers
}
}
// WithSubWindowsNumber function represent you can set the sub windows number to control better the flow overflow in a very short time,but this operation can increase your time which handler the outdated windows
func WithSubWindowsNumber(numbers int64) rateLimiterOptions {
return func(limiter *slideWindowsLimiter) {
limiter.subWindowsSize = numbers
limiter.smallWindowsDistance = limiter.windowsSize / limiter.subWindowsSize
}
}
// WithWindowsSize function represent you can set windows size to promise the actual max Request numbers can‘t exceed the max request which you set in the period of time
func WithWindowsSize(times time.Duration) rateLimiterOptions {
return func(limiter *slideWindowsLimiter) {
limiter.windowsSize = times.Nanoseconds()
limiter.smallWindowsDistance = limiter.windowsSize / limiter.subWindowsSize
}
}
func init() {
slideLimiter = initDefaultLimiter()
deferCreateWindows(slideLimiter)
}
func getRateLimiterMiddleware(options ...rateLimiterOptions) (result *slideWindowsLimiter) {
result = initDefaultLimiter()
for i := 0; i < len(options); i++ {
options[i](result)
}
deferCreateWindows(result)
return result
}
func deferCreateWindows(limiter *slideWindowsLimiter) {
limiter.once.Do(func() {
var i int64 = 0
for ; i < limiter.subWindowsSize; i++ {
limiter.windows[i] = 0
}
})
}
func initDefaultLimiter() (result *slideWindowsLimiter) {
result = &slideWindowsLimiter{
permitsPerWindows: MaxRequestPerWindows,
// windows length is prime number may be can defeat conflict better
windows: make(map[int64]int64, SmallWindows+3),
timestamp: time.Now().UnixNano(),
lock: sync.Mutex{},
smallWindowsDistance: WindowsSize / SmallWindows,
windowsSize: WindowsSize,
subWindowsSize: SmallWindows,
}
result.cond = sync.NewCond(&result.lock)
return result
}
func RegistryRateLimiting(options ...rateLimiterOptions) MiddleWare {
middleware := getRateLimiterMiddleware(options...)
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
if middleware.TryAcquire() {
next.ServeHTTP(writer, request)
} else {
writer.WriteHeader(500)
writer.Write([]byte(RateLimitingError))
}
},
)
}
}
var slideLimiter *slideWindowsLimiter
func TryAcquire() bool {
return slideLimiter.TryAcquire()
}
func (s *slideWindowsLimiter) TryAcquire() bool {
var diff int64
s.lock.Lock()
for atomic.LoadInt32(&s.clearFlag) != 0 {
s.cond.Wait()
}
diff = time.Now().UnixNano() - s.timestamp
var index = diff / s.smallWindowsDistance
if diff <= s.windowsSize {
if s.totalCount < s.permitsPerWindows {
s.totalCount++
s.windows[index]++
s.lock.Unlock()
return true
} else {
s.lock.Unlock()
return false
}
} else {
if atomic.CompareAndSwapInt32(&s.clearFlag, 0, 1) {
go func() {
s.lock.Lock()
defer s.lock.Unlock()
s.timestamp += diff
var i int64 = 0
var invalidWindows = index % s.subWindowsSize
for ; i <= invalidWindows; i++ {
s.totalCount -= s.windows[i]
s.windows[i] = 0
}
atomic.CompareAndSwapInt32(&s.clearFlag, 1, 0)
s.cond.Broadcast()
}()
}
s.lock.Unlock()
}
return s.TryAcquire()
}