เพื่อนที่คุ้นเคยกับ JS รู้ว่า js 是单线程
เดี่ยว เนื่องจากข้อ จำกัด แบบเธรดเดี่ยวของ JavaScript บนเซิร์ฟเวอร์ Multi-core เราจึงต้องเริ่มต้นกระบวนการหลายกระบวนการเพื่อเพิ่มประสิทธิภาพเซิร์ฟเวอร์ให้สูงสุด
กลุ่มกระบวนการ node.js สามารถใช้เพื่อเรียกใช้หลายอินสแตนซ์ node.js ซึ่งสามารถแจกจ่ายเวิร์กโหลดระหว่างเธรดแอปพลิเคชันของพวกเขา เมื่อไม่จำเป็นต้องมีการแยกกระบวนการให้ใช้โมดูล worker_threads
แทนซึ่งอนุญาตให้ใช้เธรดแอปพลิเคชันหลายตัวภายในอินสแตนซ์ Node.js เดียว
แนะนำโมดูลคลัสเตอร์หลังจากเวอร์ชัน v0.8一个主进程(master) 管理多个子进程(worker) 的方式实现集群
.
โมดูลคลัสเตอร์ทำให้ง่ายต่อการสร้างกระบวนการเด็กที่แชร์พอร์ตเซิร์ฟเวอร์
child_process
ล่างของcluster
net
TCP
UDP
เมื่อคลัสเตอร์เริ่มต้นเซิร์ฟเวอร์ TCP จะเริ่มต้นภายในและตัวบ่งชี้ไฟล์ของซ็อกเก็ตเซิร์ฟเวอร์ TCP จะถูกส่งไปยังกระบวนการทำงาน
ในแอปพลิเคชัน cluster
โมดูล一个主进程只能管理一组工作进程
child_process
const คลัสเตอร์ = ต้องการ ('คลัสเตอร์') และคอมเพล็กซ์
.isPrimary
.isWorker
.isMaster
.worker
การอ้างอิงงานปัจจุบันไปยังวัตถุกระบวนการ [ในกระบวนการเด็ก].workers
เก็บแฮชของวัตถุกระบวนการทำงานที่ใช้งานอยู่โดยมีฟิลด์ id
เป็นคีย์ สิ่งนี้ทำให้ง่ายต่อการวนซ้ำผ่านกระบวนการของคนงานทั้งหมด มีเฉพาะในกระบวนการหลัก cluster.wokers[id] === worker
[ในกระบวนการหลัก].settings
เป็นแบบอ่านอย่างเดียว รายการการกำหนดค่าคลัสเตอร์ หลังจากเรียกเมธอด .setupPrimary() หรือ .fork() แล้ว ออบเจ็กต์การตั้งค่านี้จะมีการตั้งค่า รวมถึงค่าเริ่มต้นด้วย เมื่อก่อนเป็นวัตถุเปล่า ไม่ควรเปลี่ยนแปลงหรือตั้งค่าออบเจ็กต์นี้ด้วยตนเองcluster.settings
รายการการกำหนดค่ารายละเอียด:- `execargv` <string []> รายการของพารามิเตอร์สตริงที่ส่งผ่านไปยังไฟล์ปฏิบัติการ node.js **默认值:** `process.execArgv`。 - `exec` <string> พา ธ ไฟล์ไปยังไฟล์กระบวนการทำงาน **默认值:** `process.argv[1]`。 - `args` <string []> อาร์กิวเมนต์สตริงที่ส่งผ่านไปยังกระบวนการของคนงาน **默认值:**`process.argv.slice(2)`。 - `cwd` <string>ไดเร็กทอรีการทำงานปัจจุบันของกระบวนการของผู้ปฏิบัติงาน ** ค่าเริ่มต้น: ** `undefined` (สืบทอดมาจากกระบวนการหลัก) - `serialization` <string> ระบุประเภทการทำให้เป็นอนุกรมที่ใช้ในการส่งข้อความระหว่างกระบวนการ ค่าที่เป็นไปได้คือ `'json'` และ`' ขั้นสูง ' **ค่าเริ่มต้น:** `เท็จ` - `Silent` <boolean> จะส่งเอาต์พุตไปยังอินพุตและเอาต์พุตมาตรฐานของกระบวนการหลักหรือไม่ **ค่าเริ่มต้น:** `เท็จ` - `stdio` <rray> กำหนดค่าอินพุตและเอาต์พุตมาตรฐานของกระบวนการวางไข่ เนื่องจากโมดูลคลัสเตอร์อาศัย IPC เพื่อเรียกใช้การกำหนดค่านี้จะต้องมีรายการ `'IPC'` เมื่อตัวเลือกนี้มีให้มันจะแทนที่ `เงียบ ' - `uid` <gumber> ตั้งค่ารหัสผู้ใช้ของกระบวนการ - `gid` <gumber> ตั้งค่า ID กลุ่มของกระบวนการ - `InspectPort` <number> | <function> ตั้งค่าพอร์ตผู้ตรวจสอบสำหรับกระบวนการของคนงาน นี่อาจเป็นตัวเลขหรือฟังก์ชั่นที่ไม่มีพารามิเตอร์และส่งคืนตัวเลข โดยค่าเริ่มต้นกระบวนการของคนงานแต่ละคนมีพอร์ตของตัวเองเริ่มต้นจากกระบวนการหลักของกระบวนการ `debugport` และเพิ่มขึ้น - `windowshide` <boolean> ซ่อนหน้าต่างคอนโซลกระบวนการวางไข่ที่สร้างขึ้นตามปกติบนระบบ Windows **ค่าเริ่มต้น:** `เท็จ`
.fork([env])
วางไข่กระบวนการของผู้ปฏิบัติงานใหม่ [ในกระบวนการหลัก].setupPrimary([settings])
โหนด> 16.setupMaster([settings])
ใช้เพื่อเปลี่ยนพฤติกรรม 'ส้อม' เริ่มต้น หลังจากใช้การตั้งค่าจะปรากฏใน cluster.settings
การเปลี่ยนแปลงการตั้งค่าใด ๆ จะส่งผลกระทบต่อการโทรในอนาคตไปยัง .fork()
เท่านั้นซึ่งยังไม่ได้ใช้งานกระบวนการของผู้ปฏิบัติงาน ค่าเริ่มต้นข้างต้นใช้กับการโทรครั้งแรกเท่านั้น โหนดน้อยกว่า 16 [ในกระบวนการหลัก].disconnect([callback])
เรียกว่าเมื่อกระบวนการทั้งหมดของผู้ปฏิบัติงานตัดการเชื่อมต่อและปิดการจัดการ [ในกระบวนการหลัก]เพื่อให้คลัสเตอร์มีเสถียรภาพและแข็งแกร่งมากขึ้นโมดูล cluster
เปิดเผยเหตุการณ์มากมาย:
'message'
ทริกเกอร์เมื่อกระบวนการหลักของคลัสเตอร์ได้รับข้อความจากกระบวนการของผู้ปฏิบัติงานใด ๆ'exit'
เมื่อกระบวนการใด ๆ ของผู้ปฏิบัติงานตายโมดูลคลัสเตอร์จะทริกเกอร์เหตุการณ์ 'exit'
Cluster.on ('Exit', (คนงาน, รหัส, สัญญาณ) => { console.log ('คนงาน %D เสียชีวิต ( %s) รีสตาร์ท ... ' Worker.process.pid, สัญญาณ ||); cluster.fork (); })
'listening'
หลังจากโทร listen()
จากกระบวนการของคนงานเมื่อเหตุการณ์ 'listening'
ถูกทริกเกอร์บนเซิร์ฟเวอร์ cluster
ในกระบวนการหลักจะทำให้เกิดเหตุการณ์ 'listening'
cluster.on ('ฟัง', (คนงาน, ที่อยู่) => { console.log ( `ตอนนี้คนงานเชื่อมต่อกับ $ {address.address}: $ {address.port}`); });
'fork'
เมื่อกระบวนการของคนงานใหม่วางไข่โมดูลคลัสเตอร์จะเรียกเหตุการณ์ 'fork'
Cluster.on ('Fork', (คนงาน) => { หมดเวลา [worker.id] = settimeout (errormsg, 2000); });
'setup'
, เรียกใช้ทุกครั้ง .setupPrimary()
เรียกว่าdisconnect
จะถูกเรียกใช้หลังจากช่อง IPC ของกระบวนการทำงานถูกตัดการเชื่อมต่อ เมื่อกระบวนการของคนงานออกจากปกติจะถูกฆ่าหรือตัดการเชื่อมต่อด้วยตนเองcluster.on ('ตัดการเชื่อมต่อ', (คนงาน) => { console.log (`คนงาน #$ {worker.id} ได้ตัดการเชื่อมต่อ '); });
วัตถุ Worker
มีข้อมูลสาธารณะและวิธีการทั้งหมดของกระบวนการคนงาน ในกระบวนการหลักคุณสามารถใช้ cluster.workers
เพื่อรับ ในกระบวนการของคนงานคุณสามารถใช้ cluster.worker
เพื่อรับ
id
กระบวนการ .id
เมื่อกระบวนการของคนงานยังมีชีวิตอยู่นี่คือกุญแจสำคัญที่ดัชนีใน cluster.workers
.process
กระบวนการของคนงานทั้งหมดถูกสร้างขึ้นโดยใช้ child_process.fork()
และวัตถุที่ส่งคืนโดยฟังก์ชั่นนี้จะถูกเก็บไว้เป็น .process
ในกระบวนการของคนงาน process
ทั่วโลกจะถูกเก็บไว้.send(message[, sendHandle[, options]][, callback])
ในกระบวนการหลักสิ่งนี้จะส่งข้อความไปยังกระบวนการของผู้ปฏิบัติงานเฉพาะ มันเหมือนกับ ChildProcess.send()
ในกระบวนการของคนงานสิ่งนี้จะส่งข้อความไปยังกระบวนการหลัก มันเหมือนกับ process.send()
.destroy()
.kill([signal])
ฟังก์ชั่นนี้จะฆ่ากระบวนการของคนงาน ฟังก์ชั่น kill()
ฆ่ากระบวนการของคนงานโดยไม่ต้องรอการตัดการเชื่อมต่อที่สง่างามมันมีพฤติกรรมเช่นเดียวกับ worker.process.kill()
สำหรับความเข้ากันได้แบบย้อนหลังวิธีนี้เป็นนามแฝงกับ worker.destroy()
.disconnect([callback])
ถูกส่งไปยังกระบวนการของคนงานทำให้มันเรียกว่า .disconnect()
ซึ่งจะปิดเซิร์ฟเวอร์ทั้งหมดรอเหตุการณ์ 'close'
บนเซิร์ฟเวอร์เหล่านั้นแล้วถอดช่อง IPC.isConnect()
ฟังก์ชั่นนี้จะส่งคืน true
หากกระบวนการของผู้ปฏิบัติงานเชื่อมต่อกับกระบวนการหลักผ่านช่อง IPC ของมัน false
เป็นอย่างอื่น กระบวนการของคนงานเชื่อมต่อกับกระบวนการหลักของพวกเขาหลังจากการสร้าง.isDead()
ฟังก์ชั่นนี้จะส่งคืน true
หากกระบวนการของผู้ปฏิบัติงานสิ้นสุดลง (เนื่องจากออกหรือรับสัญญาณ) มิฉะนั้นจะส่งคืน false
เพื่อให้คลัสเตอร์มีความเสถียรและแข็งแกร่งยิ่งขึ้นโมดูล cluster
ยังเปิดเผยเหตุการณ์ต่าง ๆ :
'message'
ในกระบวนการของคนงาน'exit'
'exit'
当前worker工作进程
', MessageHandler);
if (Cluster.isprimary) { const worker = cluster.fork (); Worker.on ('ออก', (รหัส, สัญญาณ) => { ถ้า (สัญญาณ) { console.log (`คนงานถูกฆ่าตายโดยสัญญาณ: $ {signal}`); } อื่นถ้า (รหัส! == 0) { console.log (`คนงานออกจากรหัสข้อผิดพลาด: $ {code}`); } อื่น { console.log ('ความสำเร็จของคนงาน!'); - - }
'listening'
โทร listen()
จากกระบวนการของคนงานเพื่อฟังกระบวนการของผู้ปฏิบัติงานปัจจุบันcluster.fork (). on ('ฟัง', (ที่อยู่) => { // กระบวนการของคนงานกำลังฟัง})
disconnect
เมื่อกระบวนการของคนงานออกจากปกติจะถูกฆ่าหรือตัดการเชื่อมต่อด้วยตนเองcluster.fork (). on ('disconnect', () => { // จำกัด การเรียกใช้วัตถุคนงานปัจจุบัน});
ในโหนด การสื่อสารระหว่างกระบวนการ (IPC) ถูกใช้เพื่อตระหนักถึงการสื่อสารระหว่างกระบวนการระหว่างกระบวนการหลักและกระบวนการย่อย .send()
(A.Send หมายถึงการส่ง cluster模块
ไปยังวิธีการส่ง EventEmitter
เพื่อส่งข้อความและฟังเหตุการณ์ message
เพื่อรวบรวมข้อมูล นอกจากนี้ยังเป็นตัวอย่าง การสื่อสารระหว่างกระบวนการ อย่าง
child.send()
child.on('message')
process.on('message')
process.send()
# Cluster.ismaster # cluster.fork () # คลัสเตอร์คนงาน # cluster.workers [id] .on ('ข้อความ', MessageHandler); # cluster.workers [id] .send (); # process.on ('ข้อความ', MessageHandler); # process.send (); const cluster = ต้องการ ('คลัสเตอร์'); const http = ต้องการ ('http'); # กระบวนการหลักถ้า (Cluster.ismaster) { // ติดตามคำขอ HTTP console.log (`หลัก $ {process.pid} กำลังทำงานอยู่); ให้ numreqs = 0; // นับคำขอ ฟังก์ชั่น MessageHandler (msg) { if (msg.cmd && msg.cmd === 'NotifyRequest') { numreqs += 1; - - // เริ่มคนงานและฟังข้อความที่มี NotifyRequest // เริ่มกระบวนการหลายครั้ง (จำนวนคอร์ CPU) // กระบวนการวางไข่คนงาน const numcpus = ต้องการ ('os'). cpus (). ความยาว; สำหรับ (ให้ i = 0; i <numcpus; i ++) { console.log(i) cluster.fork (); - // กระบวนการหลักของผู้ปฏิบัติงานคลัสเตอร์สื่อสารกับกระบวนการเด็กสำหรับ (const id ใน cluster.workers) { // *** ฟังเหตุการณ์จากคลัสเตอร์กระบวนการเด็กคนงาน [ID] .on ('ข้อความ', MessageHandler); // *** ส่ง cluster.workers [id] .send ({ไปยังกระบวนการเด็ก ประเภท: 'MasterToworker', จาก: 'อาจารย์', ข้อมูล: { หมายเลข: math.floor (math.random () * 50) - - - Cluster.on ('Exit', (คนงาน, รหัส, สัญญาณ) => { console.log (`คนงาน $ {worker.process.pid} ตาย '); - } อื่น { # กระบวนการเด็ก // กระบวนการของผู้ปฏิบัติงานสามารถแบ่งปันการเชื่อมต่อ TCP ใด ๆ // ในตัวอย่างนี้มันเป็นเซิร์ฟเวอร์ HTTP // กระบวนการของผู้ปฏิบัติงานมีเซิร์ฟเวอร์ HTTP http.server ((req, res) => { Res.writehead (200); Res.end ('Hello World n'); - - - - แจ้งอาจารย์เกี่ยวกับคำขอ! - - - - - - // ****** ส่ง process.send ({cmd: 'NotifyRequest'}); // ****** ฟัง process.on ('ข้อความ' ฟังก์ชั่น (ข้อความ) { // xxxxxxx - }). ฟัง (8000); console.log (`คนงาน $ {process.pid} เริ่มต้น '); -
การสื่อสารการคืนค่าระหว่างกระบวนการ NODEJS นั้นเกี่ยวข้องกับการส่งข้อความเท่านั้นและไม่ได้ถ่ายโอนวัตถุจริง
ก่อนที่จะส่งข้อความวิธี send()
จะรวบรวมข้อความลงในที่จับและ JSON.stringify
ส่งผ่านช่อง JSON.parse
มี app.listen(port)
ในรหัส
เหตุผลก็คือกระบวนการหลักส่งที่จับของวัตถุบริการที่อยู่ในกระบวนการหลักไปยังกระบวนการย่อยหลายกระบวนการผ่านวิธีการส่ง () ดังนั้นสำหรับแต่ละกระบวนการย่อยหลังจากกู้คืนที่จับพวกเขาจะได้รับวัตถุบริการเดียวกัน เมื่อเครือข่ายเมื่อมีการร้องขอไปยังเซิร์ฟเวอร์บริการกระบวนการจะถูกยึดครองดังนั้นจะไม่มีข้อยกเว้นเมื่อฟังในพอร์ตเดียวกัน
# master.js const fork = ต้องการ ('child_process'). fork; const cpus = ต้องการ ('os'). cpus (); สำหรับ (ให้ i = 0; i <cpus.length; i ++) { const worker = fork ('worker.js'); console.log ('กระบวนการทำงานที่สร้างขึ้น, PID: %S PPID: %S', Worker.pid, process.pid); }
# worker.js const http = ต้องการ ('http'); http.createserver ((req, res) => { Res.end ('ฉันเป็นคนงาน, pid:' + process.pid + ', ppid:' + process.ppid); }) ฟัง (3000);
ในตัวอย่างโค้ดด้านบนเมื่อคอนโซลดำเนินการ
node master.js
คนงานเพียงคนเดียวสามารถฟังพอร์ต 3000 และส่วนที่เหลือจะโยนError: listen EADDRINUSE :::3000
ข้อผิดพลาด
คือ
发送句柄
* http://nodejs.cn/api/child_process.html#child_process_subprocess_send_message_sendhandle_options_callback * ข้อความ * sendhandle - subprocess.send (ข้อความ, sendhandle)
หลังจากที่ช่อง IPC ถูกสร้างขึ้นระหว่างกระบวนการพาเรนต์และเด็กข้อความจะถูกส่งผ่านวิธีการส่งของวัตถุ二个参数sendHandle 就是句柄,可以是TCP套接字、TCP服务器、UDP套接字等
# master.js const fork = ต้องการ ('child_process'). fork; const cpus = ต้องการ ('os'). cpus (); const server = ต้องการ ('net'). createserver (); Server.Listen (3000); process.title = 'node-master' สำหรับ (ให้ i = 0; i <cpus.length; i ++) { const worker = fork ('worker.js'); # ส่งด้ามจับ Worker.Send ('เซิร์ฟเวอร์', เซิร์ฟเวอร์); console.log ('กระบวนการทำงานที่สร้างขึ้น, PID: %S PPID: %S', Worker.pid, process.pid); }
// worker.js ให้คนงาน; process.title = 'node-worker' process.on ('ข้อความ', ฟังก์ชั่น (ข้อความ, sendhandle) { if (message === 'เซิร์ฟเวอร์') { คนงาน = sendhandle; Worker.on ('การเชื่อมต่อ', ฟังก์ชั่น (ซ็อกเก็ต) { console.log ('ฉันเป็นคนงาน, pid:' + process.pid + ', ppid:' + process.ppid) - - })
node master.js
หากคุณเข้าใจ cluster
คุณจะรู้ว่ากระบวนการเด็กถูกสร้างขึ้นผ่าน cluster.fork()
ใน Linux ระบบให้วิธี fork
ดังนั้นทำไมโหนดจึงเลือกใช้ cluster模块
ด้วยตัวเองแทนที่จะใช้วิธีดั้งเดิมของระบบโดยตรง เหตุผลหลักคือสองจุดต่อไปนี้:
กระบวนการส้อมจะตรวจสอบพอร์ตเดียวกันซึ่งจะทำให้เกิดข้อผิดพลาดในการประกอบอาชีพ
ของ
cluster模块
ปัญหาแรกเราตรวจสอบว่ากระบวนการปัจจุบันเป็น worker进程
master进程
ถ้าเป็นเช่นนั้นฟังบนพอร์ตหรือไม่
worker进程
การตอบคำถาม master进程
สอง cluster模块
มีฟังก์ชั่นโหลดบาลานซ์ในตัว Round-Robin อัลกอริทึมการตั้งเวลาสามารถแก้ไขได้ผ่านตัวแปรสภาพแวดล้อม NODE_CLUSTER_SCHED_POLICY
)
เมื่อรหัสผ่านข้อ process.on('uncaughtException', handler)
ที่ไม่ได้จับได้กระบวนการจะออกในเวลานี้ เมื่อกระบวนการของคนงานพบข้อยกเว้นที่ไม่ถูกต้องมันอยู่ในสถานะที่ไม่แน่นอน
- ผู้ปฏิบัติงาน - | uncaughtexception | - - | ----------+ - | + --------------------> + ----------------------- -> | | ทางออก | - - ตาย | - |
เมื่อกระบวนการมีข้อยกเว้นที่ทำให้เกิดความผิดพลาดหรือ oom และถูกฆ่าตายโดยระบบซึ่งแตกต่างจากเมื่อมีข้อยกเว้นที่ไม่ถูกต้องเรายังคงมีโอกาสที่จะให้กระบวนการดำเนินการต่อไป กระบวนการออกจากกระบวนการปัจจุบันโดยตรงและอาจารย์จะแยกคนงานใหม่ทันที
โมดูล child_process ให้ความสามารถในการรับกระบวนการเด็กซึ่งเป็นเพียง执行cmd命令的能力
โดยค่าเริ่มต้น stdin、 stdout 和stderr 的管道会在父Node.js 进程和衍生的子进程之间建立
ท่อเหล่านี้มีความจุ จำกัด (และเฉพาะแพลตฟอร์ม) หากกระบวนการเด็กเกินขีด จำกัด นี้เมื่อเขียนไปยัง stdout และไม่มีการบันทึกเอาต์พุตบล็อกกระบวนการเด็กและรอให้บัฟเฟอร์ท่อรับข้อมูลเพิ่มเติม นี่เป็นพฤติกรรมเดียวกับท่อในเปลือก หากเอาต์พุตไม่ได้ใช้ให้ใช้ตัวเลือก {stdio: 'ละเว้น'}
1.1
บทนำ
const cp = ต้องการ ('child_process'
)
spawn(command, args)
ARGS
fork(modulePath, args)
: ใช้เมื่อคุณต้องการเรียกใช้กระบวนการโหนดเป็นกระบวนการอิสระเพื่อให้การประมวลผลการคำนวณและตัวอธิบายไฟล์ถูกแยกออกจากกระบวนการหลักของโหนดspawn(command, args)
: การประมวลผลบางอย่างใช้ ExecFile (ไฟล์, args [, callback]) เมื่อมี I/OS กระบวนการย่อยจำนวนมากหรือเมื่อกระบวนการมีเอาต์พุตจำนวนมากexecFile(file, args[, callback])
ใช้เมื่อคุณต้องการเรียกใช้โปรแกรมภายนอกเท่านั้น ความเร็วในการดำเนินการอย่างรวดเร็วและค่อนข้างปลอดภัยในการประมวลผลอินพุตของexec(command, options)
execSync
execFileSync
, spawnSync
อีกสามวิธีคือส่วนขยายของ spawn()
ข้อความ กระบวนการหลักยกเว้นช่องทางการสื่อสาร IPC ที่จัดตั้งขึ้นระหว่างทั้งสอง แต่ละกระบวนการมีหน่วยความจำของตัวเองและอินสแตนซ์ V8
ของ
ตัว
เอง
const t = json.parse (process.argv [2]); console.error (`กระบวนการลูก t = $ {json.stringify (t)}`); process.send ({hello: `son pid = $ {process.pid} โปรดให้กระบวนการพ่อ pid = $ {process.ppid} hello`}); process.on ('ข้อความ', (msg) => { console.error (`กระบวนการเด็ก msg = $ {json.stringify (msg)}`); });
# parent.js const {fork} = ต้องการ ('child_process'); สำหรับ (ให้ i = 0; i <3; i ++) { const p = fork ('./ child.js', [json.stringify ({id: 1, ชื่อ: 1})]); p.on ('ข้อความ', (msg) => { console.log (`messsgae จากเด็ก msg = $ {json.stringify (msg)}`,); - P.Send ({Hello: `คำทักทายจากพ่อ $ {process.pid} กระบวนการ ID = $ {i}`}); -
เริ่มต้น parent.js ผ่าน node parent.js
จากนั้นตรวจสอบจำนวนกระบวนการผ่าน ps aux | grep worker.js
CPU Core
นี่คือโหมด Master-Worker คลาสสิก (โหมด Master-Slave)
ในความเป็นจริงการฟอร์กกระบวนการมีราคาแพงและจุดประสงค์ในการคัดลอกกระบวนการคือการใช้ทรัพยากร CPU อย่างเต็มที่ดังนั้น NodeJS จึงใช้วิธีการขับเคลื่อนเหตุการณ์ในเธรดเดียวเพื่อแก้ปัญหาการเกิดขึ้นพร้อมกันสูง
สถานการณ์ที่ใช้งานได้ <br/> ใช้โดยทั่วไปสำหรับสถานการณ์ที่ใช้เวลานานและใช้งานโดยใช้โหนดเช่นการดาวน์โหลดไฟล์
Fork สามารถใช้การดาวน์โหลดแบบหลายเธรด: แบ่งไฟล์ออกเป็นหลายบล็อกจากนั้นแต่ละกระบวนการดาวน์โหลดชิ้นส่วนและในที่สุดก็รวมเข้าด้วย
const cp = ต้องการ ('child_process'); // พารามิเตอร์แรกคือชื่อหรือเส้นทางของไฟล์ที่เรียกใช้งานที่จะเรียกใช้ นี่คือเสียงสะท้อน cp.execfile ('echo', ['hello', 'world'], (err, stdout, stderr) => { ถ้า (err) {console.error (err); console.log ('stdout:', stdout); console.log ('stderr:', stderr);
)
;
ยังคงเรียกว่าภายใน แต่มีขีด จำกัด แคชสูงสุด
const cp = ต้องการ ('child_process'); cp.exec (`cat $ {__ dirname} /messy.txt | sort | uniq`, (err, stdout, stderr) => { console.log (stdout);
)
;
งานเดียว
const cp = ต้องการ ('child_process'); const child = cp.spawn ('echo', ['hello', 'World']); child.on ('ข้อผิดพลาด', console.error); # เอาต์พุตเป็นสตรีมเอาต์พุตไปยังกระบวนการหลัก stdout, คอนโซล child.stdout.pipe (process.stdout); child.stderr.pipe (process.stderr
)
; เส้นทาง const = ต้องการ ('เส้นทาง'); const cat = cp.spawn ('cat', [path.resolve (__ dirname, 'messy.txt')]); const sort = cp.spawn ('sort'); const uniq = cp.spawn ('uniq'); #เอาต์พุตเป็นสตรีม cat.stdout.pipe (sort.stdin); sort.stdout.pipe (uniq.stdin);uniq.stdout.pipe (
process.stdout
);
Spawn กำลังสตรีมมิ่งดังนั้นจึงเหมาะสำหรับงานที่ใช้เวลานานเช่นการดำเนินการติดตั้ง NPM และพิมพ์กระบวนการติดตั้ง
จะถูกเรียกใช้หลังจากกระบวนการสิ้นสุดลงและอินพุตมาตรฐานและกระแสเอาต์พุต (SDTIO) ของ กระบวนการเด็กถูก 'close'
เหตุการณ์นี้แตกต่างจาก exit
เนื่องจากหลายกระบวนการสามารถแชร์สตรีม STDIO เดียวกันได้
พารามิเตอร์:
คำถาม: รหัสต้องมีอยู่หรือไม่?
(ดูเหมือนว่าไม่ได้มาจากความคิดเห็นในรหัส) ตัวอย่างเช่นหากคุณใช้ kill
เพื่อฆ่ากระบวนการเด็กรหัสคืออะไร?
:
รหัสสัญญาณหากกระบวนการเด็กออกไปด้วยตัวเองแล้ว code
คือรหัสออกมิฉะนั้นจะเป็นโมฆะ
หากกระบวนการเด็กถูกยกเลิกผ่านสัญญาณ signal
จะเป็นสัญญาณที่จะสิ้นสุดกระบวนการไม่เช่นนั้นจะเป็นโมฆะ
ของทั้งสองเราจะต้องไม่เป็นโมฆะ
สิ่งที่ควรทราบ :
เมื่อเหตุการณ์ exit
ถูกทริกเกอร์กระแส stdio ของกระบวนการเด็กอาจยังคงเปิดอยู่ (สถานการณ์?) นอกจากนี้ NodeJS ฟังสัญญาณ Sigint และ Sigterm (สายตา JS สามารถทำงานทำความสะอาดได้ในเวลานี้เช่นการปิดฐานข้อมูล ฯลฯ )
SIGINT
: ขัดจังหวะสัญญาณการเลิกจ้างโปรแกรมมักจะออกเมื่อผู้ใช้กด CTRL+C ใช้เพื่อแจ้งกระบวนการเบื้องหน้าเพื่อยุติกระบวนการ
SIGTERM
: สิ้นสุดสัญญาณสิ้นสุดโปรแกรมสัญญาณนี้สามารถบล็อกและประมวลผลได้และมักจะใช้เพื่อกำหนดให้โปรแกรมออกจากปกติ คำสั่งเชลล์ฆ่าสร้างสัญญาณนี้โดยค่าเริ่มต้น หากสัญญาณไม่สามารถยกเลิกได้เราจะลอง Sigkill (บังคับให้เลิกจ้าง)
เมื่อสิ่งต่อไปนี้เกิดขึ้นข้อผิดพลาดจะถูกทริกเกอร์ เมื่อเกิดข้อผิดพลาดออกไปอาจหรือไม่อาจทริกเกอร์ (หัวใจแตกสลาย)
ถูกเรียกใช้เมื่อใช้ process.send()
เพื่อส่งข้อความ
พารามิเตอร์ :
message
sendHandle
ค่า
.disconnected()
; เป็นเท็จ แสดงว่าสามารถรับข้อความจากกระบวนการเด็กหรือส่งข้อความไปยังกระบวนการเด็ก
.Disconnect () : ปิดช่อง IPC ระหว่างกระบวนการหลักและกระบวนการเด็ก เมื่อวิธีการนี้ถูกเรียกเหตุการณ์ disconnect
จะยิง หากกระบวนการเด็กเป็นอินสแตนซ์ของโหนด (สร้างผ่าน child_process.fork ()) ดังนั้น process.disconnect()
สามารถเรียกได้อย่างแข็งขันภายในกระบวนการเด็กเพื่อยุติช่อง IPC
สนองต่อปัญหาแบบเธรด
ถูก
โดยกระบวนการโหนด
รหัส单线程
แต่สภาพแวดล้อมโฮสต์ของ JavaScript ไม่ว่าจะเป็นโหนดหรือเบราว์เซอร์เป็นแบบมัลติเธรด
เหตุใด JavaScript จึงเป็นเธรดเดี่ยว?
ปัญหานี้ต้องเริ่มต้นด้วยเบราว์เซอร์ หลีกเลี่ยงความขัดแย้งของ DOM ในสภาพแวดล้อมของเบราว์เซอร์เธรดการเรนเดอร์ UI และเอ็นจิ้นการดำเนินการของ JS นั้นเป็นเอกสิทธิ์เฉพาะบุคคล
worker_threads
process.env.uv_threadpool_size
ถึง
ismainthread, Parentport WorkerData ด้าย MessageChannel Messageport คนงาน } = ต้องการ ('worker_threads'); ฟังก์ชั่น MainThread () { สำหรับ (ให้ i = 0; i <5; i ++) { const worker = คนงานใหม่ (__ ชื่อไฟล์, {workerData: i}); Worker.on ('ออก', code => {console.log (`หลัก: คนงานหยุดด้วยรหัสออก $ {code}`);}); Worker.on ('ข้อความ', msg => { console.log (`หลัก: รับ $ {msg}`); Worker.postMessage (MSG + 1); - - - function workerThread () { console.log (`คนงาน: WorkerDate $ {WorkerData}`); parentport.on ('ข้อความ', msg => { console.log (`คนงาน: รับ $ {msg}`); - Parentport.postMessage (WorkerData); - if (ismainthread) { MainThread (); } อื่น { WorkerThread (); }
const assert = ต้องการ ('ยืนยัน'); const { คนงาน, MessageChannel Messageport ismainthread, พาเรนพอร์ต } = ต้องการ ('worker_threads'); if (ismainthread) { const worker = คนงานใหม่ (ชื่อไฟล์ __); const subchannel = new MessageChannel (); Worker.PostMessage ({HereisYourport: SubChannel.port1}, [subchannel.port1]); subchannel.port2.on ('ข้อความ', (value) => { console.log ('ได้รับ:', ค่า); - } อื่น { parentport.once ('ข้อความ', (value) => { ยืนยัน (value.hereisyourport อินสแตนซ์ messageport); value.hereisyourport.postMessage ('คนงานกำลังส่งสิ่งนี้'); value.hereisyourport.close (); - -
เป็นหน่วยที่เล็กที่สุดของการจัดสรรทรัพยากรและเธรดเป็นหน่วยที่เล็กที่สุดของการจัดตาราง CPU
IPC (การสื่อสารระหว่างกระบวนการ) คือ进程间通信
ระหว่างกระบวนการ
มีหลายวิธีในการใช้ IPC: ท่อ, คิวข้อความ, semaphores, ซ็อกเก็ตโดเมนและ node.js ถูกนำไปใช้ผ่านท่อ
ในความเป็นจริงกระบวนการหลักจะสร้างช่อง IPC ก่อนและฟัง IPC นี้ก่อนที่จะสร้างกระบวนการเด็กแล้วสร้างกระบวนการเด็ก node_channel_fd)
เป็นข้อมูลอ้างอิงที่สามารถใช้เพื่อระบุทรัพยากรได้
โดยทั่วไปเมื่อเราต้องการตรวจสอบหลายกระบวนการในพอร์ตเดียวเราอาจพิจารณาใช้ตัวแทนกระบวนการหลัก:
อย่างไรก็ตามโซลูชันพร็อกซีนี้จะทำให้การรับสัญญาณแต่ละครั้งและการส่งต่อพร็อกซีใช้ตัวอธิบายไฟล์สองตัวและตัวอธิบายไฟล์ของระบบมี จำกัด
แล้วทำไมต้องใช้มือจับ? เหตุผลก็คือในสถานการณ์จริงการสร้างการสื่อสาร IPC อาจเกี่ยวข้องกับสถานการณ์การประมวลผลข้อมูลที่ send()
มากขึ้น การส่งสัญญาณหลีกเลี่ยงการใช้ตัวอธิบายไฟล์ที่เกิดจากการส่งต่อพร็อกซีที่กล่าวถึงข้างต้น
ต่อไปนี้เป็นประเภทที่จับที่สนับสนุนการส่ง:
หลังจากกระบวนการแม่ของกระบวนการเด็กกำพร้าสร้างกระบวนการเด็กกระบวนการแม่ออก แต่เด็กหนึ่งคนหรือมากกว่านั้น กระบวนการที่สอดคล้องกับกระบวนการหลักยังคงมีชีวิตอยู่ นี่คือภาพประกอบโดยตัวอย่างรหัสต่อไปนี้
# worker.js const http = ต้องการ ('http'); const server = http.createServer ((req, res) => { Res.end ('ฉันเป็นคนงาน, pid:' + process.pid + ', ppid:' + process.ppid); // บันทึกกระบวนการของผู้ปฏิบัติงานปัจจุบัน PID และกระบวนการหลัก PPID PPID - ให้คนงาน; process.on ('ข้อความ', ฟังก์ชั่น (ข้อความ, sendhandle) { if (message === 'เซิร์ฟเวอร์') { คนงาน = sendhandle; Worker.on ('การเชื่อมต่อ', ฟังก์ชั่น (ซ็อกเก็ต) { Server.emit ('การเชื่อมต่อ', ซ็อกเก็ต); - - });
# master.js const fork = require('child_process').fork; const server = require('net').createServer(); server.listen(3000); const worker = fork('worker.js'); worker.send('server', server); console.log('worker process created, pid: %s ppid: %s', worker.pid, process.pid); process.exit(0); // 创建子进程之后,主进程退出,此时创建的worker 进程会成为孤儿进程
控制台进行测试,输出当前工作进程pid 和父进程ppid
由于在master.js 里退出了父进程,活动监视器所显示的也就只有工作进程。
再次验证,打开控制台调用接口,可以看到工作进程5611 对应的ppid 为1(为init 进程),此时已经成为了孤儿进程
守护进程运行在后台不受终端的影响,什么意思呢?
Node.js 开发的同学们可能熟悉,当我们打开终端执行node app.js
开启一个服务进程之后,这个终端就会一直被占用,如果关掉终端,服务就会断掉,即前台运行模式
。
如果采用守护进程进程方式,这个终端我执行node app.js
开启一个服务进程之后,我还可以在这个终端上做些别的事情,且不会相互影响。
创建子进程
在子进程中创建新会话(调用系统函数setsid)
改变子进程工作目录(如:“/” 或“/usr/ 等)
父进程终止
options.detached
为true 可以使子进程在父进程退出后继续运行(系统层会调用setsid 方法),这是第二步操作。// index.js const spawn = require('child_process').spawn; function startDaemon() { const daemon = spawn('node', ['daemon.js'], { cwd: '/usr', detached : true, stdio: 'ignore', - console.log('守护进程开启父进程pid: %s, 守护进程pid: %s', process.pid, daemon.pid); daemon.unref(); - startDaemon()
daemon.js 文件里处理逻辑开启一个定时器每10 秒执行一次,使得这个资源不会退出,同时写入日志到子进程当前工作目录下
/usr/daemon.js const fs = ต้องการ('fs'); const { Console } = require('console'); // custom simple logger const logger = new Console(fs.createWriteStream('./stdout.log'), fs.createWriteStream('./stderr.log')); setInterval(function() { logger.log('daemon pid: ', process.pid, ', ppid: ', process.ppid); }, 1000 * 10);
守护进程实现Node.js 版本源码地址
https://github.com/Q-Angelo/project-training/tree/master/nodejs/simple-daemon
在实际工作中对于守护进程并不陌生,例如PM2、Egg-Cluster 等,以上只是一个简单的Demo 对守护进程做了一个说明,在实际工作中对守护进程的健壮性要求还是很高的,例如:进程的异常监听、工作进程管理调度、进程挂掉之后重启等等,这些还需要去不断思考。
目录是什么?
进程的当前工作目录可以通过process.cwd()
命令获取,默认为当前启动的目录,如果是创建子进程则继承于父进程的目录,可通过process.chdir()
命令重置,例如通过spawn 命令创建的子进程可以指定cwd 选项设置子进程的工作目录。
有什么作用?
例如,通过fs 读取文件,如果设置为相对路径则相对于当前进程启动的目录进行查找,所以,启动目录设置有误的情况下将无法得到正确的结果。还有一种情况程序里引用第三方模块也是根据当前进程启动的目录来进行查找的。
// 示例process.chdir('/Users/may/Documents/test/') // 设置当前进程目录console.log(process.cwd()); // 获取当前进程目录