Mert Tosun
← Yazılar
Redis ile Rate Limiting: Go'da Token Bucket ve Sliding Window Implementasyonu

Redis ile Rate Limiting: Go'da Token Bucket ve Sliding Window Implementasyonu

Mert TosunGo

API’lerinizi veya giriş noktalarınızı kötüye kullanıma karşı korumak için rate limiting (istek sınırlama) neredeyse zorunlu. Tek makinede bellek içi sayaçlar işe yarar; fakat birden fazla instance çalıştığınızda tutarlı bir limit için Redis gibi merkezi bir depo gerekir.

Bu yazıda golang rate limiting aramasıyla bulunabilecek, doğrudan kod odaklı bir yaklaşım sunuyoruz: token bucket ve sliding window modellerini Redis üzerinde atomik olarak nasıl uygularsınız?

Neden Redis?

Senaryo Bellek içi Redis
Tek pod Basit Gereksiz olabilir
Çoklu pod / horizontal scale Tutarsızlık Tek kaynak
Süre bazlı pencereler Zor ZSET / TTL ile doğal
Atomik artış Mutex INCR, Lua, EVAL

Token bucket (jeton kutusu)

Fikir: Kova belirli bir hızla jeton dolar; her istek bir jeton harcar. Jeton yoksa 429 veya Retry-After dönersiniz.

Redis’te sayaç + son zaman damgası ile yaklaşılabilir; daha temiz bir yol, Lua script ile tek round-trip’te hem güncellemeyi hem kararı vermektir.

Temel parametreler

  • Kapasite (capacity): Kovada en fazla kaç jeton
  • Yenileme hızı (rate): Saniyede birim jeton (veya dakikada)

Örnek Lua (basitleştirilmiş)

Jetonları süreye göre yenileyip, allow veya deny döner:

-- KEYS[1] key, ARGV[1] capacity, ARGV[2] refill rate per second, ARGV[3] now, ARGV[4] cost
local key = KEYS[1]
local capacity = tonumber(ARGV[1])
local rate = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local cost = tonumber(ARGV[4])

local data = redis.call('HMGET', key, 'tokens', 'last')
local tokens = tonumber(data[1])
local last = tonumber(data[2])

if not tokens then
  tokens = capacity
  last = now
end

local delta = math.max(0, now - last)
local refill = delta * rate
tokens = math.min(capacity, tokens + refill)

if tokens >= cost then
  tokens = tokens - cost
  redis.call('HSET', key, 'tokens', tokens, 'last', now)
  redis.call('PEXPIRE', key, 3600000)
  return {1, tokens}
else
  redis.call('HSET', key, 'tokens', tokens, 'last', now)
  redis.call('PEXPIRE', key, 3600000)
  return {0, tokens}
end

Go tarafında github.com/redis/go-redis/v9 ile:

ctx := context.Background()
rdb := redis.NewClient(&redis.Options{Addr: "localhost:6379"})

const script = `...lua...`

const capacity = 100
const ratePerSec = 10.0
now := float64(time.Now().UnixMilli())

res, err := rdb.Eval(ctx, script, []string{"ratelimit:user:42"},
    capacity, ratePerSec, now, 1).Result()
// res[0] == 1 ise izin var

Golang rate limiting için paket olarak golang.org/x/time/rate (lokal) veya ulule/limiter gibi Redis destekli kütüphaneler de vardır; üretimde Lua ile atomiklik ve metrik (izin verilen / reddedilen sayısı) eklemeyi unutmayın.


Sliding window (kayan pencere)

Sabit pencere (dakikalık sayaç) patlamada sınırı aşabilir; sliding window son N saniyedeki istekleri sayar.

Redis’te sık kullanılan yöntemler:

  1. Sorted set (ZSET): Her isteğin zaman damgasını skor olarak ekleyin; ZREMRANGEBYSCORE ile eski kayıtları silin; ZCARD ile sayıyı alın.
  2. Karma yaklaşım: Yaklaşık sliding window için sayaç + ağırlıklandırma (Redis Labs dokümantasyonundaki “sliding window log”).

ZSET ile örnek akış

  1. ZADD key now memberId (member unique olmalı)
  2. ZREMRANGEBYSCORE key 0 now-windowMs
  3. ZCARD key → limit aşıldı mı?

Lua ile tek atomik işlemde yapmak yarış durumlarını önler.


Sliding Window Counter (yaklaşık)

Daha az bellek için Redis Cell modülü veya basit INCR + TTL ile “bu saniye + önceki saniye ağırlığı” gibi hibrit modeller kullanılabilir. Gecikme duyarlı API’lerde tam sliding window; yüksek trafikte yaklaşık sayaçlar.


Üretimde kontrol listesi

  • Anahtar tasarımı: ratelimit:{api}:{userId} veya IP bazlı; brute force’a karşı IP + kullanıcı birleşimi.
  • TTL: Anahtarların sızıntı yapmaması için HSET/ZSET sonrası PEXPIRE.
  • Clock skew: Pod’lar arası saat farkı; mümkünse Redis TIME kullanmak.
  • 429 yanıtı: Retry-After başlığı (RFC 6585).
  • Metrikler: Prometheus’ta rate_limit_allowed_total, rate_limit_rejected_total.

Özet

  • Token bucket: Trafik şekillendirme ve “burst” toleransı için ideal; Redis + Lua ile atomik uygulama.
  • Sliding window: Adil limit için ZSET veya Lua script; sabit pencereye göre daha az sert köşe.
  • golang rate limiting anahtar kelimesiyle hedeflenen içeriklerde, kod örnekleri ve gerçek Redis anahtarları okuyucuya doğrudan değer katar.

İleride aynı konuda gRPC veya HTTP middleware ile entegrasyonu da ayrı bir yazıda derinleştirebilirsiniz.