Skip to content

Latest commit

 

History

History
287 lines (194 loc) · 34.3 KB

Circuit Breaker pattern.md

File metadata and controls

287 lines (194 loc) · 34.3 KB

‏Circuit Breaker pattern

برطرف‌کردن خطا‌های که زمان نامشخصی برای بررسی و حل آنها وجود دارد. در این شرایط این الگو می‌تواند ثبات(stability) و تاب‌آوری(resiliency) یک برنامه را بهبود بخشد.

موضوع و مشکل

در یک محیط توزیع‌شده، تماس‌ها با منابع و سرویس‌‌های ریموت ممکن است به دلیل خطا‌های گذرا (transient fault)، مانند اتصالات آهسته و کند در شبکه، timeoutها یا بیش از حد پاسخ‌دادن به درخواست‌ها یا در دسترس نبودن منابع ریموت، با خطا مواجه شوند. این خطاها معمولاً پس از مدت کوتاهی خود را اصلاح می‌کنند و یک برنامه ابری باکیفیت باید با استفاده از یک راهبُرد مانند Retry pattern آماده باشد تا آنها را مدیریت کند.

بااین‌حال، ممکن است شرایطی نیز وجود داشته باشد که خطاها ناشی از رویداد‌های غیرمنتظره باشد و رفع آن شاید زیاد طول بکشد. شدت این خطاها از قطع جزئی اتصال تا خرابی کامل یک سرویس متفاوت است. در این شرایط ممکن است برای یک برنامه بیهوده باشد که به طور مداوم عملیاتی را که بعید به نظر می‌رسد موفقیت‌آمیز باشد را بارها امتحان کند و در عوض برنامه باید سریعاً بپذیرد که عملیات شکست‌خورده است و براین‌اساس این شکست را مدیریت کند.

علاوه بر این، اگر یک سرویس بسیار تحت‌فشار باشد، خرابی در یک قسمت از سیستم ممکن است منجر به خرابی‌های پشت‌سرهم و متوالی شود. به‌عنوان‌مثال، عملیاتی که یک سرویس را فراخوانی می‌کند می‌تواند طوری config شود تا یک timeout را اجرا کند و اگر سرویس در این مدت مشخص پاسخ ندهد پس درنهایت با یک پیام شکست پاسخ خود را بدهد. بااین‌حال، این راهبُرد می‌تواند باعث شود که بسیاری از درخواست‌‌های هم‌زمان برای همان عملیات تا پایان دوره زمانی مسدود شوند. این درخواست‌های مسدود شده ممکن است منابع مهم سیستم مانند حافظه، thread ها، اتصالات پایگاه‌داده و غیره را در خود نگه دارند. در نتیجه، این منابع ممکن است تمام شود و باعث خرابی سایر بخش‌‌های احتمالاً نامرتبط سیستم شود که نیاز به استفاده از منابع مشابه دارند. در این مواقع، ترجیح داده می‌شود که عملیات فوراً با شکست مواجه شود و فقط در صورت احتمال وقوع موفقیت سعی کنید سرویس را فراخوانی کنید. توجه داشته باشید که تنظیم یک timeout کوتاه‌تر ممکن است به حل این مشکل کمک کند، اما مهلت زمانی(timeout) نباید آن‌قدر کوتاه باشد که عملیات در بیشتر مواقع با شکست مواجه شود، حتی اگر درخواست سرویس در نهایت با موفقیت انجام شود.

راه‌حل

الگوی قطع‌کننده مدار (Circuit Breaker) که توسط «مایکل نایگارد» در کتابش با عنوان  Release It! رایج شده است، می‌تواند مانع از تلاش مکرر برنامه برای اجرای عملیاتی شود که احتمال شکست آن وجود دارد. اجازه‌دادن به ادامه کار بدون انتظار برای رفع عیب یا هدردادن چرخه‌‌های CPU درحالی‌که مشخص می‌کند که حل مشکل طولانی‌مدت است. الگوی Circuit Breaker همچنین یک برنامه یا اپلیکیشن را قادر می‌سازد تا بررسی کند یا تشخیص دهد چه زمانی مشکل برطرف شده است. اگر به نظر می‌رسد مشکل برطرف شده است، برنامه می‌تواند سعی کند عملیات را فراخوانی کند.

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

قطع‌کننده مدار به‌عنوان یک پروکسی برای عملیاتی کار می‌کند که احتمال وقوع خطا در آن وجود دارد. پراکسی باید تعداد خرابی‌‌های که تازه رخ‌داده است را کنترل کند و از این اطلاعات برای تصمیم‌گیری برای ادامه عملیات استفاده کند یا به‌سرعت یک استثنا/exception را بازگرداند.

پروکسی را می‌توان به‌عنوان یک ماشین حالت (state machine) با حالت‌‌های زیر پیاده‌سازی کرد که عملکرد یک Circuit Breaker را تقلید می‌کند:

حالت بسته (Closed): این request از application به عملیات پردازشی مسیردهی می‌شود. پراکسی شماره و تعداد خرابی‌‌های اخیر را نگه می‌دارد و اگر فراخوانی عملیات ناموفق باشد، پراکسی شمارش این تعداد را افزایش می‌دهد. اگر تعداد خرابی‌‌های اخیر از یک آستانه مشخص در یک بازه زمانی معین بیشتر شود، پروکسی در حالت کلید باز قرار می‌گیرد. در این مرحله، پراکسی یک تایمر زمان‌بندی را شروع می‌کند و زمانی که این تایمر منقضی شد، پراکسی آن کلید را در حالت نیمه‌باز قرار می‌دهد.

هدف این شمارنده یا تایمر این است که به سیستم زمان بدهد تا مشکلی را که باعث خرابی شده است را قبل از اینکه به برنامه اجازه دهد تا دوباره عملیات را شروع کند، برطرف کند.

حالت باز (Open): که درخواست یا request از برنامه بلافاصله با شکست مواجه می‌شود و یک استثنا/exception به application باز می‌گردد.

حالت نیمه‌باز (Half-Open): تعداد محدودی از request ‌های برنامه مجاز به عبور و فراخوانی عملیات هستند. در صورت موفقیت‌آمیز بودن این درخواست‌ها، فرض بر این است که عیبی که قبلاً باعث خرابی شده بود برطرف شده است و کلید مدار به حالت بسته تغییر می‌کند (شمارگر خرابی تنظیم مجدد یا reset شده است). اگر هر درخواستی با شکست مواجه شود، Circuit Breaker فرض می‌کند که خطا همچنان وجود دارد، بنابراین به حالت باز برمی‌گردد و تایمر timeout را مجدداً راه‌اندازی می‌کند تا به سیستم یک timeout بیشتر برای بازیابی از خرابی بدهد.

حالت نیمه‌باز برای جلوگیری از طغیان‌کردن ناگهانی requestها در بازیابی سرویس مناسب است. زمانی که یک سرویس بازیابی می‌شود، ممکن است بتواند حجم محدودی از درخواست‌ها را تا زمانی که بازیابی کامل شود پشتیبانی کند، اما درحالی‌که بازیابی در حال انجام است، حجم زیاد کار می‌تواند باعث شود که سرویس دچار timeout بشود یا دوباره از کار بیفتد.

circuit-breaker-diagram

در شکل بالا، شمارشگر خرابی (failure counter) مورداستفاده در حالت بسته بوده و بر اساس زمان کار می‌کند و به طور خودکار در فواصل زمانی خاصی reset می‌شود. این حالت باعث جلوگیری از ورود circuit breaker به حالت باز در صورت بروز خرابی‌های گاه‌به‌گاه کمک می‌کند. آستانه خرابی که circuit breaker را به حالت باز می‌رساند، تنها زمانی به دست می‌آید که تعداد معینی از خرابی در یک بازه زمانی مشخص رخ‌داده باشد. شمارنده مورداستفاده در حالت نیمه‌باز، تعداد تلاش‌های موفقیت‌آمیز برای فراخوانی عملیات را ثبت می‌کند. پس از موفقیت‌آمیز بودن تعداد معینی از فراخوانی‌‌های متوالی، کلید مدار به حالت بسته کلید می‌کند. اگر هر فراخوانی ناموفق باشد، قطع‌کننده مدار بلافاصله وارد حالت باز می‌شود و دفعه بعد که وارد حالت نیمه‌باز شد، شمارنده success دوباره reset می‌شود.

چگونه بازیابی سیستم به‌صورت خارجی انجام می‌شود؟ احتمالاً با بازیابی یا راه‌اندازی مجدد یک مؤلفه خراب شده یا تعمیرکردن اتصال شبکه.

الگوی Circuit Breaker پایداری و ثباتی را فراهم می‌کند درحالی‌که سیستم پس از خراب‌شدن به‌سرعت بازیابی می‌شود و تأثیر آن بر کارایی برنامه را به حداقل می‌رساند. همین‌طور این الگو می‌تواند سرعت پاسخ‌دهی سیستم را حفظ کند و این کار را به کمک ردکردن سریع درخواست‌‌های عملیاتی که احتمال شکست آن وجود دارد، به‌جای اینکه منتظر بماند تا درخواست دچار timeout شود یا اصلاً پاسخی برای درخواست برنگردد. اگر circuit breaker هر بار که تغییر حالت می‌دهد، رویدادی را اعلان می‌کند، این گزینه می‌تواند برای نظارت بر سلامت (health monitor) بخشی از سیستم که توسط circuit breaker محافظت می‌شود یا برای هشداردادن به administrator هنگامی که یک circuit breaker به حالت باز می‌رود، استفاده شود.

این الگو باتوجه‌به نوع خرابی احتمالی قابل‌تنظیم است. به‌عنوان‌مثال، شما می‌توانید یک تایمر افزایش timeout را برای circuit breaker اعمال کنید. می‌توانید circuit breaker را در ابتدا برای چند ثانیه در حالت باز قرار دهید و سپس اگر خرابی برطرف نشد timeout را به چند دقیقه افزایش دهید و به همین ترتیب این کار را تکرار کنید. در برخی موارد، به‌جای بازگرداندن حالت باز که بیانگر شکست عملیات است و ایجاد یک exception/استثنا در ادامه آن، بازگرداندن یک مقدار پیش‌فرض که برای برنامه معنادار است می‌تواند مناسب‌تر باشد.

مسائل و ملاحظات

هنگام تصمیم‌گیری در مورد نحوه اجرای این الگو باید نکات زیر را در نظر بگیرید:

رسیدگی به استثناها - Exception Handling. برنامه‌ای که عملیاتی را از طریق circuit breaker فراخوانی می‌کند باید برای رسیدگی به استثنا‌های مطرح شده در صورت در دسترس نبودن عملیات آماده شود. نحوه رسیدگی به استثناها مختص و خاص هر برنامه خواهد بود. به‌عنوان‌مثال، یک برنامه می‌تواند به طور موقت کارایی خود را کاهش دهد و یک عملیات جایگزین را برای انجام همان کار یا به‌دست‌آوردن همان داده‌ها فراخوانی کند یا استثنایی را به کاربر گزارش دهد و از او بخواهد که بعداً دوباره امتحان و تلاش مجدد کند.

نوع استثنا - Types of Exceptions. یک درخواست ممکن است به دلایل زیادی با شکست مواجه شود که برخی از آنها ممکن است نشان‌دهنده نوع شدیدتر شکست نسبت به سایرین باشد. به‌عنوان‌مثال، یک درخواست ممکن است به دلیل ازکارافتادن یک سرویس ریموت حدود چند دقیقه طول بکشد تا بازیابی شود یا به‌خاطر timeout به دلیل بارگیری موقت سرویس، با شکست مواجه شود. یک circuit breaker ممکن است بتواند انواع استثنا‌هایی(exception) را که رخ می‌دهند بررسی کند و بسته به ماهیت این استثناها، راهبُرد خود را تنظیم کند. به‌عنوان‌مثال و در مقایسه با تعداد خرابی‌‌های ناشی از در دسترس نبودن سرویس، ممکن است به تعداد بیشتری از timeout exceptions نیاز داشته باشد تا circuit breaker در حالت باز قرار گیرد.

‌ Logging. یک قطع‌کننده مدار باید تمام درخواست‌های ناموفق (و احتمالاً درخواست‌های موفقیت‌آمیز) را ثبت کند تا administrator بتواند بر سلامت عملیات نظارت کند.

قابلیت بازیابی - Recoverability. حتماً باید circuit breaker را طوری پیکربندی کنید که با الگوی recovery احتمالی از عملیاتی که آن را محافظت می‌کند مطابقت داشته باشد. به‌عنوان‌مثال، اگر circuit breaker برای مدت طولانی در حالت باز بماند، حتی اگر دلیل خرابی برطرف شده باشد، می‌تواند exception ایجاد کند. به طور مشابه، circuit breaker می‌تواند در صورت تغییر سریع از حالت باز به حالت نیمه‌باز، نوسان داشته باشد و زمان پاسخگویی برنامه‌ها را کاهش دهد.

تست‌کردن عملیات شکست‌خورده - Testing Failed Operations. در حالت باز، به‌جای استفاده از شمارنده برای تعیین زمان تغییر به حالت نیمه‌باز، یک circuit breaker می‌تواند به طور دوره‌ای سرویس یا منبع ریموت را ping کند تا مشخص کند که آیا دوباره در دسترس است یا خیر. این ping می‌تواند به شکل تلاشی برای فراخوانی عملیاتی باشد که قبلاً ناموفق بوده است، یا می‌تواند از یک عملیات ویژه ارائه شده توسط سرویس ریموت به طور خاص برای آزمایش سلامت سرویس استفاده کند، همان‌طور که در الگوی Health Endpoint Monitoring pattern توضیح داده شده است.

تنظیم مجدد دستی - Manual Override. در سیستمی که زمان بازیابی برای یک عملیات ناموفق بسیار متغیر است، ارائه یک گزینه تنظیم مجدد دستی که به administrator امکان می‌دهد قطع‌کننده مدار را ببندد (و شمارنده خطا را بازنشانی کند) سودمند است. به طور مشابه، اگر عملیات محافظت شده توسط قطع‌کننده مدار موقتاً در دسترس نباشد، یک administrator می‌تواند یک circuit breaker را به حالت باز وادار کند (و timeout timer را مجدداً راه‌اندازی کند).

هم‌زمانی - Concurrency. هر قطع‌کننده مدار می‌تواند توسط تعداد زیادی از نمونه‌های(instances)‌ هم‌زمان یک برنامه قابل‌دسترسی باشد. پیاده‌سازی نباید request‌های همه‌مان را مسدود کند یا به هر فراخوانی به یک عملیات سربار اضافی وارد کند.

منابع متمایز - Resource Differentiation. اگر ممکن است چندین ارائه‌دهنده (provider) مستقل وجود داشته باشد، هنگام استفاده از یک قطع‌کننده مدار برای یک resource بسیار محتاط باشید. به‌عنوان‌مثال، در یک data store که حاوی چندین قطعه(shards) است، ممکن است یک مورد کاملاً در دسترس باشد درحالی‌که دیگری مشکلی موقتی را تجربه می‌کند. اگر پاسخ‌‌های خطا در این سناریوها ادغام شوند در نتیجه امکان دارد یک برنامه سعی کند به برخی از موارد صحیح دسترسی داشته باشد، حتی زمانی که احتمال شکست بسیار زیاد است. برعکس این حالت در زمانی است که دسترسی به موارد دیگر ممکن است مسدود شود حتی اگر احتمال موفقیت آن وجود داشته باشد.

**شتاب دادن به قطع‌کننده مدار - Accelerated Circuit Breaking **. گاهی اوقات یک پاسخ خطا می‌تواند حاوی اطلاعات کافی باشد تا کلید مدار فوراً خاموش شود و برای زمان کوتاهی خاموش بماند. به‌عنوان‌مثال، پاسخ خطا از یک منبع مشترک که دچار سرریز ناشی از بار زیاد شده است می‌تواند نشان دهد که تلاش مجدد یا retry کردن به‌هیچ‌وجه توصیه نمی‌شود و در عوض برنامه باید چند دقیقه دیگر دوباره درخواست خود را امتحان کند.

نکته: یک سرویس می‌تواند HTTP 429 (Requestsهای خیلی زیاد) را برگرداند درصورتی‌که کلاینت درخواست‌‌های بیش از حد لازم را ارسال می‌کند یا HTTP 503 (سرویس در دسترس نیست) را برگرداند اگر سرویس در حال حاضر در دسترس نباشد. پاسخ‌‌های سرویس می‌تواند شامل اطلاعات اضافی مانند مدت‌زمان پیش‌بینی‌شده تأخیر باشد.

اجرای مجدد درخواست‌های ناموفق - Replaying Failed Requests. یک circuit breaker در حالت سوئیچ باز به‌جای اینکه به‌سرعت از کار بیفتد می‌تواند جزئیات هر درخواست را در یک journal ثبت کند و ترتیبی دهد که این درخواست‌ها زمانی که منابع یا سرویس ریموت در دسترس قرار می‌گیرد، دوباره اجرا شوند.

تایم‌اوت نامناسب در سرویس‌های خارجی - Inappropriate Timeouts on External Services. قطع‌کننده مدار ممکن است نتواند به طور کامل از برنامه‌ها در برابر عملیاتی که در سرویس‌های خارجی که با یک timeout طولانی پیکربندی شده‌اند و با شکست مواجه می‌شوند، محافظت کند. اگر timeout بیش از حد طولانی باشد، ممکن است thread ای که از یک قطع‌کننده مدار استفاده می‌کند، برای مدت طولانی مسدود شود، قبل از اینکه قطع‌کننده مدار نشان دهد که عملیات شکست‌خورده است. در این زمان، بسیاری از نمونه‌های دیگر نیز ممکن است سعی کنند سرویس را از طریق قطع‌کننده مدار فراخوانی کنند و تعداد قابل‌توجهی از threadها را قبل از اینکه همه آنها شکست بخورند را ببندند.

چه زمانی از این الگو استفاده کنیم

از این الگو استفاده کنید:

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

این الگو توصیه نمی‌شود:

  • برای مدیریت دسترسی به منابع خصوصی داخلی در یک اپلیکیشن، مانند in-memory data structureها . در این محیط، استفاده از قطع‌کننده مدار باعث افزایش سربار (overhead) به سیستم شما می‌شود.

  • به‌عنوان جایگزینی برای رسیدگی به استثناها (handling exceptions) در منطق تجاری برنامه.

مثال

در یک برنامه تحت وب که چندین صفحه با داده‌های به‌دست‌آمده از یک سرویس خارجی پر می‌شوند. اگر سیستم حداقل مقدار caching را پیاده‌سازی کند، بیشتر بازدیدها به این صفحات باعث ایجاد یک حالت رفت و برگشتی یا به‌اصطلاح round trip به سرویس مقصد می‌شود. اتصالات از برنامه تحت وب به سرویس را می‌توان با یک timeout در بازه زمانی (معمولاً ۶۰ ثانیه) پیکربندی کرد و اگر سرویس در این زمان پاسخ ندهد، منطق موجود در هر صفحه وب فرض می‌کند که سرویس در دسترس نیست و یک exception ایجاد می‌کند.

بااین‌حال، اگر سرویس از کار بیفتد و سیستم بسیار پر مشغله یا تحت‌فشار کاری باشد، کاربران باید حدود تا ۶۰ ثانیه منتظر بمانند تا یک exception رخ دهد. در نهایت منابعی مانند حافظه، ارتباطات شبکه و threadها ممکن است تمام شود و از اتصال سایر کاربران به سیستم جلوگیری کند، حتی اگر به صفحاتی که داده‌ها را از سرویس بازیابی می‌کنند دسترسی نداشته باشند.

مقیاس‌بندی (Scaling) سیستم با افزودن وب سرورهای بیشتر و اجرای توزیع‌کننده بار (load balancing) ممکن است تا زمانی که منابع تمام می‌شوند به تأخیر بیفتد؛ اما این مشکلی را حل نمی‌کند؛ زیرا درخواست‌های کاربرها همچنان دارای پاسخ نیستند و همه سرورهای وب ممکن است در نهایت منابع خود را تمام کنند.

کلاس CircuitBreaker اطلاعات وضعیت را در مورد circuit breaker در یک object که interface ICircuitBreakerStateStore نشان‌داده‌شده در کد زیر را پیاده‌سازی می‌کند، حفظ می‌کند.

interface ICircuitBreakerStateStore
{
  CircuitBreakerStateEnum State { get; }

  Exception LastException { get; }

  DateTime LastStateChangedDateUtc { get; }

  void Trip(Exception ex);

  void Reset();

  void HalfOpen();

  bool IsClosed { get; }
}

مشخصه State وضعیت فعلی circuit breaker را نشان می‌دهد و همان‌طور که توسط شمارش CircuitBreakerStateEnum تعریف شده است، Open، HalfOpen یا Closed خواهد بود. اگر کلید مداربسته باشد، ویژگی IsClosed باید درست باشد، اما اگر باز یا نیمه‌باز باشد، باید نادرست باشد. متد Trip وضعیت circuit breaker را به حالت باز تغییر می‌دهد و استثنایی را که باعث تغییر حالت شده است به همراه تاریخ و ساعت وقوع استثنا ثبت می‌کند. ویژگی‌های LastException و LastStateChangedDateUtc این اطلاعات را برمی‌گرداند. متد Reset نیز circuit breaker را می‌بندد و روش HalfOpen نیز circuit breaker را روی نیمه‌باز قرار می‌دهد.

کلاس InMemoryCircuitBreakerStateStore در این مثال شامل پیاده‌سازی interface ICircuitBreakerStateStore است. کلاس CircuitBreaker نمونه‌ای از این کلاس را ایجاد می‌کند تا وضعیت CircuitBreaker را حفظ کند.

متد ExecuteAction در کلاس CircuitBreaker عملیاتی را که به‌عنوان یک Action delegate مشخص شده است، بسته‌بندی (wraps) می‌کند. اگر قطع‌کننده مداربسته باشد، ExecuteAction delegate Action را فراخوانی می‌کند. اگر عملیات ناموفق باشد، یک کنترل‌کننده خطا TrackException را فراخوانی می‌کند که وضعیت circuit breaker را برای باز کردن آن را تنظیم می‌کند. مثال کد زیر این جریان را مشخص می‌کند.

public class CircuitBreaker
{
  private readonly ICircuitBreakerStateStore stateStore =
    CircuitBreakerStateStoreFactory.GetCircuitBreakerStateStore();

  private readonly object halfOpenSyncObject = new object ();
  ...
  public bool IsClosed { get { return stateStore.IsClosed; } }

  public bool IsOpen { get { return !IsClosed; } }

  public void ExecuteAction(Action action)
  {
    ...
    if (IsOpen)
    {
      // The circuit breaker is Open.
      ... (see code sample below for details)
    }

    // The circuit breaker is Closed, execute the action.
    try
    {
      action();
    }
    catch (Exception ex)
    {
      // If an exception still occurs here, simply
      // retrip the breaker immediately.
      this.TrackException(ex);

      // Throw the exception so that the caller can tell
      // the type of exception that was thrown.
      throw;
    }
  }

  private void TrackException(Exception ex)
  {
    // For simplicity in this example, open the circuit breaker on the first exception.
    // In reality this would be more complex. A certain type of exception, such as one
    // that indicates a service is offline, might trip the circuit breaker immediately.
    // Alternatively it might count exceptions locally or across multiple instances and
    // use this value over time, or the exception/success ratio based on the exception
    // types, to open the circuit breaker.
    this.stateStore.Trip(ex);
  }
}

مثال زیر کدی را نشان می‌دهد (که از مثال قبلی حذف شده است) که در صورت بسته نبودن circuit breaker اجرا می‌شود. این مثال ابتدا بررسی می‌کند که آیا circuit breaker برای مدتی بیشتر از زمان مشخص شده توسط فیلد محلی OpenToHalfOpenWaitTime در کلاس CircuitBreaker باز بوده است یا خیر. اگر این‌طور باشد، متد ExecuteAction نیز circuit breaker را روی نیمه‌باز تنظیم می‌کند، سپس سعی می‌کند عملیات مشخص شده توسط delegate Action را انجام دهد.

در صورت موفقیت‌آمیز بودن عملیات، circuit breaker به حالت بسته بازنشانی می‌شود. اگر عملیات با شکست مواجه شود، به حالت باز برمی‌گردد و زمان وقوع exception به‌روزرسانی می‌شود تا circuit breaker قبل از تلاش مجدد برای انجام عملیات، مدت بیشتری منتظر بماند.

علاوه بر این، از یک قفل برای جلوگیری از تلاش circuit breaker برای برقراری تماس هم‌زمان با عملیات درحالی‌که نیمه‌باز است، استفاده می‌کند. تلاش هم‌زمان برای فراخوانی عملیات به‌گونه‌ای انجام می‌شود که گویی circuit breaker باز است و با استثنایی که بعداً توضیح داده شد با شکست مواجه می‌شود.

...
    if (IsOpen)
    {
      // The circuit breaker is Open. Check if the Open timeout has expired.
      // If it has, set the state to HalfOpen. Another approach might be to
      // check for the HalfOpen state that had be set by some other operation.
      if (stateStore.LastStateChangedDateUtc + OpenToHalfOpenWaitTime < DateTime.UtcNow)
      {
        // The Open timeout has expired. Allow one operation to execute. Note that, in
        // this example, the circuit breaker is set to HalfOpen after being
        // in the Open state for some period of time. An alternative would be to set
        // this using some other approach such as a timer, test method, manually, and
        // so on, and check the state here to determine how to handle execution
        // of the action.
        // Limit the number of threads to be executed when the breaker is HalfOpen.
        // An alternative would be to use a more complex approach to determine which
        // threads or how many are allowed to execute, or to execute a simple test
        // method instead.
        bool lockTaken = false;
        try
        {
          Monitor.TryEnter(halfOpenSyncObject, ref lockTaken);
          if (lockTaken)
          {
            // Set the circuit breaker state to HalfOpen.
            stateStore.HalfOpen();

            // Attempt the operation.
            action();

            // If this action succeeds, reset the state and allow other operations.
            // In reality, instead of immediately returning to the Closed state, a counter
            // here would record the number of successful operations and return the
            // circuit breaker to the Closed state only after a specified number succeed.
            this.stateStore.Reset();
            return;
          }
        }
        catch (Exception ex)
        {
          // If there's still an exception, trip the breaker again immediately.
          this.stateStore.Trip(ex);

          // Throw the exception so that the caller knows which exception occurred.
          throw;
        }
        finally
        {
          if (lockTaken)
          {
            Monitor.Exit(halfOpenSyncObject);
          }
        }
      }
      // The Open timeout hasn't yet expired. Throw a CircuitBreakerOpen exception to
      // inform the caller that the call was not actually attempted,
      // and return the most recent exception received.
      throw new CircuitBreakerOpenException(stateStore.LastException);
    }
    ...

برای استفاده از یک شیء CircuitBreaker برای محافظت از یک عملیات، یک برنامه یک نمونه (instance) از کلاس CircuitBreaker ایجاد می‌کند و متد ExecuteAction را فراخوانی می‌کند و عملیاتی را که باید به‌عنوان پارامتر انجام شود را مشخص می‌کند. اگر عملیات به دلیل باز بودن CircuitBreaker شکست خورد، برنامه باید آماده باشد تا exception CircuitBreakerOpenException را بگیرد. کد زیر یک مثال را نشان می‌دهد:

var breaker = new CircuitBreaker();

try
{
  breaker.ExecuteAction(() =>
  {
    // Operation protected by the circuit breaker.
    ...
  });
}
catch (CircuitBreakerOpenException ex)
{
  // Perform some different action when the breaker is open.
  // Last exception details are in the inner exception.
  ...
}
catch (Exception ex)
{
  ...
}

منابع مرتبط

الگوهای زیر نیز ممکن است هنگام اجرای این الگو مفید باشند:

  • الگوی Reliable web app به شما نشان می‌دهد که چگونه الگوی قطع‌کننده مدار را برای برنامه‌ها و اپلیکیشن‌های تحت وب که مناسب اجرا روی محیط ابری هستند را اعمال کنید.

  • الگوی Retry توضیح می‌دهد که چگونه یک برنامه می‌تواند با خرابی‌های موقت پیش‌بینی‌شده زمانی که سعی می‌کند به یک سرویس یا منبع شبکه وصل شود، با اجرای مجدد عملیاتی که قبلاً شکست‌خورده است، مقابله کند.

  • الگوی Health Endpoint Monitoring یک قطع‌کننده مدار ممکن است بتواند سلامت یک سرویس را با ارسال یک درخواست به endpoint ای که توسط یک سرویس expose شده است را آزمایش کند. سرویس باید اطلاعاتی را که وضعیت آن را نشان می‌دهد را برگرداند.