Andrew Gerrand | 5bc444d | 2014-12-10 11:35:11 +1100 | [diff] [blame] | 1 | # Rate Limiting |
| 2 | |
| 3 | ## Time Tick based Approach |
| 4 | |
| 5 | To limit the rate of operations per unit time, use time.Tick: |
| 6 | |
jim-slattery-rs | 7d05df0 | 2014-12-22 09:57:57 -0800 | [diff] [blame] | 7 | ```go |
Andrew Gerrand | 5bc444d | 2014-12-10 11:35:11 +1100 | [diff] [blame] | 8 | import "time" |
| 9 | |
| 10 | rate_per_sec := 10 |
| 11 | throttle := time.Tick(1e9 / rate_per_sec) |
| 12 | for req := range requests { |
| 13 | <-throttle // rate limit our Service.Method RPCs |
| 14 | go client.Call("Service.Method", req, ...) |
| 15 | } |
| 16 | ``` |
| 17 | |
| 18 | To allow some bursts, add a buffer to the throttle: |
jim-slattery-rs | 7d05df0 | 2014-12-22 09:57:57 -0800 | [diff] [blame] | 19 | ```go |
Andrew Gerrand | 5bc444d | 2014-12-10 11:35:11 +1100 | [diff] [blame] | 20 | import "time" |
| 21 | |
| 22 | rate_per_sec := 10 |
| 23 | burst_limit := 100 |
| 24 | tick := time.NewTicker(1e9 / rate_per_sec) |
| 25 | defer tick.Stop() |
| 26 | throttle := make(chan int64, burst_limit) |
| 27 | go func() { |
| 28 | for ns := range tick { |
| 29 | select { |
| 30 | case: throttle <- ns |
| 31 | default: |
| 32 | } |
| 33 | } // exits after tick.Stop() |
| 34 | }() |
| 35 | for 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 | |
| 43 | Here 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-rs | 7d05df0 | 2014-12-22 09:57:57 -0800 | [diff] [blame] | 47 | ```go |
Andrew Gerrand | 5bc444d | 2014-12-10 11:35:11 +1100 | [diff] [blame] | 48 | // |
| 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 | // |
| 68 | package ratelimit |
Dave Day | 0d6986a | 2014-12-10 15:02:18 +1100 | [diff] [blame] | 69 | |
Andrew Gerrand | 5bc444d | 2014-12-10 11:35:11 +1100 | [diff] [blame] | 70 | import "time" |
| 71 | |
| 72 | type Ratelimiter struct { |
Dave Day | 0d6986a | 2014-12-10 15:02:18 +1100 | [diff] [blame] | 73 | rate int // conn/sec |
| 74 | last time.Time // last time we were polled/asked |
Andrew Gerrand | 5bc444d | 2014-12-10 11:35:11 +1100 | [diff] [blame] | 75 | |
Dave Day | 0d6986a | 2014-12-10 15:02:18 +1100 | [diff] [blame] | 76 | allowance float64 |
Andrew Gerrand | 5bc444d | 2014-12-10 11:35:11 +1100 | [diff] [blame] | 77 | } |
| 78 | |
| 79 | // Create new rate limiter that limits at rate/sec |
| 80 | func NewRateLimiter(rate int) (*Ratelimiter, error) { |
| 81 | |
Dave Day | 0d6986a | 2014-12-10 15:02:18 +1100 | [diff] [blame] | 82 | r := Ratelimiter{rate: rate, last: time.Now()} |
Andrew Gerrand | 5bc444d | 2014-12-10 11:35:11 +1100 | [diff] [blame] | 83 | |
Dave Day | 0d6986a | 2014-12-10 15:02:18 +1100 | [diff] [blame] | 84 | r.allowance = float64(r.rate) |
| 85 | return &r, nil |
Andrew Gerrand | 5bc444d | 2014-12-10 11:35:11 +1100 | [diff] [blame] | 86 | } |
| 87 | |
| 88 | // Return true if the current call exceeds the set rate, false |
| 89 | // otherwise |
Dave Day | 0d6986a | 2014-12-10 15:02:18 +1100 | [diff] [blame] | 90 | func (r *Ratelimiter) Limit() bool { |
Andrew Gerrand | 5bc444d | 2014-12-10 11:35:11 +1100 | [diff] [blame] | 91 | |
Dave Day | 0d6986a | 2014-12-10 15:02:18 +1100 | [diff] [blame] | 92 | // handle cases where rate in config file is unset - defaulting |
| 93 | // to "0" (unlimited) |
| 94 | if r.rate == 0 { |
| 95 | return false |
| 96 | } |
Andrew Gerrand | 5bc444d | 2014-12-10 11:35:11 +1100 | [diff] [blame] | 97 | |
Dave Day | 0d6986a | 2014-12-10 15:02:18 +1100 | [diff] [blame] | 98 | 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 Gerrand | 5bc444d | 2014-12-10 11:35:11 +1100 | [diff] [blame] | 103 | |
Dave Day | 0d6986a | 2014-12-10 15:02:18 +1100 | [diff] [blame] | 104 | // 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 Gerrand | 5bc444d | 2014-12-10 11:35:11 +1100 | [diff] [blame] | 109 | |
Dave Day | 0d6986a | 2014-12-10 15:02:18 +1100 | [diff] [blame] | 110 | var ret bool |
Andrew Gerrand | 5bc444d | 2014-12-10 11:35:11 +1100 | [diff] [blame] | 111 | |
Dave Day | 0d6986a | 2014-12-10 15:02:18 +1100 | [diff] [blame] | 112 | if r.allowance < 1.0 { |
| 113 | ret = true |
| 114 | } else { |
| 115 | r.allowance -= 1.0 |
| 116 | ret = false |
| 117 | } |
Andrew Gerrand | 5bc444d | 2014-12-10 11:35:11 +1100 | [diff] [blame] | 118 | |
Dave Day | 0d6986a | 2014-12-10 15:02:18 +1100 | [diff] [blame] | 119 | return ret |
Andrew Gerrand | 5bc444d | 2014-12-10 11:35:11 +1100 | [diff] [blame] | 120 | } |
Andrew Gerrand | 5bc444d | 2014-12-10 11:35:11 +1100 | [diff] [blame] | 121 | ``` |
| 122 | |
| 123 | Using this package is quite easy: |
| 124 | |
jim-slattery-rs | 7d05df0 | 2014-12-22 09:57:57 -0800 | [diff] [blame] | 125 | ```go |
Andrew Gerrand | 5bc444d | 2014-12-10 11:35:11 +1100 | [diff] [blame] | 126 | // 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 Gerrand | 5bc444d | 2014-12-10 11:35:11 +1100 | [diff] [blame] | 140 | ``` |
| 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 | |
| 146 | time.Tick: http://golang.org/pkg/time/#Tick |