12/04/2026 18:17น.

EP.86 การจัดการการเชื่อมต่อใน WebSocket ด้วย Redis Pub/Sub
#Go
#Golang
#WebSocket
#Redis
#Redis Pub/Sub
เมื่อแอปพลิเคชันแบบ Real-time ขยายตัว ผู้ใช้งานเพิ่มขึ้น การรัน WebSocket Server เพียงเครื่องเดียวอาจไม่เพียงพอ — ทำให้เกิดความจำเป็นต้อง แยกเซิร์ฟเวอร์ WebSocket ออกเป็นหลายเครื่อง (multi-instance) เพื่อรองรับปริมาณการใช้งานขนาดใหญ่
ใน EP นี้ เราจะพาคุณเรียนรู้การใช้ Redis Pub/Sub เพื่อประสานการทำงานระหว่าง WebSocket Server หลายเครื่อง ให้ผู้ใช้ที่เชื่อมต่อกับเซิร์ฟเวอร์ใด ๆ ก็สามารถ ส่ง-รับข้อความร่วมกันได้แบบ Real-time
🔄 ทำไมต้องใช้ Redis Pub/Sub?
โดยปกติ WebSocket Server จะเก็บเฉพาะ connection ของ client ที่เชื่อมต่อกับตนเองเท่านั้น หากมีหลาย instance ผู้ใช้ที่อยู่บนเซิร์ฟเวอร์ A จะ ไม่สามารถส่งข้อความไปถึง ผู้ใช้ที่อยู่บนเซิร์ฟเวอร์ B ได้
Redis Pub/Sub ช่วยแก้ปัญหานี้ด้วยการทำตัวเป็น Message Broker ระหว่าง WebSocket Servers:
- Publisher: เมื่อ client ส่งข้อความเข้าเซิร์ฟเวอร์ → เซิร์ฟเวอร์นั้น publish ข้อความไปยัง Redis channel
- Subscriber: ทุก instance ของ WebSocket Server subscribe channel เดียวกัน และส่งข้อความนั้นไปยัง client ของตนเอง
✅ ผลลัพธ์:
- ผู้ใช้ทุกคนได้รับข้อความเหมือนกัน ไม่ว่าจะต่อเข้าที่เครื่องไหน
- ระบบสามารถ scale แนวนอน (horizontal scaling) ได้ง่าย
🧪 ตัวอย่างโค้ด Go + Redis Pub/Sub
package main
import (
"context"
"fmt"
"log"
"net/http"
"github.com/go-redis/redis/v8"
"github.com/gorilla/websocket"
)
var (
upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { return true },
}
clients = make(map[*websocket.Conn]bool)
ctx = context.Background()
)
func main() {
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
go subscribeRedis(rdb)
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println("Upgrade error:", err)
return
}
defer conn.Close()
clients[conn] = true
for {
_, msg, err := conn.ReadMessage()
if err != nil {
log.Println("ReadMessage error:", err)
delete(clients, conn)
break
}
// Publish message to Redis
if err := rdb.Publish(ctx, "chat_channel", msg).Err(); err != nil {
log.Println("Publish error:", err)
}
}
})
log.Println("WebSocket server started on :8080")
http.ListenAndServe(":8080", nil)
}
func subscribeRedis(rdb *redis.Client) {
sub := rdb.Subscribe(ctx, "chat_channel")
ch := sub.Channel()
for msg := range ch {
for client := range clients {
if err := client.WriteMessage(websocket.TextMessage, []byte(msg.Payload)); err != nil {
log.Println("WriteMessage error:", err)
client.Close()
delete(clients, client)
}
}
}
}
🔍 อธิบายโค้ด
| ส่วน | รายละเอียด |
|---|---|
clients map | เก็บ WebSocket connections ของผู้ใช้ที่เชื่อมต่อกับ instance นี้ |
rdb.Publish | เมื่อมี client ส่งข้อความเข้ามา → ส่งไปยัง Redis channel |
subscribeRedis() | รับข้อความจาก Redis แล้วกระจายให้ client ที่เชื่อมต่อกับเครื่องนี้ |
💡 ประโยชน์ของการใช้ Redis Pub/Sub
- ✅ รองรับ Multi-instance WebSocket Server
- ✅ ขยายระบบแบบ Horizontal Scaling ได้ง่าย
- ✅ รองรับผู้ใช้งานจำนวนมาก โดยไม่สูญเสียการสื่อสาร
- ✅ ไม่ต้องให้เครื่อง server แต่ละตัวรู้จักกันโดยตรง
🚀 ท้าให้ลอง!
- สร้างระบบ WebSocket Server หลาย instance (เช่น รันหลาย process หรือหลาย container)
- เชื่อมต่อ Redis Pub/Sub เป็นตัวกลาง
- ทดสอบว่า client ที่เชื่อมต่อกับคนละ server สามารถคุยกันได้จริง
🔜 Next EP:
EP.87: การป้องกัน DDoS ใน WebSocket Server
เราจะไปต่อกับการ ป้องกันการโจมตี เช่น DDoS, connection flood, และ malformed frame attacks เพื่อให้ระบบ WebSocket ของคุณพร้อมสำหรับงานจริง
อ่านบทความ Series อื่นๆ
🔵 Facebook: https://www.facebook.com/superdev.academy.th
🔴 YouTube : Superdev Academy
📸 Instagram: Superdev Academy
🎬 TikTok: https://www.tiktok.com/@superdevacademy?lang=th-TH
🌐 Website: https://www.superdevacademy.com/