03/08/2026 05:00น.

Golang The Series EP.165: Load Balancing AI Servers - การกระจายงานเมื่อมีผู้ใช้ AI พร้อมกันจำนวนมาก
#Golang Load Balancer
#AI Load Balancing
#AI Infrastructure
#Reverse Proxy Go
#Ollama Scaling
#Health Check Go
#Local LLM Infrastructure
ยินดีต้อนรับเข้าสู่ EP.165 ครับ! ในตอนที่แล้วเราได้ทำระบบ Rate Limiting เพื่อสกัดกั้นการสแปมและควบคุมการยิง API ไม่ให้เกินขีดจำกัดไปแล้ว แต่ในความเป็นจริง เมื่อระบบ AI ขององค์กรได้รับความนิยมเพิ่มขึ้นเรื่อยๆ จำนวนผู้ใช้งานที่ได้รับสิทธิ์ถูกต้องจะหลั่งไหลเข้ามาอย่างมหาศาล จนเซิร์ฟเวอร์เพียงเครื่องเดียว (Single Instance) ไม่สามารถรับน้ำหนักไหวอีกต่อไป
โดยเฉพาะอย่างยิ่งหากองค์กรของคุณมีการรัน Local LLM Infrastructure เอง (เช่น การรันโมเดลด้วย Ollama, vLLM หรือ TGI บน GPU Node หลายๆ เครื่อง) แต่ละ Request ที่ส่งเข้ามาประมวลผลคำถามยาวๆ หรือทำ Document Ingestion จะกินทรัพยากร GPU และ VRAM สูงมาก หากเราโยนงานทั้งหมดไปที่เครื่องเดียว เครื่องนั้นจะเกิดอาการ Latency พุ่งสูง ค้าง หรือ Crash ลงมาทันที
ทางออกคือการทำ Load Balancing (การกระจายภาระงาน) โดยใช้ Go สร้างตัวกลางในการจัดสรร Request ส่งต่อไปยัง AI Server Worker แต่ละเครื่องได้อย่างเหมาะสมและมีเสถียรภาพครับ!
อัลกอริทึมในการกระจายงาน (Load Balancing Algorithms for AI)
ในการกระจายงานไปยัง AI Inference Nodes มีกลยุทธ์ยอดนิยม 3 แบบหลักๆ ได้แก่:
Round Robin (วนรอบเท่ากัน): แจกงานเรียงตามลำดับ 1, 2, 3 แล้ววนกลับมา 1 ใหม่ เหมาะสำหรับเครื่องที่มีสเปกฮาร์ดแวร์เท่ากัน และงานมีขนาดใกล้เคียงกัน
Least Connections (ส่งให้เครื่องที่งานน้อยสุด): ตรวจสอบว่า Node ไหนกำลังประมวลผล Request ค้างอยู่น้อยที่สุด แล้วส่งงานใหม่ไปที่นั่น เหมาะกับงาน AI มากที่สุด เพราะแต่ละคำถามใช้เวลาประมวลผล Token ไม่เท่ากัน
Weighted Round Robin (กระจายตามกำลังเครื่อง): เครื่องไหนติด GPU สเปกสูงกว่า ก็กำหนดค่า Weight ให้รับสัดส่วนงานได้มากกว่า
สถาปัตยกรรมระบบ Load Balancer สำหรับ AI Nodes
เราจะสร้าง Reverse Proxy ขนาดเล็กในภาษา Go ที่คอยตรวจเช็กความพร้อม (Health Check) ของ AI Nodesแต่ละตัว และกระจาย Request เข้าไปด้วยกลยุทธ์ Round Robin with Health Check เพื่อให้มั่นใจว่า Request จะไม่ถูกส่งไปหา Node ที่ล่มหรือดับอยู่ครับ
Plaintext
[Client Requests]
│
▼
┌──────────────────────────┐
│ Go Load Balancer │
│ (Round Robin + Proxy) │
└─────────────┬────────────┘
│
┌──────────────────────────┼──────────────────────────┐
│ (Active) │ (Active) │ (Down ❌)
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ AI Node 1 │ │ AI Node 2 │ │ AI Node 3 │
│ (GPU Node A) │ │ (GPU Node B) │ │ (GPU Node C) │
└──────────────┘ └──────────────┘ └──────────────┘
โครงสร้างข้อมูลและระบบกระจายงาน (Components & Proxy Logic)
เพื่อรักษาระเบียบของ Clean Architecture เราจะแยกส่วนโครงสร้างโหนด และฟังก์ชันจัดการ Reverse Proxy ออกจากส่วนการทำงานหลัก
ส่วนที่ 1: โครงสร้างข้อมูล Node และระบบ Health Checker
Go
package main
import (
"fmt"
"log"
"net/http"
"net/http/httputil"
"net/url"
"sync"
"sync/atomic"
"time"
)
// AINode เก็บรายละเอียดสถานะของ AI Server แต่ละเครื่อง
type AINode struct {
URL *url.URL
Alive bool
ReverseProxy *httputil.ReverseProxy
mu sync.RWMutex
}
// SetAlive อัปเดตสถานะความพร้อมของ Node
func (node *AINode) SetAlive(alive bool) {
node.mu.Lock()
node.Alive = alive
node.mu.Unlock()
}
// IsAlive ตรวจสอบว่า Node ยังทำงานอยู่หรือไม่
func (node *AINode) IsAlive() bool {
node.mu.RLock()
defer node.mu.RUnlock()
return node.Alive
}
// AILoadBalancer ตัวบริหารจัดการกลุ่ม AI Nodes
type AILoadBalancer struct {
nodes []*AINode
current uint64
}
// GetNextNode เลือก Node ถัดไปที่พร้อมใช้งานด้วยกลยุทธ์ Round Robin
func (lb *AILoadBalancer) GetNextNode() *AINode {
nodeCount := len(lb.nodes)
if nodeCount == 0 {
return nil
}
// ใช้ atomic เพิ่มค่าตัวนับอย่างปลอดภัยจากหลาย Goroutine
next := atomic.AddUint64(&lb.current, 1)
// วนหา Node ที่สถานะ Alive เป็น true
for i := 0; i < nodeCount; i++ {
idx := int((next + uint64(i)) % uint64(nodeCount))
if lb.nodes[idx].IsAlive() {
return lb.nodes[idx]
}
}
return nil
}
// HealthCheck ทำหน้าที่ยิงไปสำรวจสถานะของแต่ละ Node ทุกๆ ระยะเวลาที่กำหนด
func (lb *AILoadBalancer) HealthCheck() {
client := http.Client{
Timeout: 2 * time.Second, // ตั้ง Timeout เพื่อไม่ให้ค้างนาน
}
for _, node := range lb.nodes {
go func(n *AINode) {
resp, err := client.Get(n.URL.String() + "/health")
if err != nil || resp.StatusCode != http.StatusOK {
if n.IsAlive() {
log.Printf("⚠️ [Health Check] AI Node %s ปิดการตอบสนอง -> ปรับสถานะเป็น DOWN", n.URL.String())
n.SetAlive(false)
}
return
}
_ = resp.Body.Close() // ปิด Connection Body เพื่อป้องกัน Resource Leak
if !n.IsAlive() {
log.Printf("✅ [Health Check] AI Node %s กลับมาทำงานปกติ -> ปรับสถานะเป็น UP", n.URL.String())
n.SetAlive(true)
}
}(node)
}
}
ส่วนที่ 2: ส่วนควบคุมระบบหลักและการรับส่ง Proxy: main.go
Go
func main() {
// 1. รายชื่อของ AI Servers ที่เรามีในระบบ (จำลองพอร์ตของ Ollama/vLLM)
rawURLs := []string{
"http://localhost:11434", // AI Node 1 (Ollama Instance A)
"http://localhost:11435", // AI Node 2 (Ollama Instance B)
"http://localhost:11436", // AI Node 3 (Ollama Instance C)
}
var nodes []*AINode
for _, rawURL := range rawURLs {
targetURL, err := url.Parse(rawURL)
if err != nil {
log.Fatalf("URL ไม่ถูกต้อง: %v", err)
}
proxy := httputil.NewSingleHostReverseProxy(targetURL)
nodes = append(nodes, &AINode{
URL: targetURL,
Alive: true, // กำหนดค่าเริ่มต้นให้พร้อมใช้งาน
ReverseProxy: proxy,
})
}
lb := &AILoadBalancer{nodes: nodes}
// 2. เริ่มต้น Background Health Checker ให้สแกนทุกๆ 10 วินาที
go func() {
ticker := time.NewTicker(10 * time.Second)
for range ticker.C {
lb.HealthCheck()
}
}()
// 3. HTTP Handler ทำหน้าที่เป็น Reverse Proxy กระจาย Request
http.HandleFunc("/api/ai/generate", func(w http.ResponseWriter, r *http.Request) {
targetNode := lb.GetNextNode()
if targetNode == nil {
http.Error(w, "❌ ไม่มี AI Server เครื่องใดพร้อมใช้งานในขณะนี้", http.StatusServiceUnavailable)
return
}
fmt.Printf("🔀 [Load Balancer] กระจายงานไปที่ -> %s\n", targetNode.URL.String())
targetNode.ReverseProxy.ServeHTTP(w, r)
})
log.Println("🚀 AI Load Balancer รันทำงานอยู่ที่พอร์ต :8080...")
log.Fatal(http.ListenAndServe(":8080", nil))
}
ข้อควรระวังในการกระจายงาน AI (Streaming & State Management)
Server-Sent Events (SSE) / Streaming Response: โมเดล AI ยุคใหม่นิยมส่งคำตอบกลับแบบคายออกมาทีละคำ (Token Streaming) ผ่าน SSE ตัว Reverse Proxy ของ Go ต้องระวังเรื่องการตั้งค่า Flush Interval เพื่อให้ Chunk ข้อมูลไหลผ่านไปยัง Client ได้แบบ Real-time โดยไม่โดนค้างใน Memory Buffer
Stateless Node Design: พยายามออกแบบ AI Worker ให้เป็น Stateless มากที่สุด หากต้องการทำ Chat History หรือ Conversation Memory ควรแยกประวัติการสนทนาไปเก็บไว้ที่ Centralized Database (เช่น Redis หรือ PostgreSQL) เพื่อให้ Request ถัดไปของผู้ใช้คนเดิมวิ่งไปลง AI Node เครื่องไหนก็ได้โดยไม่สูญเสียบริบทคำตอบ
🎯 ท้าให้ลอง (Daily Mission)
ลองนำโค้ดด้านบนไปรัน แล้วทดสอบสถานการณ์ที่เกิดขึ้นจริงในระดับ Production:
การบ้านชวนคิด: หากในระบบของคุณ มี AI Node 1 เครื่องที่ใช้ GPU ประมวลผลเร็วมาก (เช่น NVIDIA H100) และอีก 2 เครื่องใช้ GPU สเปกทั่วไป (เช่น RTX 4090) การใช้อัลกอริทึม Round Robin แบบธรรมดาอาจทำให้เครื่องที่ช้าเกิดคิวสะสม ในขณะที่เครื่องแรงนั่งว่าง
ในฐานะวิศวกรระบบ คุณจะปรับแต่งโครงสร้างฟังก์ชัน GetNextNode() และฟิลด์ใน AINode อย่างไรให้รองรับแนวคิด Weighted Round Robin เพื่อกระจายสัดส่วนงานตามศักยภาพฮาร์ดแวร์อย่างเหมาะสม? ลองดีไซน์โค้ดส่วนนี้กันดูนะครับ!
❓ คำถามที่พบบ่อย (FAQ)
ทำไมเราถึงต้องทำ Load Balancer เองด้วยภาษา Go แทนที่จะใช้ Nginx หรือ HAProxy?
ในระบบ AI Infrastructure ทั่วไป Nginx หรือ HAProxy ก็สามารถทำ Reverse Proxy และ Load Balancing ได้ดีเยี่ยมครับ แต่การเขียน Load Balancer ด้วยภาษา Go เองจะช่วยให้เราสามารถปรับแต่งตรรกะเชิงลึก (Custom Logic) ให้เข้ากับการทำงานของ AI ได้ง่ายกว่า เช่น การจัดการสตรีมมิ่ง Token ของ LLM, การเขียนอัลกอริทึม Custom Health Check เฉพาะทาง, หรือการเพิ่มระบบ Rate Limiting/Authentication รวมไว้ที่จุดเดียวโดยไม่ต้องพึ่งพาโมดูลเสริมซับซ้อน
ถ้า AI Node เครื่องหนึ่งเกิดค้าง (Hang) แต่ไม่ถึงกับดับ (Crash) ระบบ Health Check จะรู้หรือไม่?
ในโค้ดตัวอย่างเบื้องต้น เราใช้การยิง HTTP GET ไปที่พอร์ต /health ด้วย Timeout สั้นๆ (2 วินาที) ถ้าเครื่องนั้นตอบกลับช้าเกินกำหนด (เนื่องจากติดคิวประมวลผลคำถามยาวๆ) ระบบจะมองว่าตัวเครื่องเริ่มมีปัญหาและสลับสถานะเป็น DOWN ชั่วคราว ทั้งนี้ ในระบบ Production จริง เราอาจจะต้องปรับเกณฑ์ Health Check ให้ละเอียดขึ้น เช่น ตรวจสอบอัตราการใช้หน่วยความจำ GPU (VRAM) หรือจำนวน Active Connections ปัจจุบันร่วมด้วย
การทำ Load Balancing แบบ Round Robin มีข้อเสียอย่างไรกับงาน AI?
ข้อเสียหลักคือ "ความไม่เท่าเทียมของระยะเวลาประมวลผล" ครับ เนื่องจากคำถาม (Prompt) แต่ละอันที่ส่งมาหา AI มีความยาวและใช้เวลาประมวลผล Token ไม่เท่ากัน หาก Request ยาวๆ ไปกองที่ Node A หมดในขณะที่ Node B ว่างงาน Round Robin แบบดั้งเดิมก็จะยังคงสลับส่งงานมาที่ Node B ตามรอบ ทำให้เกิดสภาวะโหลดไม่สมดุล (Imbalance) จึงเป็นที่มาของการแนะนำให้ใช้กลยุทธ์แบบ Least Connections หรือ Weighted Round Robin ในระดับถัดไป
ถ้าต้องการเก็บประวัติการสนทนา (Chat History) จะมีผลอะไรกับ Load Balancer หรือไม่?
มีผลแน่นอนครับ หาก AI Node ถูกออกแบบให้เก็บสถานะความจำ (Memory State) ไว้ภายในเครื่องนั้นๆ เมื่อผู้ใช้ส่งข้อความต่อเนื่อง (Follow-up) แล้ว Load Balancer ดันกระจาย Request ไปลงเครื่องอื่น ประวัติการสนทนาจะหายทันที วิธีแก้คือต้องออกแบบให้ AI Worker เป็น Stateless และแยกส่วนการเก็บ Context หรือ Chat History ไปไว้ที่ Centralized Cache (เช่น Redis) เพื่อให้ผู้ใช้สามารถยิงคำขอไปที่ Node ไหนก็ได้โดยที่บริบทการสนทนายังคงอยู่ครบถ้วนครับ
สรุป
บทความนี้เราได้เรียนรู้เรื่องการรับมือกับปัญหาขวดคอขวด (Bottleneck) เมื่อระบบ AI ขององค์กรมีผู้ใช้งานพร้อมกันจำนวนมาก ด้วยการสร้าง Load Balancer ในภาษา Go ร่วมกับ Reverse Proxy เพื่อกระจาย Request ไปยัง AI Nodes ต่างๆ อย่างเท่าเทียม นอกจากนี้เรายังได้ทำความรู้จักกับระบบ Background Health Check ที่ช่วยคัดกรองและตรวจสอบสถานะความพร้อมของเซิร์ฟเวอร์แบบ Real-time เพื่อป้องกันไม่ให้ระบบส่งงานไปหาโหนดที่ล่มไปแล้ว ช่วยเพิ่มความเสถียรและความพร้อมใช้งานให้กับระบบ Enterprise AI Infrastructure ของเราได้อย่างมีประสิทธิภาพครับ
ในตอนต่อไป (EP.166): ต่อให้เราจะมี Load Balancer คอยกระจายงานอย่างดีแค่ไหน แต่ถ้าเกิดเหตุการณ์ที่ API ของ AI เจ้าหลัก หรือเซิร์ฟเวอร์ปลายทางล่มสนิททั้งระบบ การปล่อยให้ Request วิ่งไปชนแล้วเกิด Timeout ช้าๆ จะพาให้ระบบ Backend ทั้งหมดของเราพังพินาศตามไปด้วย (Cascading Failure) ตอนหน้าเราจะมาติดตั้งสวิตช์ตัดไฟอัตโนมัติด้วยเรื่อง "Circuit Breaker Pattern วิธีรับมือเมื่อ API ของ AI เกิดการล่มสลาย" ห้ามพลาดครับ Gophers!
ฝากกดติดตามพวกเราได้ที่ Superdev Academy ในทุกช่องทางนะครับ!
🔵 Facebook: Superdev Academy Thailand (อัปเดตข่าวสารและบทความใหม่)
🎬 YouTube: Superdev Academy Channel (ติวเข้มแบบวิดีโอ)
📸 Instagram: @superdevacademy (เกร็ดความรู้สั้นๆ และเบื้องหลังการทำงาน)
🎬 TikTok: @superdevacademy (Tips & Tricks ฉบับย่อยง่าย)
🌐 Website: superdevacademy.com (คลังบทความและคอร์สเรียนฉบับเต็ม)