-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsliding_window.go
94 lines (79 loc) · 1.93 KB
/
sliding_window.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
package ratelimit
import (
"context"
"fmt"
"time"
)
type SlidingWindowLimiter struct {
s Store[slidingWindowRecord]
}
const (
currentCountFieldTag = "current"
)
type slidingWindowRecord struct {
Start int64 `redis:"start"`
PrevCount int64 `redis:"prev"`
CurrentCount int64 `redis:"current"`
}
func NewSlidingWindowLimiter(s Store[slidingWindowRecord]) Limiter {
return &SlidingWindowLimiter{
s: s,
}
}
func (l *SlidingWindowLimiter) Allow(ctx context.Context, key string, limit *Limit) (*Result, error) {
if limit.Rate <= 0 {
return &Result{
Allowed: true,
}, nil
}
window, err := l.s.Get(ctx, key)
if err != nil || window.Start == 0 {
l.s.Set(ctx, key, &slidingWindowRecord{
Start: time.Now().UnixNano(),
CurrentCount: 1,
})
return &Result{
Allowed: true,
}, nil
}
now := time.Now().UnixNano()
windowLength := limit.Period.Nanoseconds()
if (now - window.Start) >= windowLength {
window.Start += windowLength
window.PrevCount = window.CurrentCount
window.CurrentCount = 0
err = l.s.Set(ctx, key, window)
if err != nil {
fmt.Println(err)
}
}
d := float64(windowLength-(now-window.Start)) / float64(windowLength)
currentCount := float64(window.PrevCount)*d + float64(window.CurrentCount)
if currentCount >= float64(limit.Rate) {
ttl := l.retryAfter(int64(limit.Rate), window.Start, now, windowLength, window.PrevCount, window.CurrentCount)
return &Result{
Allowed: false,
RetryAfter: time.Duration(ttl),
}, nil
} else {
err = l.s.Increment(ctx, key)
if err != nil {
fmt.Println(err)
}
return &Result{
Allowed: true,
}, nil
}
}
func (l *SlidingWindowLimiter) retryAfter(size, start, now, unit int64, preCount int64, curCount int64) int64 {
d := 1.
if preCount != 0 {
d -= float64(size-curCount) / float64(preCount)
}
x := d*float64(unit) + float64(start)
return int64(x) - now
}
type Result struct {
Allowed bool
RetryAfter time.Duration
}