Rate Limiting in Go (Golang): Libraries, Patterns, and Implementation Examples
Rate limiting is one of the key mechanisms for protecting and controlling load in modern web services and microservice architectures. In the Go (Golang) ecosystem, there are several mature tools for implementing rate limiting, from the built-in time package to specialized libraries such as golang.org/x/time/rate. In this article, we will take a detailed look at how rate limiting works in Go, which libraries to use, and provide practical code examples.
What is Rate Limiting and Why is it Needed?
Rate limiting is a technique for controlling the number of requests a client can make to a server within a specific time period. The main goals are:
- Protection against DDoS attacks — preventing server overload.
- Fair resource distribution — limiting "greedy" clients.
- SLA compliance — guaranteeing availability for all users.
- Cost savings — reducing load on databases and external APIs.
In Go, rate limiting is implemented using the Token Bucket and Leaky Bucket algorithms. The Token Bucket is the most popular.
The golang.org/x/time/rate Library
The primary and recommended library for rate limiting in Go is golang.org/x/time/rate. It implements the Token Bucket algorithm and is part of Go's extended set of packages (sub-repositories).
Installation:
go get golang.org/x/time/rateMain Components
rate.Limiter— the main limiter object.rate.NewLimiter(r rate.Limit, b int)— creates a limiter with a rate ofrrequests per second and a bucket size ofb.Allow()— checks if a request can be made now (non-blocking).Wait(ctx)— blocks execution until a token is available (respects context).Reserve()— returns aReservation, allowing you to defer execution.
Example 1: Basic Rate Limiter
package main
import ( "fmt" "golang.org/x/time/rate" "time")
func main() { // 2 requests per second, bucket of 5 tokens limiter := rate.NewLimiter(2, 5)
for i := 0; i < 10; i++ { if limiter.Allow() { fmt.Printf("Request %d executed", i) } else { fmt.Printf("Request %d rejected (limit)", i) } time.Sleep(200 * time.Millisecond) }}Example 2: Rate Limiter with Waiting (Wait)
package main
import ( "context" "fmt" "golang.org/x/time/rate" "time")
func main() { limiter := rate.NewLimiter(1, 3) // 1 request per second, bucket of 3 ctx := context.Background()
for i := 0; i < 6; i++ { err := limiter.Wait(ctx) if err != nil { fmt.Println("Wait error:", err) return } fmt.Printf("Request %d executed at %s", i, time.Now().Format("15:04:05.000")) }}Implementing HTTP Middleware with Rate Limiting
In practice, rate limiting is most often applied at the HTTP server level. Let's look at an example middleware for Gin or the standard net/http.
package main
import ( "net/http" "golang.org/x/time/rate")
// RateLimiterMiddleware returns a middleware with rate limitfunc RateLimiterMiddleware(r rate.Limit, b int) func(http.Handler) http.Handler { limiter := rate.NewLimiter(r, b) return func(next http.Handler) http.Handler { return http.HandlerFunc(f