Node.js และการร้องขอที่ใช้ CPU มาก


215

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

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

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

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

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

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


2
โอลิเวียร์คุณถามคำถามที่เหมือนกันที่ฉันมีในใจ (ใหม่ถึงโหนด) และโดยเฉพาะเกี่ยวกับการประมวลผลภาพ ใน Java ฉันสามารถใช้ ExecutorService แบบเธรดและส่งงานปรับขนาดทั้งหมดและรอให้การเชื่อมต่อทั้งหมดเสร็จสิ้นในโหนดฉันไม่ได้คิดวิธีสับเปลี่ยนงานกับโมดูลภายนอกที่ จำกัด พูด) จำนวนสูงสุดของการดำเนินการพร้อมกันถึง 2 ในเวลา คุณพบวิธีที่สง่างามในการทำเช่นนี้หรือไม่?
Riyad Kalla

คำตอบ:


55

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

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

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


35
เหตุใดคุณจึงเชื่อมโยงไปยังไลบรารี Ruby ในคำถามที่มีเหตุผลเฉพาะในโลกโหนด
Jonathan Dumaine

1
@JonathanDumaine เป็นการนำคิวงานมาใช้อย่างดี ราดรหัสทับทิมและเขียนใหม่ใน javascript กำไร!
Simon Stender Boisen

2
ฉันเป็นแฟนตัวยงของเรื่องนี้คนงานเฟืองไม่ได้สำรวจเซิร์ฟเวอร์คนงานเกียร์สำหรับงานใหม่ - งานใหม่จะถูกผลักไปหาคนงานทันที ตอบสนองได้ดีมาก
Casey Flynn

1
ในความเป็นจริงมีใครบางคนส่งมันไปยังโลกของโหนด: github.com/technoweenie/coffee-resque
FrontierPsycho

@ pacerier ทำไมคุณถึงพูดอย่างนั้น? คุณเสนออะไร
luis.espinal

289

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

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

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


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

13
อย่าวางกระบวนการลูกสำหรับการร้องขอทุกครั้ง (ที่เอาชนะวัตถุประสงค์ของ node.js) วางไข่คนงานจากภายในคำขอหนักของคุณเท่านั้น หรือกำหนดเส้นทางพื้นหลังที่หนักของคุณไปยังสิ่งอื่นที่ไม่ใช่ node.js
Thilo

47
การเปรียบเทียบที่ดี mbq!
แลนซ์ฟิชเชอร์

6
ฉันชอบมันมาก "Node.js: ทำให้การปฏิบัติที่ไม่ดีทำงานไม่ดี"
อีธาน

7
@mbq ฉันชอบการเปรียบเทียบ แต่มันสามารถใช้งานได้บ้าง รูปแบบมัลติเธรดแบบดั้งเดิมจะเป็นคนที่บริกรและปรุงอาหาร เมื่อมีคำสั่งซื้อแล้วบุคคลนั้นจะต้องกลับไปปรุงอาหารก่อนที่จะสามารถจัดการกับคำสั่งอื่นได้ โมเดล node.js มีโหนดเป็นบริกรและเว็บเวิร์คเกอร์เป็นพ่อครัว บริกรจัดการกับการดึง / แก้ไขคำขอในขณะที่คนงานจัดการงานที่ต้องใช้เวลามากขึ้น หากคุณต้องการขยายขนาดให้ใหญ่ขึ้นคุณเพียง แต่ทำให้เซิร์ฟเวอร์หลักเป็นโหนดคลัสเตอร์และทำพร็อกซีย้อนกลับงานที่ใช้ CPU มากไปยังเซิร์ฟเวอร์อื่นที่สร้างขึ้นสำหรับการประมวลผลแบบเธรด milti
Evan Plaice

16

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

อีกวิธีหนึ่งในการจัดโครงสร้างสิ่งนี้คือการตั้งค่าคิวงานและส่งงานการคำนวณขนาดใหญ่เหนือคิว โปรดทราบว่ามีค่าใช้จ่ายจำนวนมากที่เกี่ยวข้องกับ IPC สำหรับคิวงานดังนั้นสิ่งนี้มีประโยชน์เฉพาะเมื่องานมีขนาดใหญ่กว่าค่าโสหุ้ย

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

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

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

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

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

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


ควรเป็นคำตอบที่ถูกต้อง .... สำหรับวิธีแก้ปัญหาที่คุณวางไข่ 8 กลุ่มคุณต้องการ 8 คอร์ใช่ไหม? หรือโหลดบาลานเซอร์หลายเซิร์ฟเวอร์
มูฮัมหมัดอูเมอร์

ยังเป็นวิธีที่ดีในการเรียนรู้เกี่ยวกับวิธีที่ 2 การตั้งค่าคิว แนวคิดของคิวนั้นค่อนข้างเรียบง่าย แต่เป็นส่วนหนึ่งของการส่งข้อความระหว่างกระบวนการและคิวที่ต่างประเทศ
มูฮัมหมัดอูเมอร์

ถูกตัอง. คุณจำเป็นต้องทำงานให้กับคอร์อื่นอย่างใด เพื่อที่คุณจะต้องมีแกนอื่น
masonk

Re: คิว คำตอบที่ใช้ได้คือการใช้คิวงาน มีบางอย่างสำหรับโหนด ฉันไม่เคยใช้คำเหล่านี้เลยฉันจึงไม่สามารถให้คำแนะนำได้ คำตอบของความอยากรู้อยากเห็นคือกระบวนการของผู้ปฏิบัติงานและกระบวนการคิวจะสื่อสารผ่านซ็อกเก็ตในที่สุด
masonk

7

สองแนวทางที่คุณสามารถใช้ได้

ในฐานะที่เป็น @Tim notes คุณสามารถสร้างงานแบบอะซิงโครนัสที่อยู่ภายนอกหรือขนานกับตรรกะการแสดงหลักของคุณ ขึ้นอยู่กับข้อกำหนดที่แน่นอนของคุณ แต่cronยังสามารถทำหน้าที่เป็นกลไกการเข้าคิว

WebWorkers สามารถทำงานกับกระบวนการ async ของคุณได้ แต่ปัจจุบันยังไม่รองรับโดย node.js มีส่วนขยายสองสามตัวที่ให้การสนับสนุนตัวอย่างเช่น: http://github.com/cramforce/node-worker

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


0

ใช้child_processเป็นทางออกหนึ่ง แต่กระบวนการลูกแต่ละอันเกิดจากการวางไข่อาจใช้หน่วยความจำมากเมื่อเทียบกับ Gogoroutines

คุณยังสามารถใช้วิธีแก้ปัญหาคิวเช่นkue

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.