Last active
July 14, 2025 11:35
-
-
Save nikgalushko/ac514d27b8bf5b4936d95875dc2bf989 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment