در معماری‌های نرم‌افزاری، همانند مدارهای الکترونیک، مفهومی داریم به نام "قطع کن". این مفهوم از دنیای الکتریک وارد معماری‌های نرم‌‎افزاری شده است.

 
 

حتما فیوزهای مینیاتوری را در سیستم‌های برق‌کشی ساختمانی دیدید. کار این فیوزها که عمدتا در جعبه تقسیم قرار دارند، جهت قطع کردن مدار در زمان به‎ وجود آمدن یک اتصالی در سیم‌کشی داخلی، یا به عبارت دقیقتر، زمانی که میزان مصرف بیشتر از آستانه این فیوز است. مثلا یخچال‌ها که مصرف بیشتری دارند، به فیوزهای 25 آمپر و روشنایی‌ها که مصرف کمتری دارند، به فیوزهای 16 آمپر مجهز می‌کنند. کل برق یک واحد را هم به فیوزهای 32 آمپر تجهیز می‎کنند.

جالب این است که دقیقا همین الگو رو در معماری‌های نرم‌افزاری هم داریم. در این نوشته مختصری در خصوص این موضوع توضیح خواهیم داد. قبل از ورود به این الگو باید مفهوم IO را بشناسیم.

IO Operation چیست؟

برای نوشتن یک نرم‌افزار مفاهیم بیرونی متعددی بجز برنامه نوشته شده، دست به دست هم می‌دهند تا در نهایت یک امکان فراهم شود. به‎عنوان مثال، بانک اطلاعاتی در اکثر نرم‌افزارها وجود دارد که داده‌های کارکردی یا موجودیت‌های یک نرم‌افزار در آن نگهداری می‌شود. (این بانک اطلاعاتی، بر حسب نیاز، می‌تواند اشکال و مدل‌های مختلفی داشته باشد: relational, key-value, ... )

در واقع عملیات IO به هر رویدادی گفته می‌شود که از مسیر اصلی برنامه یا main thread برنامه خارج شود و عموما این اصل برای IO در برنامه‌نویسی حاکم است که IO یک عملیات زمان‌بر است. لیست زیر انواع IO است:

  • خواندن از فایل
  • برقراری ارتباط شبکه‌ای شامل
    1. برقراری ارتباط با بانک‌های اطلاعاتی
    2. فراخوانی وب‌سرویس
    3. فراخوانی سرویس‌های بیرونی

فیوز یا قطع کن در نرم‌افزار

فرض کنید یک سیستم نرم‌افزاری داریم که برای انجام کارش به زیرسامانه‌ها و ماژول‌های مختلفی شکسته شده و هر کدام از این ماژول‌ها هم در معماری میکروسرویس از چندین میکروسرویس استفاده می‌کنند.

میکروسرویسی رو فرض کنیم که کارش این است که یک استعلام از یک سرویس بگیرد (فرض کنیم یک سرویس احراز هویت را باید مثلا از ثبت احوال صدا بزنیم یا قرار است یک پیامک بفرستیم یا قرار است یک ایمیل ارسال کنیم).

این میکروسرویس یک از کارهایی است که در یک سرویس بالادستی (مثلا سرویس ثبت سفارش) انجام شده و آن سرویس ثبت سفارش توالی زیر را انجام می‌دهد:

  1. دیتابیس را صدا زده و اطلاعاتی را واکشی می‌کند
  2. میکروسرویس احراز هویت را صدا می‎زند
  3. محاسباتی را انجام می‌دهد
  4. نتیجه محاسبات را در دیتابیس ذخیره می‌کند
  5. یک پیامک می‌زند
  6. جواب را بر می‌گرداند

فرض کنیم این سرویس، 100 بار در ثانیه صدا زده می‌شود و قرار است طبق SLA توافق شده در 5 ثانیه به سرویس بالادستی‌اش جواب برگرداند. این یعنی باید میکروسرویس احراز هویت و میکروسرویس پیامک، حداکثر در 3 ثانیه جواب سرویس ثبت سفارش را برگردانند. همچنین 2 ثانیه هم، خود سرویس ثبت سفارش برای سایر کارهایش نیازمند زمان است.

 
 

 

حالا شرایطی رو فرض کنیم که سرویس احراز هویت یا پیامک قطع شده یا ارتباط با آن برقرار نیست. در این حالت چه اتفاقی می‎افتد؟

 
 

در این حالت اغلب خطاهایی در سطح شبکه اتفاق خواهد افتاد که عمدتا خطای Connection time out است و خطا در لایه شبکه معمولا حدود چندین ثانیه طول می‌کشد که در شرایط مختلف این زمان متغیر است. (از 15 ثانیه به بالا) این زمان به‌گونه‌ای است که SLA سرویس زیر پا گذاشته خواهد شد و سرویس بالادستی (سرویس ثبت سفارش) هم تحت تاثیر این شرایط قرار خواهد گرفت و در زمان مورد نظرش امکان پاسخ‌دهی نخواهد داشت.

در این حالت thread ای که سرویس ثبت سفارش رو صدا زده باید باز بماند تا زمانی که سرویس احراز هویت به time out رسیده و به اصلاح خطای مورد نظرش را برگرداند. پس تبعات زیر را متصور هستیم:

  1. باز ماندن Thread در سرویس بالادستی
  2. باز ماندن Connection در سرویس بالادستی تا کلاینت
  3. باز ماندن Connection از سرویس بالادستی تا سرویس احراز هویت
  4. باز ماندن Thread در سرویس احراز هویت

همانطور که می‌دانیم هر thread باز و هر اتصال TCP باز، از سیستم‌عامل، منابع خودش را می‌گیرد و در حالتی که این قطعی اتفاق می‌افتد منابع سیستم‌عامل (CPU و RAM و Network) صرف شده تا در نهایت time out اتفاق افتاده و طولانی شدن این شرایط منجر به halt شدن سیستم خواهد شد.

راهکار برای این شرایط استفاده از الگوی طراحی Circuit Breaker است که نقش آن حفظ کردن SLA تعریف شده برای یک سرویس است.

Circuit Breaker یک قطعه برنامه یا ماژول است که در معماری‌های میکروسرویس به‌کار گرفته می‌شود (به‌دلیل تعدد dependency که در این معماری وجود دارد، اهمیت به‌کارگیری این الگو، بیش از پیش است. هر چند امکان استفاده از آن در هر معماری وجود دارد) که در مسیر (مدار) عملیات IO مستقر شده و بر اساس پارامترهای تعریف شده، اقدام به باز کردن مدار (قطع کردن ارتباط بیرونی) و بستن مدار (برقرار کردن ارتباط) می‎نماید.

این الگو معمولا برای بازکردن مدار 3 پارامتر را در نظر دارد:

 

  1. تعداد خطاهایی که ملاک بازکردن مداراست
  2. بازه مورد نظر باز کردن مدار
  3. مدت زمان قطع بودن مدار تا تست مجدد

به‎‌ کارگیری این الگو در وضعیت بحران، شرایط زیر را به وجود خواهد آورد:

  1. بعد از سپری شدن 3 ثانیه در صورتی که سرویس پاسخ نداد، خطای مورد نظر به سرویس بالادستی بازگردانده خواهد شد.
  2. در صورتی که تعداد گام اول بیشتر از پارامتر تعریف شده باشد، دیگر سرویس احراز هویت صدا زده نخواهد شد (مثلا 10 بار اگر جواب نداد) و مدار باز می‌شود.
  3. در زمان باز بودن مدار بلافاصله سرویس خطا داده و پیام می‎دهد: ارتباط با سرویس احراز هویت برقرار نیست.
  4. بعد از گذشت مثلا 60 ثانیه، مدار دوباره بسته شده (چون ممکن است ارتباط دوباره برقرار شود) و این چرخه مجددا تکرار خواهد شد.

به‌کارگیری در دنیای واقعی

ما در آویهنگ از این الگوی معماری، در پروژه‎های مختلف، زمانی که سرویس‎های بیرونی (محیط external) را فراخوانی می‎کنیم، به کرات استفاده کردیم و این باعث شده تا از مشکلاتی که در زمان قطعی برای کل معماری رخ می‎دهد، جلوگیری شود.