ความนิยมอย่างมากของคำว่า "อะซิงโครนัส" อยู่ในคลื่นของเว็บ 2.0 ซึ่งกวาดเว็บด้วย JavaScript และ Ajax แต่อะซิงโครนัสนั้นหายากในภาษาการเขียนโปรแกรมระดับสูงที่สุด PHP สะท้อนให้เห็นถึงคุณสมบัตินี้ที่ดีที่สุด: ไม่เพียง แต่บล็อกแบบอะซิงโครนัส แต่ยังไม่ได้ให้หลายเธรด PHP ดำเนินการในลักษณะการปิดกั้นแบบซิงโครนัส ข้อดีดังกล่าวเป็นประโยชน์สำหรับโปรแกรมเมอร์ในการเขียนตรรกะทางธุรกิจตามลำดับ แต่ในแอปพลิเคชันเครือข่ายที่ซับซ้อนการปิดกั้นทำให้มันไม่สามารถเกิดขึ้นพร้อมกันได้มากขึ้น
ทางด้านเซิร์ฟเวอร์ I/O มีราคาแพงมากและ I/O กระจายนั้นมีราคาแพงกว่า เฉพาะเมื่อแบ็กเอนด์สามารถตอบสนองต่อทรัพยากรได้อย่างรวดเร็วประสบการณ์ส่วนหน้าจะดีขึ้น Node.js เป็นแพลตฟอร์มแรกที่ใช้แบบอะซิงโครนัสเป็นวิธีการเขียนโปรแกรมหลักและแนวคิดการออกแบบ มาพร้อมกับ I/O แบบอะซิงโครนัสที่ขับเคลื่อนด้วยเหตุการณ์และเธรดเดี่ยวพวกเขาจะสร้างโหนดของโหนด บทความนี้จะแนะนำว่าโหนดใช้ I/O แบบอะซิงโครนัสอย่างไร
1. แนวคิดพื้นฐาน
"Async" และ "ไม่ปิดกั้น" เสียงเดียวกันและในแง่ของผลลัพธ์ที่แท้จริงทั้งสองบรรลุวัตถุประสงค์ของการขนานกัน แต่จากมุมมองของเคอร์เนลคอมพิวเตอร์ I/O มีเพียงสองวิธีเท่านั้น: การปิดกั้นและไม่ปิดกั้น ดังนั้นอะซิงโครนัส/ซิงโครนัสและการปิดกั้น/การไม่ปิดกั้นจึงเป็นสองสิ่งที่แตกต่างกัน
1.1 การบล็อก I/O และ I/O ที่ไม่ปิดกั้น
คุณลักษณะหนึ่งของการปิดกั้น I/O คือหลังจากการโทรคุณต้องรอจนกว่าการดำเนินการทั้งหมดจะเสร็จสมบูรณ์ที่ระดับเคอร์เนลระบบก่อนที่การโทรจะเสร็จสิ้น การอ่านไฟล์บนดิสก์เป็นตัวอย่างการโทรนี้จะสิ้นสุดลงหลังจากเคอร์เนลระบบเสร็จสิ้นการค้นหาดิสก์อ่านข้อมูลและคัดลอกข้อมูลลงในหน่วยความจำ
การปิดกั้น I/O ทำให้ CPU รอ I/O เสียเวลารอและพลังการประมวลผลของ CPU ไม่สามารถใช้งานได้อย่างเต็มที่ ลักษณะของ I/O ที่ไม่ปิดกั้นคือมันจะกลับมาทันทีหลังจากการโทรและชิ้นเวลา CPU สามารถใช้เพื่อจัดการการทำธุรกรรมอื่น ๆ หลังจากการส่งคืน เนื่องจาก I/O ที่สมบูรณ์ยังไม่เสร็จสิ้นสิ่งที่ส่งคืนทันทีไม่ใช่ข้อมูลที่คาดหวังโดยเลเยอร์ธุรกิจ แต่เพียงสถานะของการโทรปัจจุบัน เพื่อให้ได้ข้อมูลที่สมบูรณ์แอปพลิเคชันจะต้องโทรดำเนินการ I/O ซ้ำ ๆ ซ้ำ ๆ เพื่อยืนยันว่าเสร็จสมบูรณ์หรือไม่ (เช่นการสำรวจ) เทคนิคการเลือกตั้งต้องการสิ่งต่อไปนี้:
1. การอ่าน: การตรวจสอบสถานะ I/O โดยการโทรซ้ำเป็นวิธีการทำงานที่เป็นต้นฉบับและต่ำที่สุด
2.Select: การปรับปรุงการอ่านตัดสินสถานะเหตุการณ์ในตัวอธิบายไฟล์ ข้อเสียคือจำนวนตัวอธิบายไฟล์สูงสุดมี จำกัด
3. โพล: การปรับปรุงในการเลือกโดยใช้รายการที่เชื่อมโยงเพื่อหลีกเลี่ยงขีด จำกัด จำนวนสูงสุด แต่เมื่อมีตัวอธิบายจำนวนมากประสิทธิภาพยังคงต่ำมาก
4.Epoll: หากไม่มีการตรวจสอบเหตุการณ์ I/O ในระหว่างการเลือกตั้งมันจะนอนหลับจนกว่าเหตุการณ์จะเกิดขึ้นและปลุกมัน นี่เป็นกลไกการแจ้งเตือนเหตุการณ์ I/O ที่มีประสิทธิภาพมากที่สุดภายใต้ Linux
การสำรวจความต้องการที่ไม่ปิดกั้น I/O เพื่อให้แน่ใจว่าได้รับข้อมูลเต็มรูปแบบ แต่สำหรับแอปพลิเคชันมันยังสามารถนับได้ว่าเป็นการซิงโครไนซ์ชนิดหนึ่งเพราะยังคงต้องรอให้ I/O กลับมาอย่างสมบูรณ์ ในระหว่างการรอคอย CPU จะใช้ในการสำรวจสถานะของตัวบ่งชี้ไฟล์หรือเพื่อให้จำศีลรอเหตุการณ์ที่จะเกิดขึ้น
1.2 I/O แบบอะซิงโครนัสในอุดมคติและความเป็นจริง
I/O แบบอะซิงโครนัสที่สมบูรณ์แบบควรเป็นแอปพลิเคชันที่เริ่มต้นการโทรที่ไม่ปิดกั้นและสามารถจัดการงานถัดไปได้โดยตรงโดยไม่ต้องผ่านการสำรวจเพียงส่งข้อมูลไปยังแอปพลิเคชันผ่านสัญญาณหรือการโทรกลับหลังจาก I/O เสร็จสมบูรณ์
ในความเป็นจริง I/O แบบอะซิงโครนัสมีการใช้งานที่แตกต่างกันภายใต้ระบบปฏิบัติการที่แตกต่างกัน ตัวอย่างเช่น *Nix Platform ใช้พูลเธรดที่กำหนดเองในขณะที่แพลตฟอร์ม Windows ใช้โมเดล IOCP โหนดให้ libuv เป็นเลเยอร์การห่อหุ้มนามธรรมเพื่อห่อหุ้มการตัดสินความเข้ากันได้ของแพลตฟอร์มและทำให้มั่นใจได้ว่าการใช้งานของ I/O แบบอะซิงโครนัสของโหนดบนและแพลตฟอร์มที่ต่ำกว่านั้นเป็นอิสระ มันควรจะเน้นว่าเรามักจะพูดถึงว่าโหนดเป็นเธรดเดี่ยวซึ่งหมายความว่าการดำเนินการ JavaScript อยู่ในเธรดเดียวและมีพูลเธรดอื่น ๆ ที่เสร็จสิ้นจริง I/O งานภายในโหนด
2. I/O แบบอะซิงโครนัสของโหนด
2.1 Event Loop
รูปแบบการดำเนินการของ Node เป็นลูปเหตุการณ์จริง เมื่อกระบวนการเริ่มต้นโหนดจะสร้างลูปที่ไม่มีที่สิ้นสุดและแต่ละกระบวนการของการดำเนินการลูปร่างกายจะกลายเป็นเห็บ แต่ละกระบวนการทำเครื่องหมายคือการตรวจสอบว่ามีกิจกรรมที่รอการประมวลผลหรือไม่ ถ้าเป็นเช่นนั้นเหตุการณ์และฟังก์ชั่นการโทรกลับที่เกี่ยวข้องจะถูกลบออก หากมีฟังก์ชั่นการโทรกลับที่เกี่ยวข้องพวกเขาจะถูกดำเนินการและจะป้อนลูปถัดไป หากไม่มีการประมวลผลเหตุการณ์เพิ่มเติมออกจากกระบวนการ
2.2 ผู้สังเกตการณ์
มีผู้สังเกตการณ์หลายคนในแต่ละเหตุการณ์ลูปและโดยการถามผู้สังเกตการณ์เหล่านี้เราสามารถพิจารณาได้ว่ามีเหตุการณ์ที่จะดำเนินการหรือไม่ Event Loop เป็นรุ่นผู้ผลิต/ผู้บริโภคทั่วไป ในโหนดเหตุการณ์ส่วนใหญ่มาจากคำขอเครือข่ายไฟล์ I/O ฯลฯ เหตุการณ์เหล่านี้มีผู้สังเกตการณ์ I/O เครือข่ายที่สอดคล้องกันผู้สังเกตการณ์ I/O ไฟล์ ฯลฯ เหตุการณ์ลูปจะนำเหตุการณ์ออกมาจากผู้สังเกตการณ์และประมวลผล
2.3 คำขอวัตถุ
ในระหว่างการเปลี่ยนจาก JavaScript เป็นเคอร์เนลที่ดำเนินการ I/O มีผลิตภัณฑ์ระดับกลางที่เรียกว่าวัตถุคำขอ การใช้วิธีที่ง่ายที่สุดของ fs.open () ใน windows (เปิดไฟล์และรับไฟล์ descriptor ตามเส้นทางและพารามิเตอร์ที่ระบุ) เป็นตัวอย่างจากการเรียก JS ไปยังโมดูลในตัวระบบเรียกผ่าน libuv จริง ๆ แล้วเรียกว่าวิธี UV_FS_OPEN () ในระหว่างกระบวนการโทรจะสร้างวัตถุคำขอ FSREQWRAP และพารามิเตอร์และวิธีการที่ส่งผ่านจากเลเยอร์ JS จะถูกห่อหุ้มในวัตถุคำขอนี้ ฟังก์ชั่นการโทรกลับที่เรากังวลมากที่สุดคือตั้งค่าบนคุณสมบัติ oncompete_sym ของวัตถุนี้ หลังจากห่อวัตถุแล้วให้กดวัตถุ FSREQWRAP ลงในพูลเธรดและรอการดำเนินการ
ณ จุดนี้การโทร JS จะกลับมาทันทีและเธรด JS สามารถดำเนินการต่อไปได้ การดำเนินการ I/O ปัจจุบันกำลังรอการดำเนินการในพูลเธรดซึ่งเสร็จสิ้นขั้นตอนแรกของการโทรแบบอะซิงโครนัส
2.4 เรียกใช้การโทรกลับ
การแจ้งเตือนการโทรกลับเป็นระยะที่สองของ I/O แบบอะซิงโครนัส หลังจากการดำเนินการ I/O ในพูลเธรดเรียกว่าผลลัพธ์ที่ได้จะถูกเก็บไว้และจากนั้น IOCP จะได้รับแจ้งว่าการดำเนินการวัตถุปัจจุบันเสร็จสมบูรณ์และเธรดจะส่งคืนพูลเธรด ในระหว่างการดำเนินการเห็บแต่ละครั้งผู้สังเกตการณ์ I/O ของลูปเหตุการณ์จะเรียกวิธีการที่เกี่ยวข้องเพื่อตรวจสอบว่ามีคำขอที่เสร็จสมบูรณ์ในพูลเธรดหรือไม่ หากมีอยู่วัตถุคำขอจะถูกเพิ่มลงในคิวของผู้สังเกตการณ์ I/O แล้วประมวลผลเป็นเหตุการณ์
3. API ที่ไม่ใช่ I/O แบบอะซิงโครนัส
นอกจากนี้ยังมี API แบบอะซิงโครนัสบางตัวที่ไม่เกี่ยวข้องกับ I/O ในโหนดเช่นตัวจับเวลา setTimeout (), setInterval (), process.nexttick () และ setimmdiate () ที่ดำเนินการทันทีงานแบบอะซิงโครนัส ฯลฯ ซึ่งจะแนะนำสั้น ๆ ที่นี่
3.1 ตัวจับเวลา API
APIs ที่ด้านเบราว์เซอร์ของ settimeout () และ setInterval () มีความสอดคล้องกัน หลักการดำเนินการของพวกเขาคล้ายกับ I/O แบบอะซิงโครนัส แต่พวกเขาไม่ต้องการการมีส่วนร่วมของพูลเธรด I/O ตัวจับเวลาที่สร้างขึ้นโดยการโทรหา API ตัวจับเวลาจะถูกแทรกลงในต้นไม้สีแดงและสีดำภายในผู้สังเกตการณ์ตัวจับเวลา เห็บของ Event Loop แต่ละครั้งจะทำซ้ำวัตถุจับเวลาจากต้นไม้สีแดงและสีดำเพื่อตรวจสอบว่าเวลาเกินเวลาหรือไม่ หากเกินกว่าเหตุการณ์จะเกิดขึ้นและฟังก์ชั่นการโทรกลับจะถูกดำเนินการทันที ปัญหาหลักของตัวจับเวลาคือเวลาเวลาของมันไม่ถูกต้องเป็นพิเศษ (มิลลิวินาทีภายในความอดทน)
3.2 การดำเนินงานแบบอะซิงโครนัส API
ก่อนที่โหนดจะปรากฏขึ้นหลายคนอาจเรียกสิ่งนี้เพื่อที่จะทำงานแบบอะซิงโครนัสทันที:
การคัดลอกรหัสมีดังนี้:
settimeout (function () {
// todo
}, 0);
เนื่องจากลักษณะของลูปเหตุการณ์ตัวจับเวลาไม่ถูกต้องเพียงพอและการใช้ต้นไม้สีแดงและสีดำต้องใช้ตัวจับเวลาและความซับซ้อนของเวลาการทำงานที่หลากหลายคือ O (log (n)) วิธี process.nexttick () จะใส่ฟังก์ชั่นการโทรกลับลงในคิวเท่านั้นและนำออกมาและดำเนินการในรอบถัดไปของเห็บ ความซับซ้อนคือ O (1) และมีประสิทธิภาพมากขึ้น
นอกจากนี้ยังมีวิธี setimmediate () คล้ายกับวิธีการข้างต้นทั้งการล่าช้าการดำเนินการของฟังก์ชันการโทรกลับ อย่างไรก็ตามอดีตมีลำดับความสำคัญสูงกว่าหลังเนื่องจากลูปเหตุการณ์ตรวจสอบผู้สังเกตการณ์ตามลำดับ นอกจากนี้ฟังก์ชั่นการโทรกลับในอดีตจะถูกบันทึกไว้ในอาร์เรย์และแต่ละรอบของเห็บจะเรียกใช้ฟังก์ชันการโทรกลับทั้งหมดในอาร์เรย์ ผลลัพธ์หลังจะถูกบันทึกไว้ในรายการที่เชื่อมโยงและแต่ละรอบของเห็บจะเรียกใช้ฟังก์ชันการโทรกลับหนึ่งเดียวเท่านั้น
4. เซิร์ฟเวอร์ที่ขับเคลื่อนด้วยเหตุการณ์และประสิทธิภาพสูง
ตัวอย่างก่อนหน้านี้แสดงให้เห็นว่าโหนดใช้ I/O แบบอะซิงโครนัสอย่างไร ในความเป็นจริงโหนดยังใช้ I/O แบบอะซิงโครนัสสำหรับการประมวลผลซ็อกเก็ตเครือข่ายซึ่งเป็นพื้นฐานสำหรับโหนดในการสร้างเว็บเซิร์ฟเวอร์ รุ่นเซิร์ฟเวอร์คลาสสิกคือ:
1. ซิงโครนัส: สามารถดำเนินการตามคำขอได้ครั้งละหนึ่งรายการและคำขอที่เหลืออยู่ในสถานะรอคอย
2. ต่อกระบวนการ/คำขอต่อ: เริ่มกระบวนการหนึ่งสำหรับแต่ละคำขอ แต่ทรัพยากรระบบมี จำกัด และไม่มีความสามารถในการปรับขนาด
3. ต่อเธรด/ต่อคำขอ: เริ่มต้นหนึ่งเธรดสำหรับแต่ละคำขอ เธรดมีน้ำหนักเบากว่ากระบวนการ แต่แต่ละเธรดมีหน่วยความจำจำนวนหนึ่ง เมื่อคำขอพร้อมกันขนาดใหญ่มาถึงหน่วยความจำจะหมดเร็ว ๆ นี้
Apache ที่มีชื่อเสียงใช้แบบฟอร์มต่อเธรด/การตอบกลับซึ่งเป็นเหตุผลว่าทำไมจึงเป็นเรื่องยากที่จะรับมือกับการเกิดขึ้นพร้อมกันสูง โหนดจัดการคำขอผ่านวิธีการขับเคลื่อนเหตุการณ์ซึ่งสามารถบันทึกค่าใช้จ่ายของการสร้างและทำลายเธรด ในเวลาเดียวกันระบบปฏิบัติการมีเธรดน้อยลงเมื่อกำหนดเวลาและค่าใช้จ่ายของการสลับบริบทก็ต่ำมาก โหนดสามารถจัดการคำขอในลักษณะที่เป็นระเบียบแม้จะมีการเชื่อมต่อจำนวนมาก
เซิร์ฟเวอร์ที่รู้จักกันดี Nginx ยังละทิ้งวิธีการหลายเธรดและใช้วิธีการที่ขับเคลื่อนด้วยเหตุการณ์เช่นเดียวกับโหนด ตอนนี้ Nginx เป็นวิธีที่ยิ่งใหญ่ในการแทนที่ Apache Nginx เขียนด้วย Pure C และมีประสิทธิภาพสูง แต่เหมาะสำหรับเว็บเซิร์ฟเวอร์เท่านั้นที่ใช้สำหรับการทำพร็อกซ์หรือการปรับสมดุลโหลด ฯลฯ โหนดสามารถสร้างฟังก์ชั่นเดียวกันกับ Nginx และยังสามารถจัดการธุรกิจที่เฉพาะเจาะจงและประสิทธิภาพของตัวเองก็ดีเช่นกัน ในโครงการจริงเราสามารถรวมเข้าด้วยกันเพื่อให้ได้ประสิทธิภาพที่ดีที่สุดของแอปพลิเคชัน