در مدارهای الکتریکی ماژولی وجود دارد تحت عنوان قطع کننده مدار که وظیفه آن، قطع جریان الکترونیکی در شرایطی است که بار اضافی یا اتصال کوتاه در مدار به وجود میآید. در واقع وظیفهاین ماژول قطع مدار به صورت اتوماتیک در شرایط معیوب میباشد.
حال در دنیای نرمافزار هم مفهومی به عنوان الگوی طراحی قطع کننده وجود دارد که جهت توسعه نرمافزار از آن استفاده میکنند.
از این الگو جهت شناسایی و جلوگیری از خطاهای موجود در درخواست های خارجی استفاده میشود. این الگو منطقی رو جهت جلوگیری از تکرار مداوم خطا در زمانهایی که سرویس صدا زده شده در حالت نگهداری، خرابی موقت یا خطاهای ناگهانی شبکه باشد، ارائه میدهد.
یکی از تفاوتهای بزرگ بین درخواست های درون برنامهای و ارسال درخواست به سرویسهای خارجی این است که درخواست ارسال شده به یک سرویس خارجی تحت هر شرایطی ممکن است خراب شود یا تا زمان دریافت یک timeout بدون پاسخ بماند. این شرایط را برای حالتی در نظر بگیرید که تعداد درخواست های زیادی به یک سرویس خارجی غیر پاسخ گو وجود دارد که در این صورت به راحتی منابع موجود در سیستم به جهت پاسخ دهی درخواست ها به حداکثر ظرفیت خود رسیده و باعث ایجاد یک مشکل سراسری در برنامه میشود.
الگوی طراحی قطع کننده نقش یک واسط (ترجیحا asynchronous ) را بین درخواست دهنده(client) و پاسخ دهنده(Service or Supplier) ایجاد میکند که هنگامی که خرابی ها به یک آستانه مشخص رسیدند حالت قطع کننده فعال شده و دیگر درخواستی برای پاسخ دهنده(سرویس مورد نظر) ارسال نمیشود و مستقیما خود این الگو پاسخ مورد نظر را برای کاربر ارسال میکند.
به عنوان مثال ، فرض کنید در ساختار نرمافزاری مورد نظرتون از یک سرویس جستجوی خارجی استفاده میکنید. اگر به هر دلیلی مشکلی در این سرویس جستجو به وجود بیاید که باعث عدم پاسخ گویی یا تاخیر زیاد در ارسال پاسخ ها شود در صورتی که حجم درخواست های ارسالی به این سرویس بالا باشد میتواند مشکلی جدی برای نرمافزار ما ایجاد کند که در اینجا الگوی طراحی قطع کننده مدار میتواند با قطع ارتباط نرمافزار با سرویس مورد نظر ترافیک ایجاد شده را از بین برده و بر اساس منطق پیاده شده پاسخی مناسب به کاربران ارسال کند.
ساختار الگوی طراحی قطع کننده 1-1
بررسی شکل 1-1
در مرحله اول و دوم پاسخ درخواست حین برگشت از supplier دچار مشکل شده و به کاربر یک خطا نمایش داده میشود. در مرحله سوم با تکرار شدن ارسال درخواست به supplier و رسیدن به آستانه مواجه شدن با خطا، قطع کننده فعال میشود. نهایتا در مرحله چهارم، ارسال درخواست به Supplier توسط قطع کننده، قطع شده و خود نیز بر اساس منطق پیاده سازی شده، پاسخی را به کاربر ارسال میکند.
انواع حالات در الگوی طراحی قطع کننده مدار
1- Closed
2- Open
3- Half-Open
حالت بسته یا Closed
در این حالت ارتباط نرمافزار بدون هیچ مشکلی با سرویس خارجی در حال برقراری است و برنامه مطابق با انتظارات ما در حال کار کردن میباشد.
حالت Open
در این حالت تعداد درخواست های خطا به بیش از آستانه تعریف شده جهت ارسال درخواست رسیده و در یک بازه زمانی تعیین شده، درخواست های ارسالی به سرویس خارجی از کار می افتند و نرمافزار مطابق با سناریو پیاده شده در حالت قطع مدار به کاربران پاسخ میدهد.
حالت Half-Open
در این حالت نرمافزار سعی میکند درخواستی را جهت ارتباط با سرویس خارجی که قبلا با خطا مواجه شده بوده ، برقرار کند.
وقتی زمان مشخص شده به پایان میرسد وضعیت به حالت Half-Open تغییر میکند که در این حالت همانطور که پیشتر گفتیم نرمافزار تلاشی را جهت ارسال درخواست مجدد به سرویس خارجی انجام میدهد که در این حالت 2 سناریو وجود دارد :
1- درخواستی به سرویس خارجی ارسال شده و در صورتی که ارتباط با سرویس خارجی با موفقیت انجام شود مقدار شمارنده(تلاش های ناموفق) به صفر برگشته و الگوی طراحی به حالت Closed تغییر وضعیت می هد.
2- اگر بعد از ارسال درخواست مجدد، خطایی رخ دهد فرض بر این است که مشکل همچنان وجود دارد، بنابراین زمان به حالت اول برگشته و همچنین الگو طراحی به حالت Open تغییر وضعیت میدهد(در این حالت مجدد سرویس خارجی در دسترس نمیباشد)
فلوچارت مراحل موجود :
شکل 2-1
استفاده از الگوی طراحی قطع کننده در PHP
برای پیاده سازی این الگو با توجه به ساختار توضیح داده شده، نیازمند یک مخزن اطلاعات جهت ذخیره:
- تعداد درخواست های کاربر
- مقدار آستانه مشخص شده
- زمان
می باشیم . هرچه سرعت دریافت و ذخیره این اطلاعات بالاتر باشد در نتیجه روند اجرای این الگو سریعتر خواهد بود، لذا جهت ذخیره این موارد از ساختار کش استفاده خواهیم کرد.
class RateLimiter
{
use InteractsWithTime;
/**
* The cache store implementation.
*
*/
protected $cache;
/**
* Create a new rate limiter instance.
*
*
* @return void
*/
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
}
trait استفاده شده در این کلاس جهت کار با زمان میباشد که ساختار آن به شکل زیر میباشد :
trait InteractsWithTime
{
/**
* Get the number of seconds until the given DateTime.
*
* @param \DateTimeInterface|\DateInterval|int $delay
* @return int
*/
protected function secondsUntil($delay)
{
$delay = $this->parseDateInterval($delay);
return $delay instanceof DateTimeInterface
? max(0, $delay->getTimestamp() - $this->currentTime())
: (int) $delay;
}
/**
* Get the "available at" UNIX timestamp.
*
* @param \DateTimeInterface|\DateInterval|int $delay
* @return int
*/
protected function availableAt($delay = 0)
{
$delay = $this->parseDateInterval($delay);
return $delay instanceof DateTimeInterface
? $delay->getTimestamp()
: (new \DateTime())->add($delay)->getTimestamp();
}
/**
* If the given value is an interval, convert it to a DateTime instance.
*
* @param \DateTimeInterface|\DateInterval|int $delay
* @return \DateTimeInterface|int
*/
protected function parseDateInterval($delay)
{
if ($delay instanceof DateInterval) {
$delay = (new \DateTime())->add($delay);
}
return $delay;
}
/**
* Get the current system time as a UNIX timestamp.
*
* @return int
*/
protected function currentTime()
{
return (new \DateTime())->getTimestamp();
}
}
این کلاس بایستی دارای دو متد با کارکردهای زیر باشد :
1- بررسی تعداد درخواست های ناموفق
2- ذخیره تعداد درخواست های ناموفق و همچنین میزان زمان متوقف شدن یک عملکرد
ساختار این دو متد با نامهای tooManyAttempts و hit به شکل زیر میباشند :
/**
* Determine if the given key has been "accessed" too many times.
*/
public function tooManyAttempts($key, $maxAttempts)
{
if ($this->attempts($key) >= $maxAttempts) {
if ($this->cache->has($key.':timer')) {
return true;
}
$this->resetAttempts($key);
}
return false;
}
/**
* Increment the counter for a given key for a given decay time.
*/
public function hit($key, $decaySeconds = 60)
{
$this->cache->add(
$key.':timer', $this->availableAt($decaySeconds), $decaySeconds
);
$added = $this->cache->add($key, 0, $decaySeconds);
$hits = (int) $this->cache->increment($key);
if (! $added && $hits == 1) {
$this->cache->put($key, 1, $decaySeconds);
}
return $hits;
}
متد attempts در اینجا مشخص کننده تعداد تلاش های انجام شده میباشد که پیاده سازی آن به شکل زیر میباشد :
/**
* Get the number of attempts for the given key.
*/
public function attempts($key)
{
return $this->cache->get($key, 0);
}
/**
* Reset the number of attempts for the given key.
*/
public function resetAttempts($key)
{
return $this->cache->forget($key);
}
/**
* Get the number of retries left for the given key.
*/
public function retriesLeft($key, $maxAttempts)
{
$attempts = $this->attempts($key);
return $maxAttempts - $attempts;
}
/**
* Clear the hits and lockout timer for the given key.
*/
public function clear($key)
{
$this->resetAttempts($key);
$this->cache->forget($key.':timer');
}
در ادامه به توضیح متدهای این کلاس میپردازیم :
resetAttempts: مقدار تلاش های صورت گرفته برای یک عملکرد خاص را حذف میکند
clear: تلاش های صورت گرفته و همچنین زمان تعیین شده جهت توقف عملکرد را حذف میکند
retriesLeft: تعداد تلاشهای باقی مانده
availableIn: مدت زمان باقی مانده جهت تلاش مجدد
جمعبندی
ما در اینجا برای توضیح این الگو، ارتباط با سرویسها را بیان کردیم ، که یک مورد معمول برای قطع کننده های مدار است ، اما در هر موقعیتی که بخواهید، میتوانید با استفاده از این سناریو از قطعات سیستم در برابر خرابی قسمتهای دیگر محافظت کنید.
استفاده از این الگو ثبات و انعطاف پذیری را در برنامههای ما ایجاد میکند و به ما کمک میکند تا از مصرف منابعی که مستقیماً بر عملکرد سیستم ما تأثیر می گذارند، جلوگیری کنیم.
همچنین استفاده از این الگو در معماری Microservices میتواند طبعات بسیار مثبتی داشته باشد.
منابع
https://martinfowler.com/bliki/CircuitBreaker.html
https://docs.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker
https://www.codechief.org/article/how-to-use-circuit-breaker-design-pattern-in-laravel
https://medium.com/@DarkGhostHunter/laravel-there-is-a-rate-limiter-and-you-didnt-know-eb443b1bedc