Software Architecture

معماری مقاوم: طراحی سیستم‌هایی که در برابر خطا مقاومت می‌کنند

در عصر محاسبات بومی ابری و میکروسرویس‌های توزیع‌شده، زمان از دست رفته دیگر فقط یک ناراحتی نیست، بلکه یک ریسک حیاتی برای کسب‌وکار است. کاربران انتظار دسترسی ۲۴/۷ دارند و یک نقطه شکست می‌تواند به یک قطعی سراسری منجر شود. معماری مقاوم تنها درباره اجتناب از باگ‌ها نیست؛ بلکه یک فلسفه طراحی است که شکست را اجتناب‌ناپذیر فرض کرده و سیستم را برای مدیریت آن به صورت شایسته ساختاردهی می‌کند.

برای توسعه‌دهندگان متوسط و پیشرفته، درک مقاومت فراتر از مدیریت خطاهای پایه‌ای است. این امر نیازمند پیاده‌سازی الگوهای خاصی است که به سیستم‌ها اجازه می‌دهد شکست‌ها را تشخیص داده، آن‌ها را ایزوله کنند و بدون خدشه‌دار کردن تجربه کاربر، بازیابی شوند. در این پست، اجزای اصلی معماری مقاوم را کاوش خواهیم کرد و بر الگوی مدارشکن (Circuit Breaker) تمرکز می‌کنیم که برای جلوگیری از شکست‌های زنجیره‌ای در سیستم‌های توزیع‌شده ضروری است.

فلسفه شکست

مقاومت بر این پیش‌فرض استوار است که اجزا شکست خواهند خورد. جدایی شبکه، زمان‌های انتظار سرویس و اتمام منابع رخ‌داد‌های رایجی هستند. یک سیستم مقاوم تلاش نمی‌کند تا تمام شکست‌ها را پیشگیری کند—این غیرممکن است—بلکه تأثیر آن‌ها را به حداقل می‌رساند. اصول کلیدی عبارتند از:

  • شکست سریع: تشخیص زودهنگام شکست‌ها برای جلوگیری از اتلاف منابع روی عملیات‌های محکوم به شکست.
  • تخریب شایسته: ادامه ارائه عملکرد اصلی حتی زمانی که سرویس‌های غیرضروری از دسترس خارج هستند.
  • ایزوله‌سازی: اطمینان از اینکه شکست در یک سرویس، منابع سرویس دیگری را مستهلک نمی‌کند (مانند اتمام مخزن نخ).

پیاده‌سازی الگوی مدارشکن

یکی از مؤثرترین ابزارها برای دستیابی به مقاومت، الگوی مدارشکن (Circuit Breaker) است. این الگو از یک برنامه جلوگیری می‌کند تا مکرراً تلاش کند عملیاتی را اجرا کند که احتمال شکست آن زیاد است. آن را مانند یک فیوز الکتریکی در نظر بگیرید: اگر خطایی تشخیص داده شود، مدار قطع می‌شود و جریان را متوقف می‌کند تا زمانی که مشکل برطرف شود.

در نرم‌افزار، مدارشکن سه حالت دارد:

  1. بسته: درخواست‌ها به طور عادی جریان دارند. اگر نرخ شکست از یک آستانه فراتر رود، مدارشکن عمل می‌کند.
  2. باز: درخواست‌ها بلافاصله رد می‌شوند یا با یک مکانیزم جایگزین مدیریت می‌شوند. به سیستم زمان داده می‌شود تا بازیابی شود.
  3. نیمه‌باز: پس از گذشت زمان انتظار، سیستم اجازه می‌دهد تعداد محدودی درخواست آزمایشی ارسال شود تا ببیند آیا سرویس بازیابی شده است یا خیر.

در زیر یک نمونه ساده پایتون آورده شده است که پیاده‌سازی اولیه یک مدارشکن را با استفاده از دکوراتورها نشان می‌دهد:

import time
from functools import wraps

class CircuitBreakerOpen(Exception):
    pass

def circuit_breaker(fail_threshold=5, recovery_timeout=60):
    def decorator(func):
        times_opened = 0
        opened_at = 0

        @wraps(func)
        def wrapper(*args, **kwargs):
            nonlocal times_opened, opened_at
            
            # بررسی کنید که آیا مدار باز است و زمان بازیابی گذشته است
            if times_opened >= fail_threshold:
                if time.time() - opened_at < recovery_timeout:
                    raise CircuitBreakerOpen(f"مدار برای {func.__name__} باز است")
                else:
                    # بازنشانی به حالت نیمه‌باز (ساده‌سازی شده)
                    times_opened = 0
            
            try:
                return func(*args, **kwargs)
            except Exception as e:
                times_opened += 1
                if times_opened >= fail_threshold:
                    opened_at = time.time()
                raise e
        return wrapper
    return decorator

# مثال استفاده
@circuit_breaker(fail_threshold=3, recovery_timeout=10)
def call_external_service():
    # شبیه‌سازی یک شکست
    raise ConnectionError("سرویس در دسترس نیست")

نتیجه‌گیری

مقاومت ویژگی‌ای نیست که در انتهای توسعه به سیستم اضافه کنید؛ بلکه یک ویژگی کیفی است که باید از روز اول در بافت معماری شما گنجانده شود. با پذیرش الگوهایی مانند مدارشکن، پیاده‌سازی مکانیزم‌های تلاش مجدد قوی با بازگشت نمایی، و طراحی برای تخریب شایسته، می‌توانید سیستم‌هایی بسازید که حتی در شرایط نامساعد نیز پایدار باقی می‌مانند. هنگامی که میکروسرویس یا برنامه توزیع‌شده بعدی خود را بازسازی می‌کنید، از خود بپرسید: «اگر این وابستگی شکست بخورد چه اتفاقی می‌افتد؟» پاسخ به آن سوال، پایه و اساس مقاومت است.

Share: