Understanding Event-driven and Non-Blocking I/O in Node.js

ผมว่าคนที่มาลองเขียน Node.js แบบผม ในตอนแรกก็ต้องพยายามที่จะเข้าใจ Concept ของ Platform ตัวนี้ไม่มากก็น้อยล่ะนะ เคยมีปัญหาไหม เวลาที่ต้องพยายามเข้าใจว่า "blocking" และ "non-blocking" I/O ต่างกันอย่างไร ? อะไรคือ Event-driven Programming ?

Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.
นี่คือคำโปรยในเว็บ Nodejs.org และคุณลักษณะที่ทำให้ Node.js ต่างกับ Server Side Platform ตัวอื่นๆ คือ Node.js uses an event-driven, non-blocking I/O model แล้วมันคืออะไร วันนี้เราจะมาอธิบายด้วยเรื่องเล่าสัก 2 เรื่อง ให้พอเห็นภาพกัน

The Doctor's Office Reception Line Analogy

ใน episode 102 of the Herding Code podcast Tim Caswell ได้อธิบายการทำงานของ event-driven programming ด้วยเรื่องเล่าเกี่ยวกับการต่อแถวเพื่อพบคุณผู้ช่วยคุณหมอในออฟฟิศแห่งหนึ่ง อย่างน้อยในสหรัฐอเมริกา การที่จะเข้าพบคุณหมอได้คุณต้องกรอกแบบฟอร์มต่างๆ เช่น ข้อมูลประกันชีวิต, เอกสารความเป็นส่วนตัว และอื่นๆ

A Traditional Model  

ในการเขียนโปรแกรมฝั่ง Server โดยทั่วไปนั้น จะใช้ระบบ thread-based ขอให้นึกภาพว่าคุณที่ออฟฟิศคุณหมอสักคนในสถานที่สักแห่งและกำลังติดต่ออะไรบางอย่าง โดยมีคนต่อแถวอยู่เมื่อถึงคิวคุณ คุณต้องต่อกรอกแบบฟอร์มทั้งหมด 3 แผ่น โดยที่คุณผู้ช่วยคนนั้น จะรอคุณจนกรอกเสร็จ จึงสามารถให้บริการลูกค้าคนต่อไปได้ (ประชาสัมพันธ์มีคนเดียว) จะเห็นได้ว่า การทำงานแบบนี้จะทำให้ตัวคุณ "blocking" ลูกค้าคนอื่นๆ

แน่นอนว่ามีทางเดียวที่จะ Scale ระบบแบบนี้ นั่นคือเพิ่มจำนวน "ผู้ช่วย" แต่อย่างไรก็ตาม จะเห็นได้ต้นทุนก็เพิ่มตามไปด้วย เพราะนอกจากต้องเพิ่มคุณผู้ช่วยแล้ว ก็ยังต้องเพิ่มช่องทาง (Server) เพื่อให้ลูกค้าท่านอื่น ได้เดินเข้าไปรับบริการอย่างสะดวก

Event To The Rescue

มาดูในระบบ event-based กันบ้าง นึกภาพต่อว่าเมื่อคุณกรอกแบบฟอร์มทั้ง 3 เสร็จแล้ว และพบว่ายังมีแบบฟอร์มที่ต้องกรอกอีก คุณผู้ช่วยก็ยื่นแบบฟอร์ม คลิปหนีบกระดาษ และปากกาให้คุณ พร้อมบอกว่าช่วยกลับมาอีกครั้งเมื่อกรอกเสร็จนะคะ (มโนว่าคุณผู้ช่วยเป็นสาวแว่น) จากนั้นคุณก็ไปนั่งสงบเสงี่ยมกรอกแบบฟอร์มที่ได้มา ในขณะที่คุณผู้ช่วยก็ทำหน้าที่บริการลูกค้าท่านอื่นที่อยู่ในแถว จะเห็นว่าคุณไม่ได้ "blocking" คุณผู้ช่วยกับลูกค้าท่านอื่น

เมื่อคุณกรอกแบบฟอร์มเรียบร้อยแล้ว คุณก็แค่ไปเข้าแถวใหม่ และรอที่จะได้ถึงคิวคุณ ถ้าพบว่าคุณกรอกแบบฟอร์มผิด หรือต้องกรอกแบบฟอร์มเพิ่มเติมอีก คุณผู้ช่วยก็จะทำแบบเดิมคือยื่นแบบฟอร์ม และปากกาให้คุณไปกรอก เมื่อเสร็จแล้วก็กลับมาเข้าแถวรอคิวเหมือนเดิม

จะเห็นได้ว่าระบบแบบนี้ง่ายต่อการ Scale มากๆ (และในความจริงของการต่อคิวเพื่อรับการรักษาโดยทั่วไปก็เป็นเช่นนั้น) ถ้าแถวที่มีอยู่เริ่มยาวมากๆ ก็อาจจะต้องเพิ่มคุณผู้ช่วย แต่ก็คงจะไม่ต้องเพิ่มเยอะขนาดนั้น เมื่อเทียบกับระบบที่เป็น thread-based

The Fast Food Restaurant Analogy

หลักการดังกล่าวก็ยังเหมือนกับการที่คุณสั่งการอาหารในร้านประเภท "แดกด่วน" (Fast Food) เช่นกัน

ในระบบ thread-based นั้น ลองนึกภาพเมื่อคุณกำลังต่อแถวซื้ออาหาร พอถึงคิวคุณก็สั่งอาหารกับพนักงานและรอจนกว่าในครัวจะทำเสร็จ และพนักงานก็เสริฟให้คุณ แน่นอนว่าพนักงานคนนั้นจะไม่สามารถให้บริการลูกค้าคนต่อไปได้ จนกว่าจะส่งอาหารถึงมือคุณ ... คิดว่าไงล่ะครับ ? ถ้าต้องการขายอาหารได้มากขึ้นในระบบแบบนี้ ก็ต้องเพิ่ม "พนักงาน" ยังไงล่ะ

แน่นอน เรารู้ว่าร้านอาหาร "แดกด่วน" ทั่วไปไม่ได้ทำงานกันแบบนั้น สิ่งที่เราเห็นในร้านอาหารประเภท "แดกด่วน" คือ event-driven ที่พวกเขาจะต้องให้พนักงานทำงานอย่างเต็มประสิทธิภาพเท่าที่จะเป็นไปได้ (ก็เป็นหลักการของเจ้าของธุรกิจทั่วไปล่ะนะ) ... เมื่อไหร่ก็ตามที่คุณสั่งอาหารไป ออเดอร์ของคุณจะถูกส่งให้ใครบางคน ขณะที่พนักงานกำลังทำรายการการชำระเงินให้คุณ เมื่อคุณจ่ายเงินเสร็จแล้ว คุณก็ต้องเขยิบไปนิดนึง เพื่อที่จะให้พนักงานคนนั้นทำรายการให้ลูกค้าคนต่อไป ในร้านอาหารบางแห่ง คุณอาจจะเห็น Pager (บทความนี้เขียนนานแล้วแน่เลย ในสมัยนี้คงเป็นมือถือไม่ก็ PDA ตามร้าน) มีไฟและการสั่นเตือน เมื่อออเดอร์ของคุณเรียบร้อยแล้ว ... จุดที่สำคัญของการเล่าเรื่องนี้จะเห็นว่าออเดอร์ของคุณไม่ได้ "blocking" ออเดอร์ของคนอื่นหรือการทำงานของพนักงานคนดังกล่าว

เมื่ออาหารของคุณเรียบร้อยแล้ว พนักงานก็ส่งสัญญาณบอกให้คุณรู้ อาจจะเป็นการเรียกชื่อ หรือหมายเลขออเดอร์ของคุณ จะเห็นว่าเหตุการณ์ (event) ที่ออเดอร์ของคุณเรียบร้อยทำให้ใครบางคนทำอะไรบางอย่างเพื่อบอกให้คุณรู้ (ในภาษาโปรแกรม lingo สิ่งนี้ถูกเรียกว่า callback function) จากนั้นคุณก็จะรู้ตัวและไปรับอาหารประเภท "แดกด่วน" ของคุณ

So What Does This Have To Do With Node.js?

Web Server ทั่วๆ ไป ที่เรารู้จักกัน เช่น Apache จะทำงานด้วยระบบ thread-based ทั้งสิ้น เมื่อคุณสั่งให้ Apache ทำงาน มันจะเริ่มทำงานรับการเชื่อมต่อจากใครสักคน เมื่อได้การเชื่อมต่อแล้ว มันจะเปิดการเชื่อมต่อกับใครคนนั้นเพื่อส่งหน้าเว็บ หรือข้อมูลอะไรบางอย่างไปให้ ถ้ามันใช้เวลาในระดับ 2 - 3 microseconds ในการดึงหน้าเว็บจากที่เก็บข้อมูล และเขียนผลลัพธ์ลงฐานข้อมูล ตัว Web Server จะ "blocking" การทำงานของการรับส่งข้อมูลนั้นๆ (หรืออาจจะเรียกว่า blocking I/O) ซึ่งวิธีที่จะ Scale Web Server ประเภทนี้ คุณจะต้องเพิ่มโคลนนิ่ง Server เพื่อมาทำงานตรงนี้ (เรียกว่า thread-base เพราะแต่ละโคลนนิ่งที่เกิดขึ้น จะต้องใช้ thread เพิ่มเติมจาก OS)

ในทางกลับกัน Node.js ใช้แนวทางของ event-driven นั่นคือเมื่อได้รับ request มาแล้ว จะทำการส่งต่อไปยังคิวของการทำงาน (ใน Node.js ใช้สิ่งที่เรียกว่า event-loop นึกภาพท่อสักท่อ ที่จะมีของลงไปเรื่อยๆ) และตัว Node ก็กลับมารับ request ต่อไปทันที เมื่อมี request ไหนทำงานเรียบร้อยแล้ว event-loop ก็จะบอกเพื่อให้ Node มาตรวจสอบที่คิว และทำอะไรบางอย่างตามที่สั่งไว้ จะเห็นว่ารูปแบบการทำงานนีมีประสิทธิภาพและง่ายต่อการขยายระบบอย่างมาก เพราะ Web Server จะสามารถรับ request ที่เข้ามาได้ตลอด เนื่องจากไม่ต้องรอการทำงานของการรับส่งข้อมูลใดๆ เลย (นี่เรียกว่า "non-blocking I/O" หรือ "event-driven I/O")

มาสรุปกันสักหน่อย ลองพิจารณาการทำงานต่อไปนี้นะ
  • คุณใช้ Web Browser ทำการ request ไปที่ "/about.html" บน Web Server ที่เขียนด้วย Node.js
  • Node จะทำการรับ request ที่ได้ และเรียกฟังก์ชันที่จะเรียกไฟล์นั้นจาก disk
  • ขณะที่ Node กำลังรอไฟล์นั้น ("/about.html") Node ก็ไปรับ request ถัดไปมาทำงาน
  • เมื่อไฟล์นั้นของคุณมาถึง ก็จะมี callback function ถูกใส่ไปที่คิวของ Node Server
  • Node Server จะทำงานฟังก์ชันดังกล่าวเมื่อถึงคิว และส่งการแสดงผลหน้า "/about.html" กลับไปหาคุณที่ Web Browser
และแน่นอน มันอาจจะใช้เวลาสัก 2 - 3 microseconds ขณะที่ Server นำไฟล์ดังกล่าวมาให้เหมือนจะเป็นเวลาที่น้อยมาก แต่เวลาที่น้อยขนาดนั้นมีผลแน่นอน ถ้าเรากำลังพูดกันถึงเรื่อง highly-scalable web servers 

นี่ทำให้ Node.js แตกต่างและมีความน่าสนใจ เพิ่มเติมคือมันใช้ภาษาที่โครตจะเป็นที่รู้จัก (แม่งจะครองโลก) อย่าง Javascript ในการพัฒนาด้วย

อีกอย่างคือการที่เรื่องเล่าทั้ง 2 เรื่องพยายามบอกและทำให้เห็นว่า พนักงาน หรือคุณผู้ช่วยมีคนเดียวนั้น เพราะว่า Node.js ทำงานแบบ Single Thread นะ อย่าเข้าใจผิดว่ามันทำงานเป็น Multi-Thread ล่ะ

Popular posts from this blog

[Android Dev] การติดตั้ง Eclipse+AndroidSDK เพื่อพัฒนาโปรแกรมบน Android

12 วิธี การบริการและดูแลลูกค้าในร้าน Starbucks

5 TED Talk ที่จะช่วยให้คุณทำงานดีขึ้น