มาทำ WebSocket ให้สามารถรับ Load เยอะๆได้กัน
เกริ่นก่อนนะครับที่มาของบทความนี้เนื่องจาก WebSocket แบบเดี่ยวมีปัญหาในการรับมือกับ Request จำนวนมากพร้อมกัน ซึ่งปกติแล้ว WebSocket สามารถรองรับได้ราว 500–2000 Request พร้อมกัน ตามปกติแต่ละภาษาและเฟรมเวิร์กมีความสามารถในการจัดการกับคำขอพร้อมกันได้แตกต่างกัน การใช้เพียง WebSocket อันเดียวอาจนำไปสู่การล้มเหลวของระบบเมื่อมี Request เข้ามาอย่างหนาแน่น ดังนั้นเราจึงได้นำ Redis มาใช้เพื่อช่วยเพิ่มประสิทธิภาพ โดย Redis จะทำหน้าที่เป็นระบบ pub/sub ที่ช่วยให้ WebSocket แต่ละตัวสามารถสื่อสารและเชื่อมต่อข้อมูลกับกันและกันได้อย่างมีประสิทธิภาพ ซึ่งช่วยลดภาระและการตอบสนองที่ล่าช้าในการจัดการ Request จำนวนมากได้
WebSocket เริ่มการเชื่อมต่อโดยใช้คำขอ HTTP จาก Client To Server ซึ่งเรียกว่า Handshake หลังจากนั้นการเชื่อมต่อจะเปลี่ยนจาก HTTP ไปเป็น WebSocket และจะคงอยู่จนกว่าหนึ่งในฝ่ายจะตัดการเชื่อมต่อ ข้อดีของ WebSocket คือมันสามารถส่งข้อมูลไปและกลับระหว่างไคลเอ็นต์และเซิร์ฟเวอร์ได้ทันทีโดยไม่ต้องรีโหลดหน้าเว็บหรือทำการเชื่อมต่อใหม่
การใช้งาน WebSocket
WebSocket ถูกใช้ในหลายๆ Applications เช่น:
- เกมออนไลน์: เพื่อการสื่อสารระหว่างผู้เล่นแบบเรียลไทม์
- แชท: เพื่อส่งข้อความและข้อมูลแบบเรียลไทม์
- การเทรดหลักทรัพย์/หุ้น: เพื่อรับข้อมูลราคาแบบเรียลไทม์
- แดชบอร์ด/มอนิเตอร์: เพื่ออัพเดทข้อมูลแบบเรียลไทม์
Redis (Remote Dictionary Server) เป็นฐานข้อมูลแบบ NoSQL ที่จัดเก็บข้อมูลในหน่วยความจำ (in-memory) และสามารถใช้เป็นฐานข้อมูล, แคช (cache), และ message broker ได้ มีคุณสมบัติเด่นเรื่องความเร็วสูงเนื่องจากข้อมูลจะถูกจัดเก็บและดึงจากหน่วยความจำโดยตรง (RAM) แทนการใช้ดิสก์ ซึ่งเหมาะสำหรับแอปพลิเคชันที่ต้องการประสิทธิภาพและการตอบสนองที่รวดเร็ว
การทำงานของ Pub/Sub ใน Redis:
ใน Redis, โมเดล Pub/Sub ทำงานดังนี้:
- Publisher (ผู้ส่ง): เป็นฝ่ายส่งข้อมูลไปยังช่องหรือหัวข้อเฉพาะ โดยไม่รู้ว่ามีใครสมัครรับข้อมูลอยู่หรือไม่
- Subscriber (ผู้รับ): เป็นฝ่ายที่สมัครรับข้อมูลจากช่องเฉพาะ เมื่อมีข้อมูลใหม่จากผู้ส่งที่ส่งไปยังช่องนั้น ระบบ Redis จะส่งข้อมูลนั้นให้ผู้รับทุกคนที่สมัครรับข้อมูลจากช่องเดียวกันแบบเรียลไทม์
สิ่งที่ต้องเตรียมก่อนเริ่มบทความ
- Golang
- Postman
- VSCode
อันนี้คือ Source Code มาอธิบายเป็นส่วนๆ
มาดูที่ function init() ก่อน
- ทำการต่อ Redis
- กำหนด Upgrader สำหรับ WebSocket จะเห็นที่ allowOrigins คืออนุญาตให้ Domain อะไรมาต่อได้บ้าง จะเห็นผมอนุญาต origin == “” เพื่อที่จะให้ตัวมันเองยิงมาแล้วผ่านเอาไว้ทำ Health Check เพื่อเอาไปดูว่า Socket ยังต่อได้ปกติ
มาดูที่ function handleWebSocket
- ดึง Channel และ UUID เพื่อแยกการสื่อสารไปยังแต่ละ Client อย่างเฉพาะเจาะจงผ่าน WebSocket และรับประกันว่าข้อความจะถูกส่งไปยัง Client ที่เกี่ยวข้องเท่านั้น ส่วน UUID จะเอา Add เข้า Redis เอาไว้ไปดูว่าใคร Online อยู่จริงๆถ้าใครไม่ต้องการดูว่าใครออนไลน์อยู่ก็ไม่ต้องใส่มาก็ได้
- หลังจาก Subscribe แล้วก็จะรอข้อความจาก Publish เพื่อส่งไปหา Client ที่ทำการ Subscribe
- และจะมีการ Ping ไปที่ Client นั้นๆด้วยทุก 10 วิว่า Client นั้นยังทำงานอยู่ไหมถ้าไม่ทำงานแล้วจะทำการ UnSubscribe และทำการลบ UUID ออกจาก Redis
มาดู Function EmittingHandler
- ทำการส่ง Message เข้า Publish Client ที่เกี่ยวข้องซึ่งถ้าไม่มี Channel ไหน Subscribe ก็ไม่มีอะไรเกิดขึ้น
ส่วนอันนี้เป็น Health Check Socket กับ Redis ส่วนนี้ไม่มีอะไรเอาไว้ดูว่ายังไม่ตายใช่ไหม
มาลอง Run เล่นๆกันดีกว่า
ผมทำการจำลองขึ้นมาที่ Port 3000,3001 ที่ต่อ Redis ที่เดียวกัน
อันนี้คือ HTML เอาไว้ TEST โดยผมจะต่อ Socket ที่ Port 3000
อันนี้จะสมมุติใส่ uuid และ room1 เข้าไป
จะเห็นว่าผมต่อที่ PORT 3000 แต่ยิง Message เข้าทั้ง PORT 3000,3001 จะเห็นว่าข้อความเข้าหมด
เท่านี้เราก็ได้ Socket ที่รองรับโหลดได้จำนวนมากได้แล้ว แต่เดียวก่อนนนนนน!!!!!
ถ้าแบบนี้หน้าบ้านกระตุกแน่ๆถ้าหลังบ้านไหวหน้าบ้านไม่ไหว Browser ก็ค้างแน่นอน
จะเห็นว่าผมปรับ Code นิดหน่อยจากที่เราได้ข้อความจาก Publish ปกติเราส่งทันทีเราก็มา Delay และรวมส่งไปเป็น Messages ที่เป็น Array จากรูปผม Delay ไว้ 1 วิ สมมุติข้อความเข้ามา 10 ข้อความปกติหน้าบ้านจะได้ Socket 10 ข้อความแต่ปรับ Delay ก็จะได้ 1 Socket แต่มี 10 Messages แทน
จบไปแล้วหวังว่าจะเป็นประโยชน์ไม่มากก็น้อย Logic นี้เอาไปปรับใช้ได้ทุกภาษาเท่านี้ก็มี Socket ที่ทรงพลังแล้วไว้เจอกันบทความหน้านะครับ Good Luck