การดู : 126

04/08/2026 04:12น.

ป้องกันระบบล่มด้วย Circuit Breaker Pattern ในภาษา Go ร่วมกับ sonygobreaker

Golang The Series EP.166: Circuit Breaker Pattern วิธีรับมือเมื่อ API ของ AI ล่ม

#Golang

#Circuit Breaker

#Go API

#Cascading Failure

#Backend Development

#AI API

#Go

ยินดีต้อนรับเข้าสู่ EP.166 ครับ! จาก EP.165 เราได้เรียนรู้วิธีการกระจายภาระงานผ่าน Load Balancer ไปยัง AI Nodes หลายๆ เครื่องเพื่อรองรับปริมาณผู้ใช้งานจำนวนมากเรียบร้อยแล้ว แต่ในการทำงานจริงบนสภาพแวดล้อม Production นั้น เครือข่ายและระบบภายนอกเต็มไปด้วยความไม่แน่นอนเสมอ

ลองนึกภาพตามนะครับ หาก API ของผู้ให้บริการ AI เจ้าหลักเกิดล่ม (Outage) หรือเน็ตเวิร์กฝั่ง On-premise เกิดปัญหา Latency พุ่งสูงจากปกติ 2 วินาที กลายเป็น 30 วินาที สิ่งที่เกิดขึ้นตามมาไม่ใช่แค่โมเดล AI ตอบช้าลงเท่านั้น แต่ Request ใหม่ๆ ที่ทยอยยิงเข้ามาจะถูกดึงให้รอนานตามไปด้วย จน Goroutines สะสมค้างเต็มหน่วยความจำ Resources ของแอปพลิเคชัน Go โดนสูบหมด และส่งผลให้ระบบ Backend ทั้งหมดของเรา "พังตามกันไปเป็นโดมิโน" (Cascading Failure)

เพื่อป้องกันไม่ให้ระบบหลักของเราต้องล่มสลายตาม AI ปลายทาง เราจะนำรูปแบบการออกแบบที่เรียกว่า Circuit Breaker Pattern (สวิตช์ตัดไฟอัตโนมัติ) มาใช้ในการกักกันความเสียหายครับ!

ทำความเข้าใจสถานะทั้ง 3 ของ Circuit Breaker

Circuit Breaker ทำหน้าที่เหมือนสวิตช์เบรกเกอร์ตัดไฟในบ้าน เมื่อเกิดไฟฟ้าช็อต เบรกเกอร์จะสับลงทันทีเพื่อป้องกันไม่ให้ไฟไหม้บ้าน โดยมีสถานะการทำงานหลัก 3 สถานะ:

Plaintext

                   ┌─────────────────────────────────────────────────────────┐
                   │                                                         │
                   ▼                                                         │
             ┌──────────┐      (Error Rate สูงเกินเกณฑ์)       ┌──────────┐   │ (ยิงทดสอบสำเร็จ)
             │  CLOSED  │ ───────────────────────────────────> │   OPEN   │   │
             │ (สวิตช์ปิด)│                                     │(สวิตช์เปิด)│   │
             └──────────┘                                      └──────────┘   │
                  ▲                                                  │        │
                  │                                                  │        │
                  │                                                  │ (พักรอ Cooldown)
                  │                                                  ▼        │
                  │                (ยิงทดสอบล้มเหลว)           ┌───────────┐  │
                  └─────────────────────────────────────────── │ HALF-OPEN │ ─┘
                                                               │(กึ่งสับทดสอบ)│
                                                               └───────────┘
  • CLOSED (สถานะปกติ): สวิตช์ปิดอยู่ กระแสไฟฟ้า (Request) ไหลผ่านไปหา AI API ได้ตามปกติ โดยระบบจะคอยนับอัตราส่วนความผิดพลาด (Failure Rate) เก็บไว้ในหน่วยความจำ

  • OPEN (สวิตช์ตัดไฟ/ทำงานผิดปกติ): เมื่ออัตรา Error หรือ Timeout พุ่งสูงเกินเกณฑ์ที่เราตั้งไว้ สวิตช์จะเด้งเปิดทันที! ทุก Request ที่ส่งเข้ามาหลังจากนี้จะถูก "ปฏิเสธทันที (Fail Fast)" โดยไม่ต้องเสียเวลารอยิงไปหา AI API ที่ล่มอยู่

  • HALF-OPEN (กึ่งเปิดกึ่งปิด/ทดสอบระบบ): เมื่อเวลาผ่านไปสักระยะ (Cooldown Period) สวิตช์จะลองปล่อย Request จำนวนเล็กน้อยหลุดไปทดสอบ AI API ดู หากตอบกลับมาได้ปกติ สวิตช์จะกลับไปเป็น CLOSED แต่ถ้ายังล่มอยู่ก็จะดีดกลับไปเป็น OPEN ตามเดิม

การติดตั้ง Dependency ด้วย Sonygobreaker

หนึ่งใน Library สำหรับทำ Circuit Breaker ที่เสถียร ประสิทธิภาพสูง และได้รับความนิยมสูงสุดในวงการ Go คือ sonygobreaker ของ Sony:

Bash

go get github.com/sony/gobreaker

โครงสร้างและการเขียน Go ครอบ API ด้วย Circuit Breaker

เราจะแยกโครงสร้างฟังก์ชันจำลองการเรียก API และส่วนควบคุม Circuit Breaker ออกจากกันเพื่อให้โค้ดอ่านง่ายและเป็นระเบียบ

ส่วนที่ 1: ฟังก์ชันเรียกใช้ API และการจำลองสถานการณ์ขัดข้อง

Go

package main

import (
	"context"
	"errors"
	"fmt"
	"log"
	"time"

	"github.com/sony/gobreaker"
)

// CallExternalAIAPI จำลองฟังก์ชันเรียกใช้ AI API ภายนอก
func CallExternalAIAPI(ctx context.Context, question string, simulateFailure bool) (string, error) {
	if simulateFailure {
		// จำลองว่า API ของ AI ล่มหรือส่ง Timeout กลับมา
		time.Sleep(200 * time.Millisecond)
		return "", errors.New("503 Service Unavailable: AI Model Overloaded")
	}

	// กรณีทำงานปกติ
	return fmt.Sprintf("คำตอบ AI สำหรับคำถาม '%s': ระบบประมวลผลสำเร็จ...", question), nil
}

ส่วนที่ 2: การตั้งค่า Circuit Breaker และส่วนควบคุมหลัก: main.go

Go

func main() {
	// 1. กำหนดการตั้งค่าของ Circuit Breaker
	settings := gobreaker.Settings{
		Name:        "AI-API-Breaker",
		MaxRequests: 2,                // จำนวน Request ที่ยอมให้ผ่านไปทดสอบช่วง Half-Open
		Interval:    5 * time.Second,  // เคลียร์สถิติการนับสัดส่วน Error ทุกๆ 5 วินาที
		Timeout:     3 * time.Second,  // ระยะเวลาพักในสถานะ Open ก่อนจะเปลี่ยนเป็น Half-Open
		ReadyToTrip: func(counts gobreaker.Counts) bool {
			// เงื่อนไขในการตัดไฟ (Trip): ถ้ายิงไปเกิน 3 ครั้ง แล้วมีอัตรา Error เกิน 50% ให้ตัดไฟทันที!
			failureRatio := float64(counts.TotalFailures) / float64(counts.Requests)
			return counts.Requests >= 3 && failureRatio >= 0.5
		},
		OnStateChange: func(name string, from gobreaker.State, to gobreaker.State) {
			// Callback สังเกตการณ์เมื่อมีการเปลี่ยนสถานะของสวิตช์
			log.Printf("⚠️ [Circuit Breaker: %s] เปลี่ยนสถานะจาก %s -> %s\n", name, from, to)
		},
	}

	cb := gobreaker.NewCircuitBreaker(settings)
	question := "กรุณาสรุปรายงานประจำปีให้หน่อย"

	// 2. จำลองการยิง Request 10 ครั้งติดต่อกัน เพื่อดูพฤติกรรมตัดไฟ
	fmt.Println("🚀 เริ่มทดสอบยิง Request เข้าหา AI API...")
	for i := 1; i <= 10; i++ {
		// จำลองให้ API ล่มในช่วง Request ที่ 1 ถึง 5 และให้กลับมาใช้งานได้ในรอบหลัง
		simulateFailure := i <= 5

		// Execute จะเป็นตัวคอยควบคุมว่า Request นี้จะยอมให้หลุดไปยิง API จริงหรือไม่
		result, err := cb.Execute(func() (interface{}, error) {
			return CallExternalAIAPI(context.Background(), question, simulateFailure)
		})

		if err != nil {
			// ตรวจสอบว่า Error เกิดจาก Circuit Breaker สั่งตัดไฟเองหรือไม่
			if errors.Is(err, gobreaker.ErrOpenState) {
				fmt.Printf("🔴 [Req #%d] Circuit Breaker OPEN! (ตัดไฟทันที) -> [Fallback]: 'ขณะนี้ระบบ AI ขัดข้อง กรุณาใช้ระบบค้นหาปกติแทน'\n", i)
			} else {
				fmt.Printf("❌ [Req #%d] API Error: %v -> [Fallback]: 'ขออภัย ไม่สามารถประมวลผลคำตอบได้'\n", i, err)
			}
		} else {
			fmt.Printf("🟢 [Req #%d] Success: %v\n", i, result)
		}

		time.Sleep(800 * time.Millisecond)
	}
}

กลยุทธ์การทำ Fallback Mechanism (กลไกคำตอบสำรอง)

เมื่อ Circuit Breaker สับสวิตช์เป็น OPEN ระบบไม่ควรปล่อยให้ผู้ใช้งานเจอหน้าจอค้าง หรือข้อความ Error ที่อ่านไม่รู้เรื่อง สิ่งที่เราควรทำคือการจัดเตรียม Fallback Path:

  1. Degraded Service (สลับโมเดลสำรอง): สลับไปเรียกใช้งานโมเดลขนาดเล็กกว่าที่รันในเครื่อง Local (เช่น Llama-3-8B) แทนโมเดลหลักตัวใหญ่บน Cloud

  2. Static / Cached Answer: หยิบคำตอบพื้นฐานจาก Redis Cache (ที่เราเรียนใน EP.163) มาตอบแทนทันที

  3. Graceful Degradation Message: แสดงข้อความแจ้งเตือนที่เป็นมิตรพร้อมแนวทางแก้ไข เช่น "ขณะนี้ผู้ช่วย AI มีการใช้งานหนาแน่น ระบบได้บันทึกคำถามของคุณไว้ในคิวเรียบร้อยแล้ว"

🎯 ท้าให้ลอง (Daily Mission)

ลองนำโค้ดตัวอย่างด้านบนไปรัน แล้วสังเกตเอาต์พุตบนหน้าจอ Console ดูการเปลี่ยนสถานะจาก CLOSED ไป OPEN และตัดไฟทันทีในสภาวะที่ API เกิดปัญหา

การบ้านชวนคิด: หากในองค์กรของคุณมีการใช้งาน AI API 2 เจ้าหลัก คือ OpenAI (Primary) และ Gemini (Secondary) คุณจะเขียนฟังก์ชัน Fallback ใน Go อย่างไร ให้เมื่อ Circuit Breaker ของ OpenAI สับสวิตช์ตัดไฟ (OPEN) ระบบจะสลับไปยิงร้องขอคำตอบจาก Gemini โดยอัตโนมัติทันที? ลองดีไซน์โครงสร้างฟังก์ชันชิ้นนี้กันดูนะครับ!

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

ถ้า AI API ปลายทางกลับมาใช้งานได้ปกติแล้ว Circuit Breaker จะรู้ได้อย่างไร?

เมื่อถึงกำหนดเวลาตามค่า Timeout ในสถานะ OPEN สวิตช์จะเปลี่ยนเป็นสถานะ HALF-OPEN โดยอัตโนมัติ เพื่อทดลองปล่อย Request จำนวนจำกัด (กำหนดด้วย MaxRequests) ให้ยิงผ่านไปหา AI API หากยิงสำเร็จระบบจะสลับกลับมาเป็น CLOSED (ปกติ) ทันที แต่ถ้ายังล่มอยู่ก็จะนับเวลาพักใหม่ครับ

เราควรใช้ Timeout และ Error Rate เท่าไหร่ถึงจะเหมาะสมที่สุด?

ไม่มีตัวเลขตายตัว ขึ้นอยู่กับ SLA ของ AI Provider และพฤติกรรมผู้ใช้งาน โดยทั่วไปแนะนำให้ตั้ง Timeout อยู่ที่ช่วง 3-5 วินาที (เพื่อให้ผู้ใช้ไม่ต้องรอนานเกินไป) และตั้ง ReadyToTrip เมื่อมีอัตรา Error เกิน 50% จากการยิงทดสอบอย่างน้อย 3-5 Request ขึ้นไป เพื่อป้องกันไม่ให้สวิตช์ตัดไฟเร็วเกินไปจากความผิดพลาดชั่วคราว (Transient Fault)

Circuit Breaker แตกต่างจากการทำ Rate Limiter อย่างไร?

Rate Limiter ทำหน้าที่ควบคุมปริมาณ Request ฝั่งขาเข้า (Inbound) เพื่อป้องกันระบบเราโดนถล่มหรือจำกัดโควตาการใช้งาน แต่ Circuit Breaker ทำหน้าที่ปกป้องระบบเราจากฝั่งขาออก (Outbound) เมื่อต้องเรียกใช้บริการภายนอกที่กำลังล่มหรือตอบสนองช้า เพื่อตัดวงจรไม่ให้เกิดปัญหาล่มสลายแบบลูกโซ่ (Cascading Failure) ครับ


สรุป

บทความนี้เราได้เรียนรู้หลักการทำงานของ Circuit Breaker Pattern เครื่องมือสำคัญในการป้องกันปัญหาระบบล่มสลายแบบลูกโซ่ (Cascading Failure) เมื่อ API ของ AI ภายนอกเกิดอาการขัดข้องหรือตอบสนองช้าจนเกินไป โดยเราได้ทำความเข้าใจทั้ง 3 สถานะหลัก ได้แก่ CLOSED, OPEN และ HALF-OPEN พร้อมทั้งทดลองเขียนโค้ดภาษา Go จริงร่วมกับ Library gobreaker เพื่อควบคุมการเรียกใช้งาน และออกแบบกลไก Fallback Mechanism สำรอง เพื่อให้ระบบ Backend ของเรายังคงเสถียรและมอบประสบการณ์ที่ดีให้กับผู้ใช้งานได้แม้ในยามที่บริการภายนอกมีปัญหาครับ

ในตอนต่อไป (EP.167): ระบบของเรามีทั้ง Multi-LLM, Cache, Rate Limiter, Load Balancer และ Circuit Breaker ครบครันแล้ว แต่คำถามคือ... เราจะรู้ได้อย่างไรว่าระบบทั้งหมดทำงานได้ดีแค่ไหน? Latency ของ AI แต่ละเจ้าตอบไวเท่าไหร่? ตอนหน้าเราจะมาติดตั้งระบบวัดชีพจรของระบบด้วยเรื่อง "Monitoring AI Performance ใช้ Prometheus วัดความเร็วและประเมิน Latency ในการตอบสนอง" ห้ามพลาดครับ Gophers!

ฝากกดติดตามพวกเราได้ที่ Superdev Academy ในทุกช่องทางนะครับ!

  • 🔵 Facebook: Superdev Academy Thailand (อัปเดตข่าวสารและบทความใหม่)

  • 🎬 YouTube: Superdev Academy Channel (ติวเข้มแบบวิดีโอ)

  • 📸 Instagram: @superdevacademy (เกร็ดความรู้สั้นๆ และเบื้องหลังการทำงาน)

  • 🎬 TikTok: @superdevacademy (Tips & Tricks ฉบับย่อยง่าย)

  • 🌐 Website: superdevacademy.com (คลังบทความและคอร์สเรียนฉบับเต็ม)