Effective Rate Limiting in Go: Strategies and Examples
Implementing rate limiting in a Go application can be crucial for controlling access to resources, preventing abuse, and ensuring fair usage. Here's a guide on how to set up rate limiting in Go, with examples using different methods and libraries.
Using golang.org/x/time/rate
Package
The golang.org/x/time/rate
package is a standard and straightforward way to implement rate limiting in Go.
Example
package main
import (
"fmt"
"log"
"net/http"
"time"
"golang.org/x/time/rate"
)
var rateLimiter *rate.Limiter
func init() {
// Limit to 5 requests per second
rateLimiter = rate.NewLimiter(5, 5)
}
func rateLimitedHandler(w http.ResponseWriter, r *http.Request) {
if !rateLimiter.Allow() {
http.Error(w, "Too Many Requests", http.StatusTooManyRequests)
return
}
// Simulate work
fmt.Fprintln(w, "Request successful!")
}
func main() {
http.HandleFunc("/", rateLimitedHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Burst Mode
The rate.NewLimiter
function takes two arguments:
- Rate:
5
requests per second. - Burst: Allows up to
5
requests to burst in a shorter time window.
Custom Middleware for Rate Limiting
You can create custom middleware to apply rate limiting to specific routes in your HTTP server.
Example
package main
import (
"fmt"
"log"
"net/http"
"time"
"golang.org/x/time/rate"
)
type rateLimiter struct {
limiter *rate.Limiter
}
func newRateLimiter(r rate.Limit, b int) *rateLimiter {
return &rateLimiter{limiter: rate.NewLimiter(r, b)}
}
func (rl *rateLimiter) limit(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !rl.limiter.Allow() {
http.Error(w, "Too Many Requests", http.StatusTooManyRequests)
return
}
next.ServeHTTP(w, r)
})
}
func main() {
limiter := newRateLimiter(2, 5) // 2 requests per second with a burst of 5
handler := limiter.limit(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Request successful!")
}))
http.Handle("/", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Using Fixed Windows for Rate Limiting
Fixed window rate limiting can be implemented using a map and a time window.
Example
package main
import (
"fmt"
"log"
"net/http"
"sync"
"time"
)
var (
rateLimitWindow = 1 * time.Minute
rateLimit = 10
clients = make(map[string]int)
mu sync.Mutex
)
func rateLimitedHandler(w http.ResponseWriter, r *http.Request) {
mu.Lock()
defer mu.Unlock()
clientIP := r.RemoteAddr
// Reset the count based on time window
if count, found := clients[clientIP]; found {
if count >= rateLimit {
http.Error(w, "Too Many Requests", http.StatusTooManyRequests)
return
}
clients[clientIP]++
} else {
clients[clientIP] = 1
go func() {
time.Sleep(rateLimitWindow)
mu.Lock()
delete(clients, clientIP)
mu.Unlock()
}()
}
// Simulate work
fmt.Fprintln(w, "Request successful!")
}
func main() {
http.HandleFunc("/", rateLimitedHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Using Redis for Distributed Rate Limiting
Using Redis can help in implementing rate limiting across multiple distributed instances of an application.
Example
package main
import (
"fmt"
"github.com/go-redis/redis/v8"
"golang.org/x/net/context"
"net/http"
"time"
)
var (
rdb = redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
ctx = context.Background()
)
func rateLimitedHandler(w http.ResponseWriter, r *http.Request) {
clientIP := r.RemoteAddr
key := "rate_limit:" + clientIP
// Increment the rate limit counter in Redis
count, err := rdb.Incr(ctx, key).Result()
if err != nil {
http.Error(w, fmt.Sprintf("Redis error: %v", err), http.StatusInternalServerError)
return
}
// Set expiration on the key for the fixed window
if count == 1 {
rdb.Expire(ctx, key, time.Minute)
}
// Limit to 10 requests per minute
if count > 10 {
http.Error(w, "Too Many Requests", http.StatusTooManyRequests)
return
}
fmt.Fprintln(w, "Request successful!")
}
func main() {
http.HandleFunc("/", rateLimitedHandler)
http.ListenAndServe(":8080", nil)
}
By using these approaches, you can implement effective rate limiting in your Go applications, whether you are dealing with simple scenarios or complex, distributed environments.