سیستمهای توزیعشده مدرن، شبکههای پیچیدهای از سرویسهای متصل به هم هستند. وقتی یک جزء از کار میافتد، این خرابی میتواند بهصورت زنجیرهای گسترده و کل سیستم را از کار بیندازد. پیادهسازی الگوهای تابآوری تنها درباره مدیریت خطاها نیست؛ بلکه درباره طراحی سیستمهایی است که میتوانند در برابر آشوب مقاومت کنند. این راهنما سه ستون حیاتی را پوشش میدهد: مدارشکنها (Circuit Breakers)، تلاشهای مجدد هوشمند و تخریب ظریف (Graceful Degradation).
درک الگوی مدارشکن (Circuit Breaker)
الگوی مدارشکن الهام گرفته از مهندسی برق است. وقتی یک سرویس مکرراً از کار میافتد، مدارشکن «فعال» میشود و برای مدت مشخصی از ارسال درخواستها به آن سرویس خودداری میکند. این کار از غرق شدن فراخواننده در زمانهای انتظار (Timeouts) جلوگیری میکند و به سرویس پاییندست فرصت بازیابی میدهد.
سه حالت در یک مدارشکن وجود دارد:
- بسته (Closed): عملکرد عادی. درخواستها عبور میکنند. خطاها شمارش میشوند.
- باز (Open): مدار فعال شده است. تمام درخواستها بدون فراخوانی سرویس پاییندست سریعاً ناموفق میشوند.
- نیمهباز (Half-Open): پس از یک زمان انتظار، مدار تعداد محدودی درخواست را برای آزمایش اینکه آیا سرویس بازیابی شده است یا خیر، مجاز میشود.
در اینجا یک پیادهسازی مفهومی در پایتون با استفاده از کتابخانه tenacity برای تلاش مجدد و یک ماشین حالت ساده برای مدارشکن آورده شده است:
import time
from enum import Enum
class CircuitState(Enum):
CLOSED = "closed"
OPEN = "open"
HALF_OPEN = "half_open"
class CircuitBreaker:
def __init__(self, failure_threshold=5, recovery_timeout=30):
self.failure_count = 0
self.state = CircuitState.CLOSED
self.failure_threshold = failure_threshold
self.recovery_timeout = recovery_timeout
self.last_failure_time = None
def call(self, func, *args, **kwargs):
if self.state == CircuitState.OPEN:
if time.time() - self.last_failure_time > self.recovery_timeout:
self.state = CircuitState.HALF_OPEN
else:
raise Exception("Circuit is Open")
try:
result = func(*args, **kwargs)
if self.state == CircuitState.HALF_OPEN:
self.state = CircuitState.CLOSED
self.failure_count = 0
return result
except Exception as e:
self.record_failure()
raise e
def record_failure(self):
self.failure_count += 1
self.last_failure_time = time.time()
if self.failure_count >= self.failure_threshold:
self.state = CircuitState.OPEN
تلاش مجدد راهبردی با پسنشینی (Backoff)
تلاش مجدد برای خطاهای گذرا، مانند نوسانات شبکه یا بارگذاری موقت، ضروری است. با این حال، تلاش فوری مجدد میتواند با ارسال ترافیک بیشتر به سرویسی که در حال مبارزه است، مشکل را بدتر کند. همیشه از پسنشینی نمایی (Exponential Backoff) با جیتر (Jitter) استفاده کنید.
جیتر تأخیر را تصادفی میکند تا از «گلهی طوفانی» (Thundering Herd) از تلاشهای مجدد که دقیقاً در همان زمان به سرویس ضربه میزنند، جلوگیری شود.
import random
def retry_with_backoff(func, max_retries=3, base_delay=1):
for attempt in range(max_retries):
try:
return func()
except Exception as e:
if attempt == max_retries - 1:
raise e
delay = (2 ** attempt) + random.uniform(0, 1)
time.sleep(delay)
تخریب ظریف: فالببکهایی که اهمیت دارند
وقتی یک سرویس اصلی در دسترس نیست، سیستم باید بهجای شکست کامل، بهصورت ظریف تخریب شود. این شامل تعریف رفتار جایگزین (Fallback) است که یک تجربه کاهشیافته اما کارآمد ارائه میدهد.
مثالهایی از تخریب ظریف عبارتند از:
- بازگرداندن دادههای کششده با یک نشانگر قدیمی بودن.
- ارائه محتوای پیشفرض یا ایستا.
- غیرفعال کردن ویژگیهای غیرحیاتی در حالی که عملکرد اصلی حفظ میشود.
برای مثال، اگر یک موتور توصیهدهی از کار بیفتد، یک سایت تجارت الکترونیک ممکن است بهجای نمایش صفحه خطا، به نمایش «اقلام محبوب» از یک کش محلی روی بیاورد.
نتیجهگیری
تابآوری یک انضباط طراحی است، نه یک فکر پس از عمل. با ترکیب مدارشکنها برای توقف خرابیهای زنجیرهای، تلاشهای مجدد هوشمند برای مدیریت مشکلات گذرا و تخریب ظریف برای حفظ اعتماد کاربر، سیستمهایی میسازید که نه تنها محکم، بلکه تابآور هستند. با ابزارسازی (Instrumenting) سرویسهای خود شروع کنید، مسیرهای حیاتی خود را شناسایی کنید و این الگوها را در مهمترین نقاط پیادهسازی کنید.