Skip to content

Instantly share code, notes, and snippets.

@nikgalushko
Last active July 14, 2025 11:35
Show Gist options
  • Save nikgalushko/ac514d27b8bf5b4936d95875dc2bf989 to your computer and use it in GitHub Desktop.
Save nikgalushko/ac514d27b8bf5b4936d95875dc2bf989 to your computer and use it in GitHub Desktop.
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
package main
import (
"context"
"fmt"
"sync"
"sync/atomic"
"time"
"golang.org/x/time/rate"
)
// Демонстрация проблем с Ticker
func demoTickerProblems() {
fmt.Println("=== ДЕМО 1: Накопление событий в Ticker ===")
ticker := time.NewTicker(100 * time.Millisecond) // 10 RPS
defer ticker.Stop()
requestCount := 0
startTime := time.Now()
for i := 0; i < 5; i++ {
<-ticker.C
requestCount++
// Имитируем медленную обработку на 3-м запросе
if i == 2 {
fmt.Println("Запрос #3 обрабатывается медленно (500ms)...")
time.Sleep(500 * time.Millisecond)
}
elapsed := time.Since(startTime)
fmt.Printf("Запрос #%d в момент %v\n", requestCount, elapsed.Round(time.Millisecond))
}
fmt.Println("\nПроблема: После медленного запроса следующие идут подряд без задержки!\n")
}
// Демонстрация неконтролируемого RPS с Ticker
func demoTickerUncontrolledRPS() {
fmt.Println("=== ДЕМО 2: Неконтролируемый RPS с горутинами ===")
ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop()
var activeRequests int32
var totalRequests int32
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
var wg sync.WaitGroup
for {
select {
case <-ticker.C:
wg.Add(1)
go func() {
defer wg.Done()
atomic.AddInt32(&activeRequests, 1)
atomic.AddInt32(&totalRequests, 1)
current := atomic.LoadInt32(&activeRequests)
fmt.Printf("Активных запросов: %d\n", current)
// Имитация запроса (300ms)
time.Sleep(300 * time.Millisecond)
atomic.AddInt32(&activeRequests, -1)
}()
case <-ctx.Done():
wg.Wait()
fmt.Printf("\nВсего запросов за 1 сек: %d (ожидалось ~10)\n", totalRequests)
fmt.Println("Проблема: Запросы накапливаются, RPS не контролируется!\n")
return
}
}
}
// Демонстрация правильного использования rate.Limiter
func demoRateLimiter() {
fmt.Println("=== ДЕМО 3: Правильный контроль RPS с rate.Limiter ===")
limiter := rate.NewLimiter(10, 1) // 10 RPS, burst 1
var successCount int32
var blockedCount int32
startTime := time.Now()
var wg sync.WaitGroup
// Пытаемся сделать 20 запросов быстро
for i := 0; i < 20; i++ {
wg.Add(1)
go func(reqNum int) {
defer wg.Done()
// Используем Allow() для демонстрации (не ждём)
if limiter.Allow() {
atomic.AddInt32(&successCount, 1)
elapsed := time.Since(startTime)
fmt.Printf("✓ Запрос #%d разрешён в %v\n", reqNum, elapsed.Round(time.Millisecond))
} else {
atomic.AddInt32(&blockedCount, 1)
}
}(i)
time.Sleep(50 * time.Millisecond) // Небольшая задержка между попытками
}
wg.Wait()
fmt.Printf("\nРазрешено: %d, Заблокировано: %d\n", successCount, blockedCount)
fmt.Println("Rate limiter точно контролирует RPS!\n")
}
// Демонстрация burst capability
func demoBurstCapability() {
fmt.Println("=== ДЕМО 4: Burst возможности ===")
// Ticker не может обработать burst
fmt.Println("Ticker (без burst):")
ticker := time.NewTicker(200 * time.Millisecond) // 5 RPS
defer ticker.Stop()
tickerStart := time.Now()
for i := 0; i < 3; i++ {
<-ticker.C
fmt.Printf(" Запрос %d: %v\n", i+1, time.Since(tickerStart).Round(time.Millisecond))
}
fmt.Println("\nrate.Limiter (с burst=3):")
limiter := rate.NewLimiter(5, 3) // 5 RPS, burst 3
limiterStart := time.Now()
for i := 0; i < 6; i++ {
if err := limiter.Wait(context.Background()); err == nil {
fmt.Printf(" Запрос %d: %v\n", i+1, time.Since(limiterStart).Round(time.Millisecond))
}
}
fmt.Println("\nПреимущество: rate.Limiter обработал первые 3 запроса мгновенно (burst)!\n")
}
// Практический пример: HTTP клиент с ограничением RPS
type RateLimitedClient struct {
limiter *rate.Limiter
name string
}
func (c *RateLimitedClient) DoRequest(ctx context.Context, id int) error {
// Ждём разрешения
if err := c.limiter.Wait(ctx); err != nil {
return fmt.Errorf("rate limit exceeded: %w", err)
}
fmt.Printf("[%s] Выполняется запрос #%d\n", c.name, id)
// Имитация HTTP запроса
time.Sleep(50 * time.Millisecond)
return nil
}
func demoPracticalExample() {
fmt.Println("=== ДЕМО 5: Практический пример с HTTP клиентом ===")
client := &RateLimitedClient{
limiter: rate.NewLimiter(5, 2), // 5 RPS, burst 2
name: "APIClient",
}
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
var wg sync.WaitGroup
startTime := time.Now()
// 10 параллельных запросов
for i := 0; i < 10; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
if err := client.DoRequest(ctx, id); err != nil {
fmt.Printf("[%s] Ошибка запроса #%d: %v\n", client.name, id, err)
}
}(i)
}
wg.Wait()
elapsed := time.Since(startTime)
fmt.Printf("\nВсе запросы выполнены за %v\n", elapsed.Round(time.Millisecond))
fmt.Println("С rate.Limiter запросы распределены равномерно и не превышают лимит!")
}
func main() {
demoTickerProblems()
time.Sleep(1 * time.Second)
demoTickerUncontrolledRPS()
time.Sleep(1 * time.Second)
demoRateLimiter()
time.Sleep(1 * time.Second)
demoBurstCapability()
time.Sleep(1 * time.Second)
demoPracticalExample()
}
package main
import (
"context"
"fmt"
"sync"
"time"
"golang.org/x/time/rate"
)
// Симуляция HTTP запроса
func makeRequest(id int, method string) {
fmt.Printf("[%s] Request %d processed at %s\n", method, id, time.Now().Format("15:04:05.000"))
}
// Демонстрация проблем с ticker'ом
func tickerApproach() {
fmt.println("=== TICKER APPROACH ===")
ticker := time.NewTicker(200 * time.Millisecond) // 5 RPS
defer ticker.Stop()
// Очередь запросов
queue := make(chan int, 100)
// Симулируем всплеск трафика - 10 запросов сразу
go func() {
for i := 1; i <= 10; i++ {
queue <- i
}
close(queue)
}()
// Обработка с ticker'ом
start := time.Now()
processed := 0
for {
select {
case <-ticker.C:
select {
case id := <-queue:
makeRequest(id, "TICKER")
processed++
default:
// Нет запросов, но ticker все равно тикает
fmt.Printf("[TICKER] Tick wasted at %s\n", time.Now().Format("15:04:05.000"))
}
case <-time.After(3 * time.Second):
fmt.Printf("[TICKER] Processed %d requests in %v\n", processed, time.Since(start))
return
}
}
}
// Демонстрация rate limiter'а
func rateLimiterApproach() {
fmt.Println("\n=== RATE LIMITER APPROACH ===")
// 5 RPS с burst до 3 запросов
limiter := rate.NewLimiter(5, 3)
start := time.Now()
var wg sync.WaitGroup
// Симулируем тот же всплеск трафика
for i := 1; i <= 10; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
// Ждем разрешения
err := limiter.Wait(context.Background())
if err != nil {
fmt.Printf("[RATE_LIMITER] Request %d failed: %v\n", id, err)
return
}
makeRequest(id, "RATE_LIMITER")
}(i)
}
wg.Wait()
fmt.Printf("[RATE_LIMITER] All requests processed in %v\n", time.Since(start))
}
// Демонстрация неэффективности ticker'а при отсутствии запросов
func tickerWaste() {
fmt.Println("\n=== TICKER WASTE DEMONSTRATION ===")
ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop()
start := time.Now()
ticks := 0
// Ticker работает даже когда нет запросов
for {
select {
case <-ticker.C:
ticks++
fmt.Printf("[TICKER] Wasted tick #%d at %s\n", ticks, time.Now().Format("15:04:05.000"))
case <-time.After(2500 * time.Millisecond):
fmt.Printf("[TICKER] Wasted %d ticks in %v with no requests\n", ticks, time.Since(start))
return
}
}
}
// Демонстрация эффективности rate limiter'а
func rateLimiterEfficiency() {
fmt.Println("\n=== RATE LIMITER EFFICIENCY ===")
limiter := rate.NewLimiter(2, 1)
start := time.Now()
// Rate limiter не тратит ресурсы без запросов
fmt.Printf("[RATE_LIMITER] Created at %s\n", start.Format("15:04:05.000"))
// Ждем 2 секунды
time.Sleep(2 * time.Second)
// Теперь делаем запрос
if limiter.Allow() {
makeRequest(1, "RATE_LIMITER")
}
fmt.Printf("[RATE_LIMITER] No resources wasted during idle time\n")
}
// Демонстрация проблем с burst traffic
func burstTrafficComparison() {
fmt.Println("\n=== BURST TRAFFIC COMPARISON ===")
// Ticker не может накопить "разрешения"
fmt.Println("Ticker with burst traffic:")
ticker := time.NewTicker(1 * time.Second) // 1 RPS
defer ticker.Stop()
requests := []int{1, 2, 3, 4, 5}
// Все запросы приходят одновременно
for _, id := range requests {
select {
case <-ticker.C:
makeRequest(id, "TICKER")
default:
fmt.Printf("[TICKER] Request %d dropped - no tick available\n", id)
}
}
// Rate limiter может обработать burst
fmt.Println("\nRate limiter with burst traffic:")
limiter := rate.NewLimiter(1, 3) // 1 RPS, burst до 3
for _, id := range requests {
if limiter.Allow() {
makeRequest(id, "RATE_LIMITER")
} else {
fmt.Printf("[RATE_LIMITER] Request %d rate limited\n", id)
}
}
}
func main() {
tickerApproach()
rateLimiterApproach()
tickerWaste()
rateLimiterEfficiency()
burstTrafficComparison()
}
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment