blob: 75a3a6f71385b14e76f8a4a11028bd6abe34a940 [file] [log] [blame] [view]
Andrew Gerrand5bc444d2014-12-10 11:35:11 +11001# Rate Limiting
2
3## Time Tick based Approach
4
5To limit the rate of operations per unit time, use time.Tick:
6
jim-slattery-rs7d05df02014-12-22 09:57:57 -08007```go
Andrew Gerrand5bc444d2014-12-10 11:35:11 +11008import "time"
9
10rate_per_sec := 10
11throttle := time.Tick(1e9 / rate_per_sec)
12for req := range requests {
13 <-throttle // rate limit our Service.Method RPCs
14 go client.Call("Service.Method", req, ...)
15}
16```
17
18To allow some bursts, add a buffer to the throttle:
jim-slattery-rs7d05df02014-12-22 09:57:57 -080019```go
Andrew Gerrand5bc444d2014-12-10 11:35:11 +110020import "time"
21
22rate_per_sec := 10
23burst_limit := 100
24tick := time.NewTicker(1e9 / rate_per_sec)
25defer tick.Stop()
26throttle := make(chan int64, burst_limit)
27go func() {
28 for ns := range tick {
29 select {
30 case: throttle <- ns
31 default:
32 }
33 } // exits after tick.Stop()
34}()
35for req := range requests {
36 <-throttle // rate limit our Service.Method RPCs
37 go client.Call("Service.Method", req, ...)
38}
39```
40
41## Simpler Approach that doesn't need channels or go routines
42
43Here is a simpler approach that relies on the notion of elapsed time to provide rate control. It is implemented as a package so others can readily use it.
44
45
46
jim-slattery-rs7d05df02014-12-22 09:57:57 -080047```go
Andrew Gerrand5bc444d2014-12-10 11:35:11 +110048//
49// Ratelimiting incoming connections - Small Library
50//
51// (c) 2013 Sudhi Herle <sudhi-dot-herle-at-gmail-com>
52//
53// License: GPLv2
54//
55// Notes:
56// - This is a very simple interface for rate limiting. It
57// implements a token bucket algorithm
58// - Based on Anti Huimaa's very clever token bucket algorithm.
59//
60// Usage:
61// rl = NewRateLimiter(rate)
62//
63// ....
64// if rl.Limit() {
65// drop_connection(conn)
66// }
67//
68package ratelimit
Dave Day0d6986a2014-12-10 15:02:18 +110069
Andrew Gerrand5bc444d2014-12-10 11:35:11 +110070import "time"
71
72type Ratelimiter struct {
Dave Day0d6986a2014-12-10 15:02:18 +110073 rate int // conn/sec
74 last time.Time // last time we were polled/asked
Andrew Gerrand5bc444d2014-12-10 11:35:11 +110075
Dave Day0d6986a2014-12-10 15:02:18 +110076 allowance float64
Andrew Gerrand5bc444d2014-12-10 11:35:11 +110077}
78
79// Create new rate limiter that limits at rate/sec
80func NewRateLimiter(rate int) (*Ratelimiter, error) {
81
Dave Day0d6986a2014-12-10 15:02:18 +110082 r := Ratelimiter{rate: rate, last: time.Now()}
Andrew Gerrand5bc444d2014-12-10 11:35:11 +110083
Dave Day0d6986a2014-12-10 15:02:18 +110084 r.allowance = float64(r.rate)
85 return &r, nil
Andrew Gerrand5bc444d2014-12-10 11:35:11 +110086}
87
88// Return true if the current call exceeds the set rate, false
89// otherwise
Dave Day0d6986a2014-12-10 15:02:18 +110090func (r *Ratelimiter) Limit() bool {
Andrew Gerrand5bc444d2014-12-10 11:35:11 +110091
Dave Day0d6986a2014-12-10 15:02:18 +110092 // handle cases where rate in config file is unset - defaulting
93 // to "0" (unlimited)
94 if r.rate == 0 {
95 return false
96 }
Andrew Gerrand5bc444d2014-12-10 11:35:11 +110097
Dave Day0d6986a2014-12-10 15:02:18 +110098 rate := float64(r.rate)
99 now := time.Now()
100 elapsed := now.Sub(r.last)
101 r.last = now
102 r.allowance += float64(elapsed) * rate
Andrew Gerrand5bc444d2014-12-10 11:35:11 +1100103
Dave Day0d6986a2014-12-10 15:02:18 +1100104 // Clamp number of tokens in the bucket. Don't let it get
105 // unboundedly large
106 if r.allowance > rate {
107 r.allowance = rate
108 }
Andrew Gerrand5bc444d2014-12-10 11:35:11 +1100109
Dave Day0d6986a2014-12-10 15:02:18 +1100110 var ret bool
Andrew Gerrand5bc444d2014-12-10 11:35:11 +1100111
Dave Day0d6986a2014-12-10 15:02:18 +1100112 if r.allowance < 1.0 {
113 ret = true
114 } else {
115 r.allowance -= 1.0
116 ret = false
117 }
Andrew Gerrand5bc444d2014-12-10 11:35:11 +1100118
Dave Day0d6986a2014-12-10 15:02:18 +1100119 return ret
Andrew Gerrand5bc444d2014-12-10 11:35:11 +1100120}
Andrew Gerrand5bc444d2014-12-10 11:35:11 +1100121```
122
123Using this package is quite easy:
124
jim-slattery-rs7d05df02014-12-22 09:57:57 -0800125```go
Andrew Gerrand5bc444d2014-12-10 11:35:11 +1100126 // rate limit at 100/s
127 nl = ratelimit.NewRateLimiter(100)
128
129 ....
130 // in your event loop or network accept loop, do:
131 if rl.Limit() {
132 // drop the connection etc.
133 // i.e., this new event has exceeded the rate of 100/s
134 ...
135 }
136 else {
137 // .. rate is not exceeded, process as needed
138 ...
139 }
Andrew Gerrand5bc444d2014-12-10 11:35:11 +1100140```
141
142[Anti Huimaa](http://stackoverflow.com/questions/667508/whats-a-good-rate-limiting-algorithm) came up with this simple algorithm.
143
144# References
145
146time.Tick: http://golang.org/pkg/time/#Tick