ค้นหาเว็บไซต์

javascript - Node.js และคำขอที่ใช้หน่วยประมวลผลสูง


ฉันเริ่มลองเล่นกับเซิร์ฟเวอร์ HTTP ของ Node.js และชอบการเขียน Javascript ฝั่งเซิร์ฟเวอร์จริงๆ แต่บางอย่างก็ทำให้ฉันไม่สามารถเริ่มใช้ Node.js สำหรับแอปเว็บของฉันได้

ฉันเข้าใจแนวคิดเกี่ยวกับการประมวลผลแบบอะซิงโครนัส I/O ทั้งหมดแล้ว แต่ฉันค่อนข้างกังวลเกี่ยวกับกรณีขอบเขตที่โค้ดเชิงกระบวนการใช้ CPU หนักมาก เช่น การจัดการภาพหรือการจัดเรียงชุดข้อมูลขนาดใหญ่

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

ข้อเสนอหนึ่งคือการใช้ Web Workers สำหรับงานที่ใช้ CPU มาก อย่างไรก็ตามผมกลัวว่า web workers จะทำให้เขียนโค้ดให้สะอาดยากขึ้นเพราะมันทำงานโดยการรวมไฟล์ JS แยกต่างหาก แล้วถ้าโค้ดที่ใช้ CPU อยู่ในเมธอดของออบเจ็กต์ล่ะ? มันค่อนข้างแย่ที่ต้องเขียนไฟล์ JS สำหรับแต่ละเมธอดที่ใช้ CPU มาก

อีกข้อเสนอหนึ่งคือการสร้างกระบวนการย่อย แต่สิ่งนั้นทำให้โค้ดยากต่อการบำรุงรักษามากขึ้น

มีข้อเสนอแนะใดบ้างในการเอาชนะอุปสรรค (ที่มองเห็น) นี้? คุณเขียนโค้ดเชิงวัตถุที่สะอาดด้วย Node.js ได้อย่างไร ในขณะที่ทำให้แน่ใจว่างานที่ใช้ CPU หนักถูกดำเนินการแบบอะซิงโครนัส?

คำตอบ #1

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

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

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

คำตอบ #2

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

บางอย่างเช่นนี้ https://github.com/resque/resque

นี่คือตัวบทความจาก github เกี่ยวกับเหตุผลที่พวกเขาสร้างมัน http://github.com/blog/542-introducing-resque

คำตอบ #3

คุณไม่ต้องการให้โค้ดที่ใช้ CPU หนักทำงานแบบ async คุณต้องการให้มันทำงาน แบบขนาน คุณต้องย้ายงานประมวลผลออกจากเธรดที่ให้บริการ HTTP request นั่นคือวิธีเดียวที่จะแก้ปัญหานี้ ด้วย NodeJS คำตอบคือโมดูล cluster สำหรับสร้าง child process เพื่อทำงานหนัก (ตามที่ทราบ Node ไม่มีแนวคิดเรื่องเธรด/หน่วยความจำแชร์ มันใช้เพียงกระบวนการหรือไม่ทำอะไรเลย) คุณมีสองตัวเลือกในการจัดโครงสร้างแอปพลิเคชันของคุณ คุณสามารถได้วิธีแก้ปัญหา 80/20 โดยการสร้างเซิร์ฟเวอร์ HTTP 8 ตัวและจัดการงานที่ใช้การคำนวณหนักแบบซิงโครนัสบน child process การทำเช่นนั้นค่อนข้างง่าย คุณอาจใช้เวลาเพียงหนึ่งชั่วโมงในการอ่านเกี่ยวกับมันที่ลิงก์นั้น ในความเป็นจริง หากคุณเพียงคัดลอกโค้ดตัวอย่างที่ด้านบนของลิงก์นั้น คุณจะได้ไปถึง 95% ของกระบวนการทั้งหมด

วิธีอีกวิธีในการจัดโครงสร้างสิ่งนี้คือการตั้งค่าคิวงานและส่งงานคำนวณขนาดใหญ่ผ่านคิวงาน โปรดทราบว่ามีค่าใช้จ่ายสูงเกี่ยวกับการสื่อสารระหว่างกระบวนการ (IPC) สำหรับคิวงาน ดังนั้นวิธีนี้จึงสามารถใช้ได้เมื่อขนาดของงานใหญ่กว่าค่าใช้จ่ายดังกล่าวอย่างชัดเจน

ฉันประหลาดใจที่ไม่มีคำตอบอื่น ๆ เหล่านี้แม้แต่จะ กล่าวถึง cluster

พื้นหลัง: โค้ดแบบอะซิงโครนัสคือโค้ดที่หยุดชั่วคราวจนกว่าจะเกิดบางสิ่งบางอย่าง ที่อื่น ซึ่งเมื่อเกิดเหตุการณ์นั้นขึ้น โค้ดจะตื่นขึ้นและดำเนินการต่อ หนึ่งกรณีที่พบได้บ่อยมากที่บางสิ่งต้องเกิดขึ้นช้าๆ ที่อื่นคือการทำงาน I/O

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

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

การรอ I/O เป็นรูปแบบที่เกิดขึ้นเสมอในเว็บเซิร์ฟเวอร์ ตัวอย่างเช่น ลูกค้าทุกคนที่เชื่อมต่อกับเซิร์ฟเวอร์ของคุณจะได้ซ็อกเก็ต ส่วนใหญ่ซ็อกเก็ตมักว่างอยู่ คุณไม่ต้องการทำอะไรจนกว่าซ็อกเก็ตจะได้รับข้อมูล ซึ่งในจุดนั้นคุณต้องการจัดการกับคำขอ ภายใต้ระบบ เซิร์ฟเวอร์ HTTP เช่น Node จะใช้ไลบรารีอีเวนต์ (libev) เพื่อติดตามซ็อกเก็ตที่เปิดอยู่เป็นพัน ๆ ตัว ระบบปฏิบัติการจะแจ้ง libev และจากนั้น libev จะแจ้ง NodeJS เมื่อซ็อกเก็ตหนึ่งได้รับข้อมูล และจากนั้น NodeJS จะใส่อีเวนต์ลงในคิวอีเวนต์ และโค้ด HTTP ของคุณจะเริ่มทำงานในจุดนี้และจัดการอีเวนต์ทีละตัว อีเวนต์จะไม่ถูกใส่ลงในคิวจนกว่าซ็อกเก็ตจะมีข้อมูล ดังนั้นอีเวนต์จึงไม่เคยรอข้อมูล - ข้อมูลนั้นมีอยู่แล้วสำหรับพวกเขา

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

บทความที่เกี่ยวข้อง