از سالهای گذشته استفاده از معماری مایکروسرویس به علت مزایایی که برای سیستم های بزرگ دارد در حال افزایش است، یکی از مسائلی که بیشتر در این معماری و معماری های توزیع شده حائز اهمیت است ارتباط بین سرویس ها می باشد.
در معماری Monolith با توجه به اینکه کل سرویس روی یک ماشین اجرا می شود، ارتباط بین بخش های مختلف سرویس بصورت In-Memory Call می باشد و یا همیشه کل سرویس در دسترس است یه بالعکس. اما زمانی که معماری متفاوت باشد سرویس ها روی ماشین های مختلف شبکه قرار دارند و ارتباط بصورت Remote Call می باشد که تعامل بین سرویس ها در سطح ارتباطات شبکه ای انجام می شود. تفاوت مهم این دو نوع ارتباط این است که ارتباطات Remote می توانند Fail شوند یا اینکه در زمان مشخص شده پاسخی دریافت نشود
در معماری مایکرو سرویس یک سرویس ممکن است دچار مشکل شده باشد و در دسترس نباشد (Unavailable) یا مشکلی در سطح شبکه ی آن سرویس باشد، در نتیجه سرویس به درخواست ها پاسخی نمی دهد.
در صورت بروز هر یک از این اتفاقات سرویسی(مثلا A) که منتظر دریافت پاسخ است و درخواست های زیادی را ارسال کرده است که منتظر پاسخ هستند و لحظه به لحظه به تعداد این درخواست ها افزوده می شود، در نتیجه میزان استفاده از منابع به علت تلنبار شدن درخواست های زیاد منتظر پاسخ، بالا می رود و اگر این اتفاقات بصورت زنجیره ای رخ دهد و سرویس(های) دیگری مثل B و C منتظر پاسخ سرویس A باشند، همه سیستم های این زنجیره ممکن است که دچار مشکل منابع شوند.
مثال : فرض کنید شما در یک فروشگاه پس از صدور فاکتور توسط فروشنده از طریق دستگاه کارتخوان اقدام به پرداخت صورتحساب می کنید. پس از اینکه کارت می کشید سرویس کارتخوان(A) سعی می کند درخواست را برای Provider خود (سرویس B) ارسال کند و Provider هم پس از دریافت درخواست آن را برای سرویس پرداخت نهایی (سرویس C) ارسال خواهد کرد و منتظر پاسخ می ماند، حال فرض کنید سرویس پرداخت نهایی Down می باشد یا به هر علتی هیچ پاسخی به درخواست ها نمی دهد.
چه اتفاقی رخ خواهد داد ؟
کاملاً مشخص است که پرداخت شما Failed می شود. اما اتفاق مهم تری که رخ می دهد این است که درخواست های زیادی که از کارتخوان های مختلف سطح کشور به سرویس B آمده اند، از سرویس B به سرویس C ارسال می شوند بدون اینکه پاسخی از سرویس C دریافت کنند و پس از طی مدت زمان مشخصی Timeout می شوند، البته با استفاده از الگوهایی مانند Retry Pattern می توان برای دفعات دیگری درخواست را ارسال کرد تا نتیجه حاصل شود، اما اگر سرویس C بعلت رخداد مشکل پیش بینی نشده ای برای مدتی نامشخص در دسترس نباشد چه اتفاقی خواهد افتاد؟
درخواست های زیادی در سرویس B در صف می مانند که مثلا موجب باز ماندن کانکشن های زیاد به دیتابیس(در صورت عدم مدیریت)، ایجاد Thread های زیاد و در نتیجه مصرف منابع سرویس B شروع به بالا رفتن می کند و در نتیجه امکان Down شدن سرویس B محتمل تر خواهد بود و ما بخش دیگری از سیستم را از دست خواهیم داد.
مفهوم Cascading Failure چیست؟<br/>زمانی است که قسمتی از سیستم (یکی از سرویس ها) از کار می افتد و این Failure به مرور زمان موجب ایجاد خطا و Failure در سیستم های دیگر خواهد شد. این مسئله ممکن است در هر سیستمی رخ دهد و مختص سیستم های نرم افزاری نمی باشد حتی در بدن انسان اگر یک عضو دچار عدم کارکرد یا نارسایی شود ممکن است باعث از کار افتادن سایر بخش ها شود.
دنیای نرم افزار برای رخداد چنین مشکلی حتما باید یک راه حل داشته باشد، به همین دلیل مهندسین کامپیوتر مشابه راه حل موجود در دنیای واقعی را در دنیای نرم افزار پیاده سازی کردند.
الگوی طراحی Circuit Breaker راه حل این مشکل است، اما چگونه؟
قطع کننده های مدار و دستگاههای محافظ برق لوازم الکتریکی را به یاد بیاروید، وقتی که جریان برق دچار نوسان می شود جریان برق به لوازم الکتریکی را قطع می کنند و تا زمانی که ولتاژ به وضعیت استاندارد نرسد جریان را وصل نمی کند. حتی زمانی که جریان به وضعیت استاندارد رسید، دستگاه محافظ بعد از چند دقیقه تست، اعلام وضعیت نرمال کرده و اتصال دستگاه به جریان برق را برقرار میکنند. عملکرد Circuit Breaker چیزی مشابه سناریوی فوق می باشد.
زمانی که در Failure های متوالی از یک سرویس دریافت می شوند و این Failure ها از یک Threshold عبور می کند Breaker وارد عمل می شود. اما Breaker چه کارهایی انجام می دهد.
بعد از عبور تعداد خطا ها از آستانه تعیین شده، تمامی درخواستهایی که به سرویس دارای خطا ارسال می شوند بلافاصله با یک خطا یا یک نتیجه عمومی پاسخ داده می شود و جریان ارسال درخواست به سرویس مشکل دار موقتا قطع می شود. ( Breaker در وضیعت Open قرار می گیرد)
این قطع ارتباط تا چه زمانی ادامه دارد؟
تا زمانی که سرویس به وضعیت Stable باز گردد.
زمانی که سرویس به وضعیت مناسب برگردد Breaker به حالت Half-Open می رود و شروع به تست سرویس می کند که آیا مشکل سرویس همچنان پا برجاست یا برطرف شده است؟
زمانیکه همه چیز به حالت نرمال برگشته باشد Breaker به حالت Closed می رود و تمامی Request ها اجازه می یابند به سرویس ارسال شوند و این چرخه ادامه خواهد یافت.
استفاده از این الگو باعث می شود که زمانی که یک قسمت یه سیستم دچار مشکل شده است یا موقتا Down شده است بقیه قسمت های سیستم بکار خود ادامه دهند و مشکل Cascading Failure رخ ندهد.
پیاده سازی Circuit Breaker را می توان بصورت Central در سطح Gateway یا برای هر سرویس پیاده سازی کرد، که بنا بر استفاده و نیاز هر سازمان مزایا و معایبی دارد که می توان به موارد زیر اشاره کرد:
برای آشنایی بیشتر می توانید لینک های زیر را مشاهده کنید:
https://github.com/Netflix/Hystrix
https://github.com/afex/hystrix-go/
https://github.com/sony/gobreaker
https://github.com/Travix-International/Hystrix.Dotnet
https://github.com/danielfm/pybreaker
https://github.com/yammer/circuit-breaker-js