0Pricing
PHP Academy · บทเรียน

ความทนทาน: ตัวตัดวงจรและการลองใหม่

รักษาระบบให้ทำงานได้ดีเมื่อเกิดความล้มเหลวบางส่วน

ความทนทาน: ตัวตัดวงจรและการลองใหม่ เป็นบทเรียน PHP Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน PHP Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส PHP Academy มีบทเรียนทั้งหมด 4 บทเรียน

รูปแบบการเพิ่มความทนทาน

ในระบบแบบกระจาย ความล้มเหลวเพียงบางส่วนคือสภาวะปกติ เพราะการพึ่งพาบางอย่างจะช้า กำลังเริ่มต้นใหม่ หรือมีภาระงานเกินอยู่เสมอ ความทนทานคือการออกแบบแต่ละบริการให้การพึ่งพาที่มีปัญหาเพียงรายการเดียวไม่ทำให้บริการของคุณ (และต่อมาคือทั้งระบบ) ล่มตามไปด้วย

บทเรียนนี้ครอบคลุมชุดเครื่องมือหลัก ได้แก่ การหมดเวลา การลองใหม่พร้อมการหน่วงเวลา ตัวตัดวงจร การแบ่งส่วนทรัพยากร และการลดความสามารถอย่างเหมาะสม ทั้งหมดนี้ทำด้วย PHP

เริ่มจากการกำหนดเวลาหมด

การตั้งค่าด้านความทนทานที่สำคัญที่สุดเพียงอย่างเดียวคือ การหมดเวลา หากไม่มีสิ่งนี้ บริการปลายทางที่ช้าจะทำให้เวิร์กเกอร์ 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) มักสำเร็จเมื่อลองอีกครั้ง แต่การลองใหม่ก็อันตราย: หากลองใหม่เร็วเกินไป คุณจะเพิ่มภาระโหลดให้บริการที่กำลังมีปัญหาอยู่แล้ว กฎมีดังนี้:

  • ลองใหม่เฉพาะการดำเนินการที่ให้ผลเหมือนเดิมเมื่อทำซ้ำ และข้อผิดพลาดที่retryable (หมดเวลา, 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));
}

วนรอบการลองใหม่

เมื่อนำส่วนต่าง ๆ มารวมกัน: จำกัดจำนวนครั้ง ลองใหม่เฉพาะเมื่อเกิดข้อผิดพลาดที่ retryable หน่วงเวลาด้วยการหน่วงแบบสุ่มระหว่างการลองแต่ละครั้ง และส่งต่อความล้มเหลวครั้งล่าสุดหากทุกครั้งล้มเหลว

<?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;
        }
    }
}

การแบ่งส่วนทรัพยากร

รูปแบบการแบ่งส่วนทรัพยากรตั้งชื่อตามห้องกั้นในเรือ โดยแยกทรัพยากรออกจากกันเพื่อไม่ให้ความล้มเหลวในพื้นที่หนึ่งทำให้เรือทั้งลำจม หากเวิร์กเกอร์ทั้งหมดของคุณเรียกใช้บริการรายงานที่ช้า การหยุดให้บริการของบริการรายงานอาจใช้เวิร์กเกอร์ทุกตัวจนหมดและทำให้การชำระเงินขาดแคลนเวิร์กเกอร์

แบ่งทรัพยากรเป็นส่วน ๆ เช่น กลุ่มการเชื่อมต่อแยกกัน กลุ่มเวิร์กเกอร์หรือคิวแยกตามการพึ่งพา หรือจำกัดการทำงานพร้อมกันของบริการปลายทางแต่ละรายการ เพื่อให้การพึ่งพาที่ล้มเหลวใช้ทรัพยากรจนหมดได้เฉพาะส่วนของตัวเอง

การลดความสามารถอย่างเหมาะสมและทางเลือกสำรอง

เมื่อการพึ่งพาที่ไม่สำคัญไม่พร้อมใช้งาน ให้ลดความสามารถแทนการแสดงข้อผิดพลาด แสดงค่าที่เก็บไว้ในแคช ค่าเริ่มต้น หรือประสบการณ์การใช้งานที่ลดลง สถานะเปิดของตัวตัดวงจรเป็นตัวกระตุ้นตามธรรมชาติให้เข้าสู่เส้นทางสำรอง

<?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'];
    }
}

คีย์ป้องกันการทำซ้ำสำหรับการลองใหม่อย่างปลอดภัย

การลองใหม่จะปลอดภัยเฉพาะกับการดำเนินการที่ให้ผลเหมือนเดิมเมื่อทำซ้ำ สำหรับการกระทำที่ไม่เป็นแบบนั้น เช่น "เรียกเก็บเงินจากบัตร" ให้แนบคีย์ป้องกันการทำซ้ำที่เซิร์ฟเวอร์ใช้กำจัดคำขอซ้ำ: หากการลองใหม่มาถึงหลังจากความพยายามครั้งแรกสำเร็จไปแล้ว (แต่การตอบกลับสูญหาย) เซิร์ฟเวอร์จะส่งคืนผลลัพธ์เดิมแทนการเรียกเก็บเงินสองครั้ง

<?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 อยู่ แต่การเข้าใจการซ้อนชั้นสำคัญกว่าการยึดติดกับแพ็กเกจใดแพ็กเกจหนึ่ง

การเลือกรูปแบบที่เหมาะสม

การเลือกรูปแบบที่เหมาะสม

สรุป

การรับมือความล้มเหลวบางส่วน:

  • การหมดเวลาในทุกการเรียกใช้ป้องกันเวิร์กเกอร์หมดและความล้มเหลวแบบลูกโซ่
  • การลองใหม่เฉพาะการดำเนินการที่ให้ผลเหมือนเดิมเมื่อทำซ้ำและข้อผิดพลาดที่ retryable พร้อมการหน่วงเวลาทวีคูณและการสุ่มเวลา
  • ตัวตัดวงจรล้มเหลวอย่างรวดเร็ว (ปิด → เปิด → กึ่งเปิด) เมื่อการพึ่งพาหยุดทำงาน
  • การแบ่งส่วนทรัพยากรแยกทรัพยากรเพื่อไม่ให้ความล้มเหลวหนึ่งรายการทำให้ทุกอย่างขาดแคลน
  • การลดความสามารถอย่างเหมาะสม/ทางเลือกสำรองช่วยให้กระบวนการหลักยังทำงานได้

เมื่อนำมาซ้อนชั้นกัน รูปแบบเหล่านี้จะเปลี่ยนความล้มเหลวที่หลีกเลี่ยงไม่ได้ให้เป็นเหตุการณ์ที่จำกัดวงและกู้คืนได้

คำถามที่พบบ่อย

บทเรียน “ความทนทาน: ตัวตัดวงจรและการลองใหม่” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “ความทนทาน: ตัวตัดวงจรและการลองใหม่” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส PHP Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส PHP Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “ความทนทาน: ตัวตัดวงจรและการลองใหม่”

รักษาระบบให้ทำงานได้ดีเมื่อเกิดความล้มเหลวบางส่วน คุณปฏิบัติ PHP Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน PHP Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน PHP Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน

บทเรียน “ความทนทาน: ตัวตัดวงจรและการลองใหม่” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน PHP Academy นี้ได้ไหม

ได้ บทเรียน PHP Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. จากโมโนลิทสู่ไมโครเซอร์วิส
  2. การสื่อสารระหว่างบริการ: REST และ gRPC
  3. เกตเวย์ API และการค้นพบบริการ
  4. ความทนทาน: ตัวตัดวงจรและการลองใหม่
← กลับไปที่ PHP Academy