การใช้งาน Message Queue Pattern
Message Queue (เรียกย่อๆว่า MQ) เป็นส่วนประกอบหนึ่งที่สำคัญในการออกแบบระบบขนาดใหญ่ โดย MQ ทำหน้าที่ในการรับ Message จากต้นทาง เก็บรักษาไว้ตามลำดับที่รับ Message เข้ามา และเปิดให้ปลายทาง มาหยิบ Message ออกไปทีละ 1 Message (หรือมากกว่า) ตามลำดับที่กำหนดไว้ตามประเภทของ Queue นั้นๆโดยที่ MQ นั้นเอง ก็มีหลากหลายประเภท หลายยี่ห้อผู้ผลิต และหลากหลายลักษณะการใช้งาน แต่ในพื้นฐานแล้ว ก็จะมีลักษณะเหมือนกัน ลองมาดูภาพการทำงานแบบคร่าวๆกันครับ
จากภาพ เราเรียก Service A ว่า Producer (ผู้ผลิต) และ Service B ว่า Consumer (ผู้บริโภค) โดย Producer จะสร้าง Message และส่งเข้าไปรอไว้ในคิว เพื่อให้ Consumer มาหยิบข้อความไปใช้ ข้อความที่ส่งเข้าไปใน MQ ก็จะถูกเก็บรักษาเอาไว้รอให้ Consumer มาหยิบโดยความเร็วข้อความที่ส่งเข้ามา อาจจะไม่เท่ากับความเร็วของข้อความที่ถูกดึงออกไป เช่นถ้า Producer ส่งข้อความทีละ 10 ข้อความต่อนาที แต่ Consumer อ่านไปทำได้ทีละ 1 ข้อความต่อนาที ก็จะทำให้มีข้อความค้างใน MQ เพิ่มขึ้นเรื่อยๆ 9 ข้อความต่อนาที
ในการออกแบบระบบขนาดใหญ่ การนำ MQ เข้ามาใช้ ให้ประโยชน์หลายอย่าง ในหลายๆสถานการณ์ โดยพื้นฐานแล้ว การนำ MQ มาใช้ ช่วยในการลดการพึ่งพากันของระบบสองระบบ ยกตัวอย่างเช่นในการออกแบบระบบที่เป็น Microservice ลองสมมุติระบบตัวอย่างเป็น การคุยกันระหว่าง Microservice สองตัวโดยใช้ Direct API Call ตามรูป
การเรียก Service A 1 ครั้ง จำเป็นต้องไปเรียก Service B 1 ครั้ง แบบ Synchronous (หมายถึงจะต้องรอให้ Service B ตอบกลับมาก่อน Service A จึงจะทำงานต่อได้) ถ้ามีการเรียก A 20 ครั้ง ก็จำเป็นต้องเรียก Service B 20 ครั้ง ลองมาดู ปัญหา ที่อาจจะเกิดขึ้นกันครับ
- ถ้า Service A ใช้เวลาทำงาน 1 วินาที แต่ Service B ต้องใช้เวลาทำงานนาน เช่น 20 วินาที แปลว่า Client ที่เรียกมายัง Service A จะต้องรอการตอบกลับเป็นเวลารวม 21 วินาที
- ซึ่งถ้าจำเป็นจะต้อง Scale ให้ Service A รับ Request ได้ 1000 TPS ก็จำเป็นจะต้อง Scale Service B ให้รับ Request ได้ 1000 TPS เช่นกัน
- และถ้า Service B มีปัญหา ก็จะพาให้ Service A มีปัญหาไปด้วย และจะทำให้ Client นั้นต้องจัดการ Error ของ request ที่เรียกไปยัง Service A ด้วยเช่นกัน
- และตัวอย่าง classic ที่เจอบ่อยๆคือถ้า Service B ทำงานช้าลงเรื่อยๆจนทำให้ Service A ต้องรอนานมากๆ ตัว Service A อาจจะ Crash เนื่องจาก memory หมดได้ เพราะมี request ที่รอ Response ตอบจาก Service B ค้างเป็นจำนวนมาก
อีกประเด็นหนึ่ง ในมุมมองของ UX ถ้าผู้ใช้งานที่ฝั่ง Client จำเป็นต้องรอ 21 วินาที ต่อการทำ Action เช่นการกดปุ่ม 1 ครั้ง เป็นเรื่องที่ไม่เหมาะสมเนื่องจากความอดทนรอได้ของผู้ใช้นั้น มีไม่ถึง 5 วินาทีด้วยซ้ำครับ เพราะฉะนั้น การเปลี่ยนการออกแบบ Microservice ให้คุยกันแบบ Asynchronous (ส่งคำสั่งแล้วจบเลย ไม่ต้องรอผลเพื่อตอบกลับ) โดยการนำ Queue เข้ามาช่วย จึงเป็นคำตอบที่ดีกว่าในกรณีนี้ ตัวอย่างเช่น
จากภาพ Service A นั้น สามารถส่งคำสั่งไปรอไว้ที่ Processor Queue และสามารถตอบกลับไปยัง Client ได้ทันทีว่า Action นั้นกำลังทำงานอยู่ โดยที่ Service B ที่ทำงานช้ากว่า สามารถทยอยหยิบ Message ออกมาทำทีละ 1 Message และเมื่อทำเสร็จแล้ว ก็จะใช้ Pattern ของ Queue เข้ามาส่งคำสั่งเพื่อแจ้งไปยัง Notification Service ให้ทำการส่ง In App Notification ไปแจ้งผู้ใช้ว่า Action ที่ลูกค้าสั่งไปนั้นนั้นได้ทำงานสำเร็จเรียบร้อยแล้ว
การนำ MQ มาประยุกต์ใช้ในการออกแบบระบบนั้น ยังมีอีกหลากหลายเรื่อง ซึ่งผมจะเอามาเล่าในโอกาสต่อๆไปครับ แต่ถ้าใครสนใจอยากจะหาอ่านเพิ่มเติมด้วยตนเอง สามารถอ่านได้ที่ https://www.enterpriseintegrationpatterns.com/patterns/messaging/
และสามารถ Download draw.io diagram ที่ใช้ในบทความนี้ได้ จาก https://drive.google.com/file/d/1_mHKeqZD8J96gEwiZ0KjYkLR7Gsyjt7v/view?usp=sharing