12/04/2026 18:16น.

Golang The Series EP 130: Multi-instance WebSocket – สเกลระบบ Real-time ด้วย Redis
#WebSocket
#Go
#Golang
#Redis Pub/Sub
#Multi-instance
ยินดีต้อนรับเข้าสู่บทความที่เข้มข้นที่สุดตอนหนึ่งในซีรีส์ครับ! หากคุณกำลังสร้างระบบ Chat, Real-time Notification หรือ Dashboard ที่ต้องรองรับผู้ใช้งานพร้อมกันหลักแสน คุณจะพบว่า WebSocket แบบดั้งเดิมที่เก็บสถานะไว้ในหน่วยความจำเครื่องใครเครื่องมัน (Stateful) นั้นคืออุปสรรคสำคัญในการขยายระบบ (Scaling)
วันนี้เราจะมาสร้างสถาปัตยกรรมที่ช่วยให้ WebSocket ของเราขยายตัวออกไปได้ไม่จำกัดแบบ Horizontal Scaling ครับ
1. โจทย์สุดหิน: เมื่อ "กำแพง" กั้นระหว่าง Instance
โดยปกติ HTTP Request จะเป็น Stateless (ขอมา-ตอบไป-จบงาน) แต่ WebSocket ต่างออกไป มันคือการเปิดท่อ TCP ค้างไว้ (Stateful) ลองจินตนาการปัญหาที่เกิดขึ้นเมื่อเรามี Server มากกว่า 1 ตัว:
- นายก้อง เชื่อมต่อเข้า Instance A
- นางสาวแก้ว เชื่อมต่อเข้า Instance B (ผ่าน Load Balancer ตัวเดียวกัน)
- เมื่อก้องส่งข้อความหาแก้ว... Instance A จะมองหา "แก้ว" ใน Memory ของตัวเองไม่เจอ! เพราะแก้วไปเปิดท่อค้างไว้อีกเครื่องหนึ่ง
นี่คือจุดที่ข้อความจะหายไปในหลุมดำ หากเราไม่มี "ตัวกลาง" ที่ทำหน้าที่เชื่อมทุก Instance เข้าด้วยกัน
2. Redis Pub/Sub: กระดูกสันหลังของระบบ Real-time
ทำไมต้องเป็น Redis Pub/Sub? ในการสื่อสารระดับ Microservices เราต้องการความเร็วระดับ Microsecond และ Redis คือคำตอบที่ลงตัวที่สุด:
- Performance: ทำงานบน RAM ทั้งหมด Latency จึงต่ำมาก
- Pub/Sub Pattern: สนับสนุนการกระจายข้อมูลแบบ Broadcast (หนึ่งเครื่องประกาศ ทุกเครื่องที่ติดตามอยู่ได้รับทันที)
- Lightweight: ติดตั้งและดูแลรักษาง่ายกว่า Message Broker ขนาดใหญ่อย่าง Kafka ในโจทย์ที่เน้นความเร็วของการส่งข้อความสั้นๆ
3. สถาปัตยกรรม "The Message Backplane"
เราจะใช้ Redis เป็น "Backplane" (แผงวงจรกลาง) เพื่อส่งต่อข้อมูลระหว่าง Instance ดังนี้:
- Publish: เมื่อ Instance A ได้รับข้อความจาก Client ของตัวเอง มันจะ Publish ข้อความนั้นลงใน Redis Channel (เช่น chat_room_1)
- Subscribe: ทุกๆ Instance ที่รันอยู่จะทำการ Subscribe รอที่ Channel นั้นไว้ตั้งแต่วินาทีแรกที่เริ่มทำงาน
- Dispatch: เมื่อ Redis กระจายข้อความมาให้ ทุก Instance จะได้รับพร้อมกัน และตรวจสอบว่า "คนรับปลายทาง (Target User) อยู่ที่เครื่องฉันไหม?" ถ้าอยู่ ก็จะส่งผ่านท่อ WebSocket ให้ทันที
4. Implementation: โค้ด Go ระดับ Production-Ready
เราจะใช้ gorilla/websocket และ go-redis/v9 (เวอร์ชันล่าสุด) โดยเน้นความปลอดภัยและประสิทธิภาพครับ
A. โครงสร้าง Hub และ Redis Client
Go
type Hub struct {
// เก็บรายการ Client ที่เชื่อมต่อกับเครื่องนี้เท่านั้น (Local Clients)
clients map[*Client]bool
broadcast chan []byte
register chan *Client
unregister chan *Client
// Redis สำหรับสื่อสารข้ามเครื่อง
rdb *redis.Client
}
B. การจัดการ Redis Subscription (The Listener)
เราต้องรัน Goroutine แยกออกมาเพื่อฟังข้อความจาก Redis ตลอดเวลา:
Go
func (h *Hub) listenToRedis(ctx context.Context) {
pubsub := h.rdb.Subscribe(ctx, "global_chat")
defer pubsub.Close()
ch := pubsub.Channel()
for msg := range ch {
// เมื่อได้รับข้อความจาก Redis ให้ส่งเข้าช่องทาง broadcast ของเครื่องตัวเอง
// เพื่อกระจายให้ Client ทุกคนที่เชื่อมต่ออยู่กับเครื่องนี้
h.broadcast <- []byte(msg.Payload)
}
}
C. การรับและส่งข้อความ (Publishing)
เมื่อ Client ส่งข้อความเข้ามา แทนที่จะวน Loop ส่งให้คนอื่นในเครื่องตัวเองตรงๆ เราจะโยนให้ Redis จัดการ:
Go
func (c *Client) readPump() {
defer func() {
c.hub.unregister <- c
c.conn.Close()
}()
for {
_, message, err := c.conn.ReadMessage()
if err != nil {
break
}
// Publish ลง Redis เพื่อให้ทุก Instance (รวมถึงเครื่องตัวเอง) ได้รับข้อมูล
err = c.hub.rdb.Publish(context.Background(), "global_chat", message).Err()
if err != nil {
slog.Error("Redis Publish Failed", "error", err)
}
}
}
5. เทคนิคการจัดการ Scaling ให้มีเสถียรภาพ
- Sticky Sessions: แม้จะมี Redis มาช่วย แต่การตั้งค่า Load Balancer (เช่น Nginx) ให้ใช้ ip_hash ยังคงสำคัญ เพื่อลดการ Re-handshake บ่อยๆ ซึ่งช่วยลดภาระ CPU ของ Server
- JSON Structured Data: ห้ามส่งแค่ Text เปล่าๆ ควรส่งเป็น JSON ที่มี target_id หรือ room_id เพื่อให้แต่ละ Instance กรองข้อมูล (Filtering) ได้รวดเร็วโดยไม่ต้องเสียเวลาประมวลผลมาก
- Graceful Shutdown: (ย้อนกลับไป EP 129) ก่อนปิด Instance ต้องทำการส่งสัญญาณแจ้ง Client ให้ Reconnect และ Unsubscribe จาก Redis ให้เรียบร้อยเพื่อป้องกันอาการ "ข้อความค้าง"
สรุป
การนำ Redis Pub/Sub มาเป็นตัวกลาง เปลี่ยนจาก WebSocket Server เดี่ยวๆ ให้กลายเป็น Distributed Real-time System ที่ทรงพลัง คุณสามารถเพิ่ม Instance (Scale Out) ได้ไม่จำกัดเพื่อรองรับผู้ใช้งานที่เพิ่มขึ้น โดยที่พวกเขายังสามารถสื่อสารกันได้ลื่นไหลเหมือนอยู่ในเครื่องเดียวกัน
"Stateful doesn't have to mean Unscalable." การจัดการ State ที่ดี คือหัวใจของระบบระดับโลกครับ
ในตอนหน้า (EP 131): เราจะยกระดับความซับซ้อนไปอีกขั้น กับการนำ WebSocket เข้าสู่โลกของ Microservices Architecture เต็มรูปแบบ ว่าจะจัดการเรื่อง Authentication, API Gateway และ Service Mesh อย่างไร ห้ามพลาดครับ!