package main import ( "fmt" "log" "sync" "time" ) // APICaller simulates an external API call func APICaller(requestID int) { time.Sleep(100 * time.Millisecond) // Simulate network latency/work fmt.Printf("API call %d made at %s ", requestID, time.Now().Format("15:04:05.000")) } // RateLimiter holds the channel to manage request rate type RateLimiter struct { tokens chan struct{} } // NewRateLimiter creates a new rate limiter with a given burst capacity and refill rate. // tokensPerSecond: how many tokens are added to the bucket per second. // burstCapacity: maximum number of tokens the bucket can hold. func NewRateLimiter(tokensPerSecond int, burstCapacity int) *RateLimiter { rl := &RateLimiter{ tokens: make(chan struct{}, burstCapacity), } // Fill the bucket initially for i := 0; i < burstCapacity; i++ { rl.tokens <- struct{}{} } // Goroutine to continuously refill the token bucket go func() { ticker := time.NewTicker(time.Second / time.Duration(tokensPerSecond)) defer ticker.Stop() for range ticker.C { select { case rl.tokens <- struct{}{}: // Token added default: // Bucket is full, drop token } } }() return rl } // Take blocks until a token is available func (rl *RateLimiter) Take() { <-rl.tokens } func main() { // Allow 5 requests per second, with a burst of up to 10 requests limiter := NewRateLimiter(5, 10) var wg sync.WaitGroup log.Println("Starting API calls with rate limiting...") // Simulate 20 requests for i := 1; i <= 20; i++ { wg.Add(1) go func(requestID int) { defer wg.Done() limiter.Take() // Acquire a token before making the call APICaller(requestID) }(i) // Introduce a slight delay between initiating goroutines to see burst behavior if i%3 == 0 { time.Sleep(50 * time.Millisecond) } } wg.Wait() log.Println("All API calls finished.") }