Popularitas besar dari istilah "asinkron" berada dalam gelombang Web 2.0, yang menyapu web dengan JavaScript dan Ajax. Tapi asinkron jarang terjadi di sebagian besar bahasa pemrograman tingkat tinggi. PHP terbaik mencerminkan fitur ini: tidak hanya memblokir secara tidak sinkron, tetapi juga tidak menyediakan banyak utas. PHP dieksekusi dengan cara pemblokiran yang sinkron. Keuntungan seperti itu bermanfaat bagi programmer untuk menulis logika bisnis secara berurutan, tetapi dalam aplikasi jaringan yang kompleks, memblokir menyebabkannya gagal menjadi lebih bersamaan.
Di sisi server, I/O sangat mahal, dan I/O terdistribusi lebih mahal. Hanya ketika backend dapat dengan cepat menanggapi sumber daya, pengalaman front-end dapat menjadi lebih baik. Node.js adalah platform pertama yang menggunakan asinkron sebagai metode pemrograman utama dan konsep desain. Disertai dengan I/O asinkron, yang digerakkan oleh peristiwa dan satu threading, mereka membentuk nada node. Artikel ini akan memperkenalkan bagaimana simpul mengimplementasikan I/O asinkron.
1. Konsep Dasar
"Async" dan "non-blocking" terdengar hal yang sama, dan dalam hal hasil aktual, keduanya mencapai tujuan paralelisme. Tetapi dari perspektif Kernel I/O Komputer, hanya ada dua cara: memblokir dan tidak memblokir. Jadi asinkron/sinkron dan memblokir/nonblocking sebenarnya adalah dua hal yang berbeda.
1.1 Memblokir I/O dan I/O Non-Blocking
Salah satu fitur pemblokiran I/O adalah bahwa setelah menelepon, Anda harus menunggu sampai semua operasi selesai pada tingkat kernel sistem sebelum panggilan selesai. Mengambil membaca file pada disk sebagai contoh, panggilan ini berakhir setelah kernel sistem menyelesaikan pencarian disk, membaca data, dan menyalin data ke dalam memori.
Memblokir I/O menyebabkan CPU menunggu I/O, membuang -buang waktu tunggu, dan kekuatan pemrosesan CPU tidak dapat sepenuhnya digunakan. Karakteristik I/O non-blocking adalah bahwa ia akan kembali segera setelah panggilan, dan irisan waktu CPU dapat digunakan untuk menangani transaksi lain setelah kembali. Karena I/O lengkap tidak selesai, apa yang segera dikembalikan bukan data yang diharapkan oleh lapisan bisnis, tetapi hanya status panggilan saat ini. Untuk mendapatkan data lengkap, aplikasi perlu berulang kali memanggil operasi I/O untuk mengonfirmasi apakah itu selesai (mis. Polling). Teknik pemungutan suara membutuhkan yang berikut:
1. Baca: Memeriksa status I/O dengan panggilan berulang adalah metode kinerja paling orisinal dan terendah
2. Select: Perbaikan untuk dibaca, menilai status acara pada deskriptor file. Kerugiannya adalah bahwa jumlah maksimum deskriptor file terbatas.
3.Poll: Perbaikan yang harus dipilih, menggunakan daftar tertaut untuk menghindari batas angka maksimum, tetapi ketika ada banyak deskriptor, kinerjanya masih sangat rendah
4.Epoll: Jika tidak ada acara I/O diperiksa selama pemungutan suara, itu akan tidur sampai peristiwa terjadi dan membangunkannya. Ini adalah mekanisme pemberitahuan acara I/O yang paling efisien di bawah Linux.
Polling memenuhi kebutuhan I/O non-blocking untuk memastikan akuisisi data lengkap, tetapi untuk aplikasi itu masih dapat dihitung sebagai semacam sinkronisasi karena masih perlu menunggu I/O untuk kembali sepenuhnya. Selama menunggu, CPU digunakan untuk melintasi keadaan deskriptor file atau untuk hibernasi menunggu peristiwa terjadi.
1.2 I/O asinkron dalam ideal dan kenyataan
I/O asinkron yang sempurna harus menjadi aplikasi yang memulai panggilan non-blocking, dan dapat secara langsung menangani tugas berikutnya tanpa jajak pendapat, cukup lewati data ke aplikasi melalui sinyal atau panggilan balik setelah I/O selesai.
Pada kenyataannya, I/O asinkron memiliki implementasi yang berbeda di bawah sistem operasi yang berbeda. Misalnya, *Platform NIX mengadopsi kumpulan utas khusus, sementara platform Windows mengadopsi model IOCP. Node memberikan Libuv sebagai lapisan enkapsulasi abstrak untuk merangkum penilaian kompatibilitas platform, dan memastikan bahwa implementasi I/O asinkron dari simpul atas dan platform yang lebih rendah independen. Harus ditekankan bahwa kita sering menyebutkan bahwa node adalah utas tunggal, yang hanya berarti bahwa eksekusi JavaScript ada dalam satu utas, dan ada kumpulan utas lain yang benar-benar menyelesaikan tugas I/O dalam simpul.
2. I/O Node Asynchronous Node
2.1 Loop Acara
Model eksekusi Node sebenarnya adalah loop acara. Ketika proses dimulai, Node menciptakan loop tak terbatas, dan setiap proses mengeksekusi badan loop menjadi kutu. Setiap proses centang adalah untuk memeriksa apakah ada acara yang menunggu untuk diproses. Jika demikian, acara dan fungsi panggilan balik terkait akan dihapus. Jika ada fungsi panggilan balik yang terkait, mereka akan dieksekusi, dan kemudian loop berikutnya akan dimasukkan. Jika tidak ada lagi pemrosesan acara, keluar dari prosesnya.
2.2 Pengamat
Ada beberapa pengamat di setiap loop peristiwa, dan dengan bertanya kepada pengamat ini, kita dapat menentukan apakah ada peristiwa yang akan diproses. Loop acara adalah model produsen/konsumen yang khas. Dalam node, acara terutama berasal dari permintaan jaringan, file I/O, dll. Acara ini memiliki pengamat I/O jaringan yang sesuai, pengamat I/O file, dll. Loop acara mengeluarkan acara dari pengamat dan memprosesnya.
2.3 Permintaan Objek
Selama transisi dari JavaScript ke kernel yang melakukan operasi I/O, ada produk perantara yang disebut objek permintaan. Mengambil metode fs.open () paling sederhana di Windows (buka file dan dapatkan deskriptor file sesuai dengan jalur dan parameter yang ditentukan) sebagai contoh, dari panggilan JS ke modul bawaan, sistem panggilan melalui libuv sebenarnya disebut metode UV_FS_OPEN (). Selama proses panggilan, objek permintaan FSREQWRAP dibuat, dan parameter dan metode yang ditularkan dari lapisan JS dienkapsulasi dalam objek permintaan ini. Fungsi callback yang paling kami khawatirkan diatur pada properti OnCompete_Sym dari objek ini. Setelah objek dibungkus, dorong objek FSREQWRAP ke dalam kumpulan utas dan tunggu eksekusi.
Pada titik ini, panggilan JS segera kembali, dan utas JS dapat terus melakukan operasi selanjutnya. Operasi I/O saat ini sedang menunggu eksekusi di kumpulan utas, yang menyelesaikan tahap pertama panggilan asinkron.
2.4 Jalankan panggilan balik
Pemberitahuan Callback adalah fase kedua dari I/O asinkron. Setelah operasi I/O di kumpulan utas dipanggil, hasil yang diperoleh akan disimpan, dan kemudian IOCP diberitahu bahwa operasi objek saat ini telah selesai dan utas mengembalikan kumpulan utas. Selama setiap eksekusi centang, pengamat I/O dari loop acara akan memanggil metode yang relevan untuk memeriksa apakah ada permintaan yang telah selesai di kumpulan utas. Jika ada, objek permintaan akan ditambahkan ke antrian pengamat I/O dan kemudian diproses sebagai suatu peristiwa.
3. API non-I/O Asinkron
Ada juga beberapa API asinkron yang tidak terkait dengan I/O dalam simpul, seperti timer setTimeout (), setInterval (), Process.nexttick () dan setimmdiate () yang segera menjalankan tugas secara asinkron, dll., Yang akan diperkenalkan secara singkat di sini.
3.1 API Timer
API di sisi browser dari setTimeout () dan setInterval () konsisten. Prinsip implementasi mereka mirip dengan I/O asinkron, tetapi mereka tidak memerlukan partisipasi kumpulan utas I/O. Pengatur waktu yang dibuat dengan memanggil API timer akan dimasukkan ke dalam pohon merah dan hitam di dalam pengamat timer. Setiap centang Loop Acara akan mengulangi objek timer dari pohon merah dan hitam untuk memeriksa apakah waktu telah melebihi. Jika melebihi, suatu acara akan dibentuk dan fungsi panggilan balik akan segera dieksekusi. Masalah utama dengan timer adalah bahwa waktu waktu tidak terlalu akurat (milidetik, dalam toleransi).
3.2 API Eksekusi Tugas Asinkron
Sebelum node muncul, banyak orang mungkin menyebutnya untuk segera melakukan tugas secara tidak sinkron:
Salinan kode adalah sebagai berikut:
setTimeout (function () {
// todo
}, 0);
Karena karakteristik loop peristiwa, timer tidak cukup akurat, dan penggunaan pohon merah dan hitam membutuhkan penggunaan timer, dan kompleksitas berbagai waktu operasi adalah O (log (n)). Metode proses.nexttick () hanya akan menempatkan fungsi panggilan balik ke dalam antrian dan mengeluarkannya dan menjalankannya di babak kutu berikutnya. Kompleksitasnya adalah O (1) dan lebih efisien.
Selain itu, ada metode setimmediate () yang mirip dengan metode di atas, keduanya menunda eksekusi fungsi callback. Namun, yang pertama memiliki prioritas lebih tinggi daripada yang terakhir, karena loop peristiwa memeriksa pengamat secara berurutan. Selain itu, fungsi callback sebelumnya disimpan dalam array, dan setiap putaran kutu akan menjalankan semua fungsi callback dalam array; Hasil terakhir disimpan dalam daftar tertaut, dan setiap putaran centang hanya akan menjalankan satu fungsi panggilan balik.
4. Server yang didorong oleh acara dan berkinerja tinggi
Contoh sebelumnya menggambarkan bagaimana simpul mengimplementasikan I/O asinkron. Faktanya, Node juga menerapkan I/O asinkron untuk pemrosesan soket jaringan, yang juga merupakan dasar bagi Node untuk membangun server web. Model server klasik adalah:
1. Sinkron: Hanya satu permintaan yang dapat diproses sekaligus, dan sisa permintaan dalam keadaan menunggu
2. Per Proses/Per Permintaan: Mulai satu proses untuk setiap permintaan, tetapi sumber daya sistem terbatas dan tidak memiliki skalabilitas.
3. Per utas/per permintaan: Mulai satu utas untuk setiap permintaan. Utas lebih ringan dari proses, tetapi setiap utas menempati sejumlah memori. Ketika permintaan bersamaan besar tiba, memori akan segera habis.
Apache yang terkenal mengadopsi bentuk per-thread/per-permintaan, itulah sebabnya sulit untuk mengatasi konkurensi yang tinggi. Node menangani permintaan melalui metode yang digerakkan oleh peristiwa, yang dapat menyimpan overhead membuat dan menghancurkan utas. Pada saat yang sama, sistem operasi memiliki utas yang lebih sedikit saat menjadwalkan tugas, dan biaya pengalihan konteks juga sangat rendah. Node dapat menangani permintaan secara tertib bahkan dengan sejumlah besar koneksi.
Server Nginx yang terkenal juga meninggalkan metode multi-threading dan mengadopsi metode yang sama dengan peristiwa yang sama dengan node. Sekarang Nginx berada dalam cara besar untuk menggantikan Apache. Nginx ditulis dalam C murni dan memiliki kinerja tinggi, tetapi hanya cocok untuk server web, digunakan untuk proksi terbalik atau penyeimbangan beban, dll. Node dapat membangun fungsi yang sama seperti Nginx, dan juga dapat menangani berbagai bisnis tertentu, dan kinerjanya sendiri juga bagus. Dalam proyek aktual, kami dapat menggabungkannya untuk mencapai kinerja terbaik dari aplikasi.