Celery Görevlerinde Redis ile Dağıtık Kilitleme (Distributed Locking)
Dağıtık sistemlerde, birden fazla worker'ın aynı kritik kod bloğunu aynı anda çalıştırmasını engellemek için kilitleme (locking) mekanizmalarına ihtiyaç duyulur. Python ekosisteminde, bu iş için en popüler çözümlerden biri Redis tabanlı kilitlerdir. Redis hızlı ve güvenilir olduğu için tercih edilir.
Bu rehberde, projede uygulanan tekrar kullanılabilir context manager yaklaşımıyla, Celery görevlerinde Redis tabanlı dağıtık kilidin nasıl uygulanacağını ve kullanılacağını anlatıyoruz.
Neden Dağıtık Kilit Kullanılır?
Birden fazla Celery worker çalıştırdığınızda, aynı zamanlanmış veya tetiklenmiş görevin birden fazla worker tarafından aynı anda alınması riski vardır. Bu, tekrarlı işlem, veri bozulması veya yarış durumlarına (race condition) yol açabilir. Dağıtık kilitleme, aynı anda sadece bir worker'ın kritik kodu çalıştırmasını garanti eder.
Uygulama Özeti
Redis, dağıtık kilit için arka uç (backend) olarak kullanılır. Kilit işlemi bir context manager ile yönetilir. Böylece kodunuz temiz olur ve hata olsa bile kilit her zaman serbest bırakılır.
1. redis_lock
Context Manager'ı
app/utils/redis_lock.py
dosyasına aşağıdaki yardımcı fonksiyonu ekleyin:
from contextlib import contextmanager
import logging
logger = logging.getLogger(__name__)
@contextmanager
def redis_lock(client, lock_key, timeout=600):
"""
Redis kilidi almak ve bırakmak için context manager.
Argümanlar:
client: Redis client nesnesi.
lock_key (str): Kilit anahtarı.
timeout (int): Kilidin saniye cinsinden süresi.
Yield:
bool: Kilit alındıysa True, alınamadıysa False.
"""
lock = client.lock(lock_key, timeout=timeout)
acquired = lock.acquire(blocking=False)
try:
if acquired:
yield True
else:
yield False
finally:
if acquired:
lock.release()
logger.info("Redis kilidi serbest bırakıldı.")
2. Celery Görevinde Kilidi Kullanmak
Örneğin app/tasks/email_pending_tasks.py
dosyasında, sadece bir worker'ın görevi işlemesini sağlamak için context manager'ı şu şekilde kullanın:
import logging
from config.celery_config import celery_app, get_redis_url
from services.email_service import EmailService
import redis
from app.utils.redis_lock import redis_lock
logger = logging.getLogger(__name__)
def get_redis_client():
redis_url = get_redis_url()
return redis.Redis.from_url(redis_url)
@celery_app.task(name='process_pending_emails', queue='high')
def process_pending_emails():
"""
Aynı anda sadece bir worker'ın pending emailleri işlemesini sağlamak için Redis kilidi kullanır.
"""
logger.info("Pending emaillerin işlenmesi başlatıldı")
redis_client = get_redis_client()
lock_key = "process_pending_emails_lock"
# Kilit süresi: 30 dakika (1800 saniye)
with redis_lock(redis_client, lock_key, timeout=1800) as acquired:
if not acquired:
logger.info("Başka bir worker şu anda pending emailleri işliyor. Bu worker beklemeden çıkıyor.")
return
try:
email_service = EmailService()
email_service.process_all_pending_emails()
except Exception as e:
logger.error(f"Emailler işlenirken hata oluştu: {str(e)}")
raise # Hatanın Celery tarafından işlenmesi için tekrar fırlat
logger.info("Pending emaillerin işlenmesi tamamlandı")
En İyi Uygulama Önerileri
- Uygun bir timeout belirleyin: Kilit süresi, görevin beklenen maksimum süresinden uzun olmalı ama gereksiz yere uzun olmamalı. Böylece worker çökse bile kilit sonsuza kadar kalmaz.
- Her zaman context manager kullanın: Hata olsa bile kilidin serbest bırakılması garanti olur.
- Her kritik işlem için farklı lock anahtarı kullanın: Birden fazla kilit gerektiren görevleriniz varsa, her biri için farklı anahtar kullanın.
- Kilit işlemlerini loglayın: Kilit alımı ve bırakılması loglanırsa, hata ayıklama ve izleme kolaylaşır.
Dağıtık Kilit Ne Zaman Kullanılır?
- Üst üste binmemesi gereken zamanlanmış görevler (ör: toplu işler, e-posta gönderimi, rapor üretimi)
- Paylaşılan kaynakları veya dış sistemleri değiştiren işlemler
- Çift işlem yapılmasının sorun yaratacağı her senaryo
Sonuç
Redis ile dağıtık kilitleme, Celery ile dağıtık çalışan ortamlarda aynı anda sadece bir worker'ın kritik kodu çalıştırmasını sağlamak için basit ve etkili bir yöntemdir. Kilit mantığını tekrar kullanılabilir bir context manager ile sarmak, kodunuzu temiz, güvenli ve bakımı kolay hale getirir.
Kaynaklar: