Yüksek verimli mikroservislerin modern manzarasında, API'lerinizi kötüye kullanımdan, DDoS saldırılarından ve kazara aşırı yüklenmeden korumak isteğe bağlı değil, kritik bir gerekliliktir. Temel hız sınırlama tek düğüm uygulamalarda iyi çalışsa da, dağıtık sistemler birden fazla örnekte tutarlı uygulama sağlamak için paylaşılan bir duruma ihtiyaç duyar. İşte tam da burada Redis ön plana çıkar; ancak doğruluğu gerçekten garanti altına almak için basit anahtar-değer işlemlerinin ötesine geçmeli ve Lua komut dosyalarının gücünden yararlanmalısınız.
Dağıtık Atomikliğin Zorluğu
Yük dengeleyici arkasında üç API sunucunuz olduğunu hayal edin. Kötü niyetli bir kullanıcı saniyede 100 istek göndererek Server A, B ve C'ye sırayla ulaşır. Eğer hız sınırlama sayaçlarını her sunucuda yerel olarak saklarsanız, kullanıcı 100 istek/sn limitinizi atlayarak etkili bir şekilde saniyede 300 istek gönderme izni almış olur. Merkezi bir sayaca ihtiyacınız vardır.
Redis'te bir anahtarı artırmak (örneğin, INCR) bir çözüm gibi görünse de, uygun pencereleme mantığı olmadan yüksek eşzamanlılık altında başarısız olur. Naif bir yaklaşım, süresi dolma süresi ayarlanmış bir anahtar kullanmayı içerebilir, ancak bu "sınır problemi"ne yol açar. Bir kullanıcı 0. saniyede 10 istek ve 1. saniyede 10 istek yapabilir; bu da isteklerini zaman pencerelerinin sınırına göre zamanlayarak etkili geçiş hızını iki katına çıkarır. Bunu doğru bir şekilde çözmek için sayacı kontrol etme ve süresi dolma süresini ayarlama işlemlerini atomik olarak gerçekleştirmemiz gerekir.
Neden Lua Komut Dosyaları?
Redis, Lua komut dosyalarını atomik olarak yürütür. Bu, bir komut dosyası çalışmaya başladığında, başka hiçbir komut tarafından kesilmeyeceği anlamına gelir. Bu özellik, hız sınırlama için esastır çünkü şunları yapmamız gerekir:
- Hız sınırlama anahtarının var olup olmadığını kontrol edin.
- Mevcut sayısını alın.
- Sayacı artırın.
- Bu penceredeki ilk istekse süresi dolma süresini ayarlayın.
- Durumu çağrıya döndürün.
Bu işlemleri ayrı GET ve SET komutlarıyla yapmak yarış durumlarına açıdır. Bu mantığı bir Lua komut dosyasına gömerek, tüm mantık bloğunun tek, bölünemez bir birim olarak yürütülmesini sağlarız.
Kayan Pencere Sayacının Uygulanması
Aşağıda, sabit pencere tabanlı bir hız sınırlayıcı için sağlam bir Lua komut dosyası uygulaması bulunmaktadır. Bu komut dosyası, belirli bir zaman penceresi (milisaniye cinsinden tanımlanır) içindeki istek sayısını kontrol eder.
-- Hız sınırlama sayacını saklamak için anahtar
local key = KEYS[1]
-- Milisaniye cinsinden geçerli zaman
local now = tonumber(ARGV[1])
-- Milisaniye cinsinden pencere boyutu
local window = tonumber(ARGV[2])
-- İzin verilen maksimum istek sayısı
local limit = tonumber(ARGV[3])
-- Geçerli pencerenin başlangıcını hesaplayın
local window_start = now - window
-- Süresi dolmuş girdileri kaldırın (basit temizlik, ancak sıkı kayan pencereler için sıralı bir küme daha iyidir)
-- Sabit pencere için, anahtarın var olup olmadığını kontrol ederiz
local count = redis.call('GET', key)
if not count then
-- Bu penceredeki ilk istek
redis.call('SET', key, 1)
redis.call('EXPIRE', key, math.ceil(window / 1000))
return {1, 1}
else
count = tonumber(count)
if count < limit then
-- İstek kabul edildi
redis.call('INCR', key)
return {0, count + 1}
else
-- Hız sınırı aşıldı
return {1, count}
end
end
Bu komut dosyasında, KEYS[1] istemci için benzersiz bir tanımlayıcıyı (örneğin, IP adresini veya API Anahtarını) temsil eder. ARGV geçerli zaman damgasını, pencere süresini ve izin verilen maksimum limiti geçirir. Komut dosyası, ilk elemanı isteğin engellenip engellenmediğini (1) veya kabul edilip edilmediğini (0) gösteren ve ikinci elemanı mevcut istek sayısını sağlayan bir dizi döndürür.
Python ile Pratik Entegrasyon
Bu yapıyı redis-py kütüphanesini kullanarak bir Python Flask uygulamasıyla entegre etmek basittir. Komut dosyasını Redis istemcisine bir kez yüklersiniz ve ardından referansla çağırırsınız.
import time
import redis
# Redis istemcisini başlat
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
# Lua komut dosyasını yükle
lua_script = """
-- (Yukarıdaki Lua komut dosyasını buraya ekleyin)
"""
rate_limit_fn = r.register_script(lua_script)
def check_rate_limit(user_id, limit=10, window=60000):
now = int(time.time() * 1000)
key = f"rate_limit:{user_id}"
# Lua komut dosyasını yürüt
result = rate_limit_fn(keys=[key], args=[now, window, limit])
status, count = result
if status == 1:
# 429 Çok Fazla İstek Döndür
return False, count
# 200 OK Döndür
return True, count
Sonuç
Redis ve Lua komut dosyalarıyla dağıtık hız sınırlama uygulamak, API altyapınız için güçlü, güvenli ve yüksek performanslı bir temel sağlar. Atomikliği sağlayarak yarış durumlarını önler ve güvenlik politikalarınızı doğru bir şekilde uygularsınız. Bu yaklaşım, yalnızca arka ucunuzu aşırı yüklenmeden korumakla kalmaz, aynı zamanda hangi sunucu örneğinin isteği işlediğine bakılmaksızın, meşru kullanıcılar için adil ve tutarlı bir deneyimi de korur.