رفتن به متن راهنما
همه‌ی راهنماها

مدارشکن

مدارشکن را تنظیم کنید، هفت آرگومان آن را به ترتیب بخوانید، مشخص کنید کدام نتیجه‌ها شکست حساب می‌شوند و رد شدنِ تماس در حالت باز را بشناسید.

برنامه Reqtide PHP 0.1.0آخرین بررسی زمان خواندن 6 دقیقه

در این صفحه

وضعیت مدار در یک پردازش PHP نگه داشته می‌شود. در PHP-FPM پایپ‌لاینی که داخل یک درخواست ساخته شود، هر درخواست را با مدار بسته شروع می‌کند و هیچ درخواستی شکست‌های درخواست دیگر را نمی‌بیند. محدودیت‌های Reqtide را ببینید.

بلوک‌های کد از examples/09-choosing-policies.php (opens in a new tab) گرفته شده‌اند. اسکریپتی آن را اجرا می‌کند و خروجی را با خروجی ثبت‌شده مقایسه می‌کند. عددها فقط رفتار را قابل‌دیدن می‌کنند و پیشنهاد نیستند.

مدارشکن برای چیست

وابستگی از کار افتاده است و فراخوانی‌کننده‌ها مدام به آن تماس می‌گیرند. هر تماس منتظر می‌ماند، شکست می‌خورد و بار را بیشتر می‌کند. مدارشکن نتیجهٔ تماس‌های اخیر را زیر نظر دارد. وقتی تعداد زیادی از آن‌ها شکست بخورند، باز می‌شود و تماس‌های جدید را بی‌درنگ و بدون اجرای کد شما رد می‌کند. پس از مدتی چند تماس آزمایشی (probe) را عبور می‌دهد تا ببیند وابستگی بهبود یافته است یا نه.

تنظیم آن

قطعه کد زیر پس از چند شکست پیاپی یک مدار را باز می‌کند و نشان می‌دهد تماس پنجم چه نتیجه‌ای می‌گیرد. همان مدارشکن را روی پنجرهٔ زمانی هم می‌سازد. شروع آن این تنظیم مشترک است:

PHP
/** Every pipeline below starts from the same two required options. */
$base = static fn(): PipelineBuilder => PipelineBuilder::profile('choosing', 'v1')
    ->partition('shop', 'prod', 'catalog-api')
    ->attemptTimeout(Duration::seconds(2), AttemptTimeoutRequirement::Cooperative);

$ok = static fn(): string => 'ok';
$context = static fn(CompiledPipeline $pipeline): ExecutionContext => $pipeline->newContext(Duration::seconds(30));
PHP
echo "-- 3. Circuit breaker: count window or time window\n";
$countWindow = PipelineBuilder::lastCalls(4);
$timeWindow = PipelineBuilder::timeWindow(Duration::seconds(60), Duration::seconds(5));
$pipeline = $base()
    ->circuitBreaker($countWindow, 4, Ratio::of(1, 2), Duration::seconds(30), 1, 1, Duration::seconds(10))
    ->compileInMemory(ExceptionClassifier::create());
$invoked = 0;
$broken = static function () use (&$invoked): string {
    ++$invoked;

    throw new \DomainException('down');
};
for ($i = 1; $i <= 5; ++$i) {
    try {
        $pipeline->execute($broken, $context($pipeline));
    } catch (\DomainException) {
        // Closed circuit: your exception comes through.
    } catch (AdmissionRejectedException $e) {
        echo "call {$i} refused with {$e->reason()->value}; the callable ran {$invoked} times\n";
    }
}
// The same breaker over a 60 s window split into 5 s buckets.
$timeBased = $base()
    ->circuitBreaker($timeWindow, 4, Ratio::of(1, 2), Duration::seconds(30), 1, 1, Duration::seconds(10))
    ->compileInMemory(ExceptionClassifier::create());
echo 'time-window circuit compiled: ', $timeBased->explain()->profileId->value, "\n";
Text
-- 3. Circuit breaker: count window or time window
call 5 refused with circuit_open; the callable ran 4 times
time-window circuit compiled: choosing

تا وقتی مدار بسته است، استثنای خود شما بدون تغییر به شما می‌رسد (catch خالی در کد بالا). وقتی مدار باز شود، Reqtide تماس را با AdmissionRejectedException و دلیل circuit_open رد می‌کند و callable دیگر اجرا نمی‌شود. در مجموع چهار بار اجرا شد. تماس ردشده هیچ‌وقت به callable شما نمی‌رسد و رد شدن آن نمونه (sample) به حساب نمی‌آید.

آرگومان‌ها را به ترتیب بخوانید

امضا این است: circuitBreaker(window, minimumSamples, failureThreshold, openDuration, probeCapacity, probeSuccessThreshold, permitLease).

آرگومانمعنا
پنجره (window)تابع lastCalls(n) آخرین n نتیجهٔ تمام‌شدهٔ غیرخنثی را نگه می‌دارد. تابع timeWindow(window, bucketGranularity) نتیجه‌ها را در بازه‌های کوچک (bucket) جمع می‌کند و در مرز پنجره تقریبی است.
کمینهٔ نمونه (minimumSamples)تعداد نتیجه‌هایی که باید وجود داشته باشد تا مدار اجازهٔ باز شدن پیدا کند. این مقدار نمی‌تواند از اندازهٔ پنجرهٔ شمارشی بیشتر باشد.
آستانهٔ شکست (failureThreshold)سهم شکست‌هایی که مدار با رسیدن به آن باز می‌شود، به شکل یک Ratio. مقدار Ratio::of(1, 2) یعنی نصف.
مدت باز بودن (openDuration)مدتی که مدار باز می‌ماند، پیش از آنکه تماس‌های آزمایشی را بپذیرد.
ظرفیت تماس آزمایشی (probeCapacity)تعداد تماس‌های آزمایشی که وقتی مدار نیمه‌باز است می‌توانند هم‌زمان در جریان باشند.
آستانهٔ موفقیت تماس آزمایشی (probeSuccessThreshold)تعداد تماس‌های آزمایشی موفقی که مدار را می‌بندد. این مقدار نمی‌تواند از ظرفیت تماس آزمایشی بیشتر باشد.
اجارهٔ مجوز (permitLease)مدتی که مجوز یک تماس معتبر می‌ماند (اجاره یا lease). این مقدار باید بیش از 1 ثانیه از مهلت هر تلاش بیشتر باشد.
هفت آرگومان متد circuitBreaker().

بسته، باز، نیمه‌باز

  • بسته (closed). تماس‌ها عبور می‌کنند و نتیجهٔ هر تماس تمام‌شده وارد پنجره می‌شود. وقتی پنجره دست‌کم minimumSamples نتیجه داشته باشد و سهم شکست‌ها به آستانه برسد، مدار باز می‌شود.
  • باز (open). Reqtide تماس‌ها را با circuit_open رد می‌کند. پس از openDuration مدار نیمه‌باز می‌شود.
  • نیمه‌باز (half-open). تا probeCapacity تماس آزمایشی پذیرفته می‌شود. پس از probeSuccessThreshold تماس آزمایشی موفق، مدار بسته می‌شود. یک تماس آزمایشی ناموفق بی‌درنگ آن را دوباره باز می‌کند. تماسی که به‌دلیل نبود ظرفیت تماس آزمایشی رد شود (circuit_probe_capacity_exhausted) نمونه حساب نمی‌شود.

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

چه چیزی شکست حساب می‌شود

تصمیم با طبقه‌بند (classifier) شماست. با ExceptionClassifier این قاعده‌ها برقرار است:

  • هر استثنا شکست مدار حساب می‌شود، مگر اینکه کلاس آن را در ignoreForCircuit() فهرست کرده باشید. خطای اعتبارسنجی خود شما گزینهٔ مناسبی برای این کار است، چون چیزی دربارهٔ وابستگی نمی‌گوید.
  • اگر فهرست countAsCircuitFailure() را بدهید، فقط کلاس‌های فهرست‌شده و کلاس‌های قابل تلاش مجدد شکست حساب می‌شوند. هر استثنای دیگر برای مدار خنثی است.
  • متد unacceptableValuesWhen() مقدار برگشتی را شکست علامت می‌زند، مثلاً پیام خطایی که با وضعیت عادی برگردانده شده است.
  • رد شدن توسط محافظت‌های دیگر، لغوها و خطاهای ذخیره‌سازی خنثی‌اند. نه مدار را باز می‌کنند و نه آن را بهبود می‌دهند.

تماسی که پس از مهلت کل عملیات تمام شود هم خنثی است. فراخوانی‌کننده همان لحظه DeadlineExceeded را گرفته است و نتیجهٔ دیررس چیزی نمی‌گوید که مدار بتواند بر اساس آن عمل کند. برای callable یک تایم‌اوت جداگانه بگذارید که از مهلت کل پایپ‌لاین کوتاه‌تر باشد. آن‌وقت وابستگی کند داخل مهلت شکست می‌خورد و شکست حساب می‌شود. مدار تماس‌های کند را نمی‌شمارد.

استثنایی که کسی فهرستش نکرده، شکست حساب می‌شود. مداری که شکست‌های ناشناخته را نادیده بگیرد، در برابر وابستگی‌ای که به شکلی پیش‌بینی‌نشده خراب می‌شود همچنان بسته می‌ماند.

آزمون بازیابی

آزمون سناریوی «باز شدن، 30 ثانیه انتظار، تماس آزمایشی، بسته شدن» نباید واقعاً 30 ثانیه صبر کند. به‌جای آن ساعت مجازی را جلو ببرید. آزمون با ساعت مجازی را ببینید.