ความทนทาน: ตัวตัดวงจรและการลองใหม่
รักษาระบบให้ทำงานได้ดีเมื่อเกิดความล้มเหลวบางส่วน
ความทนทาน: ตัวตัดวงจรและการลองใหม่ เป็นบทเรียน 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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- จากโมโนลิทสู่ไมโครเซอร์วิส
- การสื่อสารระหว่างบริการ: REST และ gRPC
- เกตเวย์ API และการค้นพบบริการ
- ความทนทาน: ตัวตัดวงจรและการลองใหม่