0Pricing
PHP Academy · درس

المرونة: قواطع الدائرة وإعادة المحاولة

حافظ على سلامة الأنظمة عند حدوث أعطال جزئية

المرونة: قواطع الدائرة وإعادة المحاولة درس مجاني في PHP Academy على CoddyKit. هذا هو الدرس 4 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في PHP Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة PHP Academy 4 دروس في المجموع.

أنماط المرونة

في النظام الموزع، تكون الأعطال الجزئية هي الحالة الطبيعية — فهناك دائمًا تبعية ما بطيئة أو قيد إعادة التشغيل أو محمّلة فوق طاقتها. تتعلق المرونة بتصميم كل خدمة بحيث لا تتسبب تبعية معطلة في إسقاط خدمتكم (ثم النظام بأكمله) معها.

يغطي هذا الدرس مجموعة الأدوات الأساسية: المهلات الزمنية، وإعادة المحاولة مع التراجع، وقواطع الدائرة، والحواجز، والتدهور السلس — وكل ذلك باستخدام PHP.

المهلات أولًا

أهم إعداد للمرونة هو المهلة الزمنية. من دونها، تؤدي خدمة downstream بطيئة إلى إبقاء عمال PHP-FPM منتظرين؛ فتتراكم الطلبات؛ وينفد العمال؛ وتتوقف خدمتكم لأن شخصًا آخر كان بطيئًا. هذا هو الفشل المتسلسل النموذجي.

اضبطوا مهلة اتصال ومهلة إجمالية للطلب في كل استدعاء صادر. دائمًا.

<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;

$http = new Client([
    'connect_timeout' => 0.5, // fail fast if we can't even connect
    'timeout'         => 2.0, // hard cap on the whole call
]);
// A hung dependency now fails in 2s instead of holding a worker forever.

إعادات المحاولة — بحذر

تنجح الأعطال المؤقتة (انقطاع وجيز، أو فقدان قصير للشبكة، أو 503) غالبًا عند المحاولة الثانية. لكن إعادات المحاولة خطرة: إذا أعدتم المحاولة بسرعة مفرطة فإنكم تضاعفون الحمل على خدمة تعاني أصلًا. القواعد:

  • لا تعيدوا المحاولة إلا للعمليات idempotent والأخطاء القابلة لإعادة المحاولة (المهلات، و5xx، و429).
  • حدّدوا عدد المحاولات الأقصى.
  • لا تعيدوا محاولة أخطاء العميل 4xx أبدًا — إذ ستفشل مجددًا فحسب.

التراجع الأسي + العشوائية

تتزامن إعادات المحاولة ذات الفواصل الزمنية الثابتة من عملاء كثيرين لتشكّل تدافعًا جماعيًا يضرب الخدمة المتعافية على دفعات. والحل هو التراجع الأسي (مضاعفة التأخير مع كل محاولة) مع العشوائية لتوزيع الحمل.

<?php
function backoffDelay(int $attempt, float $base = 0.1, float $cap = 5.0): float {
    $exp = min($cap, $base * (2 ** $attempt));   // 0.1, 0.2, 0.4, ...
    return mt_rand(0, (int)($exp * 1000)) / 1000; // full jitter: 0..exp
}
for ($i = 0; $i < 5; $i++) {
    printf("attempt %d -> wait %.3fs\n", $i, backoffDelay($i));
}

حلقة إعادة المحاولة

جمع هذه الأجزاء معًا: محاولات محدودة، وإعادة المحاولة فقط عند الأخطاء القابلة لإعادة المحاولة، والانتظار مع تراجع عشوائي بين المحاولات، وإظهار آخر فشل إذا فشلت جميع المحاولات.

<?php
function withRetry(callable $op, int $maxAttempts = 4): mixed {
    $attempt = 0;
    while (true) {
        try {
            return $op();
        } catch (\Throwable $e) {
            $attempt++;
            if ($attempt >= $maxAttempts || !isRetryable($e)) {
                throw $e; // give up
            }
            usleep((int)(backoffDelay($attempt) * 1_000_000));
        }
    }
}
function isRetryable(\Throwable $e): bool { return $e->getCode() === 0 || $e->getCode() >= 500; }
function backoffDelay(int $a): float { return min(5.0, 0.1 * (2 ** $a)) * (mt_rand(0, 100) / 100); }

قاطع الدائرة

تساعد إعادات المحاولة في التعامل مع الانقطاعات الوجيزة، ولكن إذا كانت تبعية ما متوقفة فعلًا، فإن إعادة محاولة كل طلب تهدر الوقت والموارد. يتتبع قاطع الدائرة حالات الفشل، وبمجرد تجاوزها حدًا معينًا، يفتح — فيتجاوز الاستدعاءات ويفشل فورًا بدلًا من انتظار خدمة متوقفة.

له ثلاث حالات: مغلق (تمر الاستدعاءات ويُحصى الفشل)، ومفتوح (تُرفض الاستدعاءات فورًا)، ونصف مفتوح (تختبر بعض الاستدعاءات التجريبية التعافي).

آلة حالات قاطع الدائرة

الانتقالات: من مغلق إلى مفتوح عندما يتجاوز الفشل الحد المسموح. ومن مفتوح إلى نصف مفتوح بعد فترة تهدئة. ومن نصف مفتوح إلى مغلق عند نجاح الاختبار، أو عودة إلى مفتوح عند الفشل. يجب مشاركة الحالة بين عمليات PHP (Redis/APCu)، لأن كل طلب يعمل في عملية جديدة.

<?php
final class CircuitBreaker {
    public function __construct(
        private int $threshold = 5,
        private int $coolDown = 30, // seconds
    ) {}

    public function call(callable $op, array &$state): mixed {
        if ($state['status'] === 'open') {
            if (time() - $state['openedAt'] < $this->coolDown) {
                throw new \RuntimeException('Circuit open - failing fast');
            }
            $state['status'] = 'half-open'; // time to probe
        }
        try {
            $result = $op();
            $state = ['status' => 'closed', 'failures' => 0]; // recovered
            return $result;
        } catch (\Throwable $e) {
            $state['failures'] = ($state['failures'] ?? 0) + 1;
            if ($state['failures'] >= $this->threshold || $state['status'] === 'half-open') {
                $state['status'] = 'open';
                $state['openedAt'] = time();
            }
            throw $e;
        }
    }
}

الحواجز

سُمّي نمط الحاجز على اسم حجرات السفن، وهو يعزل الموارد بحيث لا يؤدي الفشل في منطقة واحدة إلى إغراق السفينة بأكملها. إذا كان بإمكان جميع عمالكم استدعاء خدمة Reports البطيئة، فقد يؤدي تعطل Reports إلى استهلاك كل عامل وحرمان Checkout من الموارد.

قسّموا الموارد — إلى مجموعات اتصال منفصلة، ومجموعات عمال أو صفوف انتظار منفصلة لكل تبعية، أو حدود تزامن لكل خدمة downstream — بحيث تستنفد التبعية الفاشلة شريحتها الخاصة فقط.

التدهور السلس والبدائل الاحتياطية

عندما تكون تبعية غير مهمة غير متاحة، استخدموا التدهور بدلًا من إرجاع خطأ. قدّموا قيمة مخزنة مؤقتًا، أو قيمة افتراضية، أو تجربة أقل اكتمالًا. وتُعد حالة الفتح في قاطع الدائرة المشغّل الطبيعي لمسار البديل.

<?php
function getRecommendations(callable $remoteCall, Redis $cache, string $userId): array {
    try {
        $recs = $remoteCall($userId);
        $cache->setex("recs:$userId", 3600, json_encode($recs));
        return $recs;
    } catch (\Throwable $e) {
        // Fallback 1: last-known-good from cache
        if ($cached = $cache->get("recs:$userId")) {
            return json_decode($cached, true);
        }
        // Fallback 2: generic popular items - never block the page
        return ['popular-1', 'popular-2'];
    }
}

مفاتيح Idempotency لإعادة المحاولة الآمنة

إعادات المحاولة آمنة فقط للعمليات idempotent. وفي إجراء غير idempotent مثل «تحصيل المبلغ من البطاقة»، أرفقوا مفتاح idempotency يستخدمه الخادم لإزالة التكرار: إذا وصلت إعادة المحاولة بعد نجاح المحاولة الأولى (لكن فقدان الاستجابة)، يعيد الخادم النتيجة الأصلية بدلًا من تحصيل المبلغ مرتين.

<?php
function chargeWithRetry(callable $http, string $orderId, int $cents): array {
    // Same key across all retries of THIS logical charge
    $key = 'charge-' . $orderId;
    return withRetry(fn() => $http('POST', '/charges', [
        'headers' => ['Idempotency-Key' => $key],
        'json'    => ['order' => $orderId, 'amount' => $cents],
    ]));
}
function withRetry(callable $op) { return $op(); } // see earlier scene

تركيب الأنماط معًا

تتكامل الأنماط، والترتيب مهم. عادةً ما يُغلّف الاستدعاء الصادر المتين كما يلي:

  • المهلة الزمنية لكل محاولة منفردة (الأعمق).
  • إعادة المحاولة مع التراجع لتغليف الاستدعاء ذي المهلة للتعامل مع الانقطاعات المؤقتة.
  • قاطع الدائرة لتغليف إعادة المحاولة، بحيث يؤدي العطل المستمر إلى فتحه بسرعة.
  • الحاجز للحد من مقدار السعة التي يمكن لهذه التبعية استهلاكها.
  • البديل في الطبقة الخارجية، لالتقاط كل ما يصل إليه.

توجد أغلفة على نمط Resilience4PHP، لكن فهم طبقات التغليف أهم من أي حزمة محددة.

تحقق سريع

اختيار النمط المناسب.

مراجعة

الصمود أمام الأعطال الجزئية:

  • تمنع المهلات الزمنية في كل استدعاء استنفاد العمال والفشل المتسلسل.
  • استخدموا إعادات المحاولة فقط للعمليات idempotent والأخطاء القابلة لإعادة المحاولة، مع التراجع الأسي + العشوائية.
  • تؤدي قواطع الدائرة إلى الفشل السريع (مغلق ← مفتوح ← نصف مفتوح) عند تعطل تبعية.
  • تعزل الحواجز الموارد بحيث لا يؤدي فشل واحد إلى حرمان كل شيء من الموارد.
  • تحافظ آليات التدهور السلس والبدائل الاحتياطية على استمرار التدفقات الأساسية.

وعند تركيبها معًا، تحوّل هذه الأنماط الأعطال الحتمية إلى أحداث محصورة قابلة للتعافي.

الأسئلة الشائعة

هل درس «المرونة: قواطع الدائرة وإعادة المحاولة» مجاني؟

نعم — نص درس «المرونة: قواطع الدائرة وإعادة المحاولة» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة PHP Academy، انتقل إلى CoddyKit PRO. تتضمن دورة PHP Academy 4 دروس في المجموع.

ماذا ستتعلم في «المرونة: قواطع الدائرة وإعادة المحاولة»؟

حافظ على سلامة الأنظمة عند حدوث أعطال جزئية تتمرن على PHP Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ PHP Academy؟

لا تُشترط خبرة سابقة. PHP Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 4 من أصل 4.

كم من الوقت يستغرق درس «المرونة: قواطع الدائرة وإعادة المحاولة»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس PHP Academy هذا؟

نعم. كل درس في PHP Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. من التطبيق الأحادي إلى الخدمات المصغّرة
  2. تواصل الخدمات: REST وgRPC
  3. بوابات API واكتشاف الخدمات
  4. المرونة: قواطع الدائرة وإعادة المحاولة
← العودة إلى PHP Academy