Tumpukan kami di Deepnote terdiri dari beberapa Node. aplikasi js berjalan di pod di kluster Kubernetes. Beberapa minggu yang lalu kami mulai mengalami masalah dengan kegagalan pemeriksaan keaktifan pod karena server Express kami tidak menanggapi waktu pemeriksaan kesehatan
Kami menduga pelakunya mungkin loop peristiwa yang macet
Men-debug loop peristiwa yang macet
Pertanyaannya adalah apa yang menyebabkan loop acara macet. Sayangnya, kami tidak dapat mereproduksi masalah dalam pengembangan, jadi kami memutuskan untuk mencoba membuat profil Node. aplikasi js dalam produksi
Debugger dapat diaktifkan sesuai permintaan, bahkan pada aplikasi yang sudah berjalan. Dan mengumpulkan profil CPU berfungsi bahkan saat loop acara aplikasi macet
Tetapi melakukan ini secara manual berarti seorang insinyur harus merespons dengan sangat cepat terhadap kegagalan pemeriksaan kesehatan, SSH ke pod, mengaktifkan debugger node, dan kemudian menjalankan profiler Chrome DevTools melalui terowongan SSH pada instans produksi
Kami tidak pernah benar-benar dapat menyelesaikan semua langkah ini tepat waktu
Mengotomatiskan pembuatan profil CPU
Kami memutuskan untuk mengotomatiskan proses pembuatan profil CPU. Kami menyadari ini sebenarnya cukup mudah karena Kubernetes mengirimkan sinyal SIGTERM ketika probe liveness pod gagal, menunggu 30 detik, dan baru kemudian mengirim sinyal SIGKILL dan mengakhiri pod. Ini berarti kita memiliki 30 detik untuk memulai debugger, mengumpulkan profil CPU, dan menyimpannya di suatu tempat yang nantinya dapat kita akses
Tetapi bagaimana cara mengotomatiskan pembuatan profil Chrome DevTools?
Sekarang yang kita butuhkan hanyalah skrip yang membungkus perintah startup standar kita — node ./dist/server.js — dalam logika yang menjebak sinyal SIGTERM
#!/bin/bash CPU_PROFILE_DESTINATION_PATH=${CPU_PROFILE_DESTINATION_PATH:=/tmp} # Sigterm handler _term() { DATE=$(date +"%Y-%m-%dT%H.%M.%S%z") HOSTNAME=$(hostname) # run the diat cli tool that will generate the cpuprofile diat cpuprofile -p $NODE_PROCESS_PID --file ${CPU_PROFILE_DESTINATION_PATH}/${HOSTNAME}-${DATE}.cpuprofile kill -TERM "$NODE_PROCESS_PID" 2>/dev/null wait $NODE_PROCESS_PID } # start the actual node process in the background - this is our app node ./dist/server.js & # store the pid of the node process started above NODE_PROCESS_PID=$! echo "Node process is running with PID $NODE_PROCESS_PID, debugger is waiting for SIGTERM" trap _term SIGTERM wait $NODE_PROCESS_PIDDan itu saja. Sekarang setiap kali aplikasi kami berhenti merespons pemeriksaan liveness, kami secara otomatis menangkap profil CPU-nya sehingga nanti kami dapat membukanya di Chrome DevTools dan memeriksa di mana aplikasi kami menghabiskan waktu. Root-penyebab masalah kemudian biasanya sangat mudah
Jika Anda menyukai apa yang Anda lihat di sini dan ingin membantu kami memecahkan tantangan teknik seperti ini, bergabunglah dengan kami di Deepnote
*Kami menyimpan file .cpuprofile di /tmp, yang dalam kasus kami adalah mount NFS, sehingga file akan tetap ada setelah pod dimatikan. Menyimpan profil ke bucket S3 akan menjadi alternatif yang bagus, tetapi di luar cakupan postingan ini
Loop acara inilah yang memungkinkan Node. js untuk melakukan operasi I/O non-pemblokiran — terlepas dari fakta bahwa JavaScript adalah utas tunggal — dengan membongkar operasi ke kernel sistem jika memungkinkan
Karena sebagian besar kernel modern multi-thread, mereka dapat menangani banyak operasi yang dijalankan di latar belakang. Ketika salah satu dari operasi ini selesai, kernel memberi tahu Node. js sehingga callback yang sesuai dapat ditambahkan ke antrean polling untuk akhirnya dieksekusi. Kami akan menjelaskan ini secara lebih rinci nanti di topik ini
Diagram di bawah ini menunjukkan urutan pelaksanaan fase yang berbeda dalam acara Loop
Analogi
Di kedai kopi, seperti Barista (baca EventLoop) menerima pesanan dari setiap pelanggan yang antri (baca IO) dan persiapan kopi, membagi-bagikan kopi, atau mengemasnya dilakukan oleh petugas yang berbeda (baca pekerja alias c ++ apis di
Fase dalam EventLoop
- timer. fase ini mengeksekusi panggilan balik yang dijadwalkan oleh setTimeout() dan setInterval()
- panggilan balik yang tertunda. mengeksekusi callback I/O yang ditangguhkan ke iterasi loop berikutnya
- menganggur, bersiaplah. hanya digunakan secara internal
- pemilihan. mengambil peristiwa I/O baru;
- memeriksa. setImmediate()_ callback dipanggil di sini
- tutup callback. beberapa callback dekat, e. g. node blockEventLoop.js_0
Apa itu Memblokir EventLoop?
Jika utas membutuhkan waktu lama untuk mengeksekusi panggilan balik (Event Loop) atau tugas (Pekerja), itu disebut "diblokir". Saat utas diblokir bekerja atas nama satu klien, utas tidak dapat menangani permintaan dari klien lain mana pun
Mari blokir EventLoop
Mari kita mulai dengan contoh cara memblokir IO
// lets prepare requisitestouch blockEventLoop.js && npm init -y && npm i fastify -s blockEventLoop. js
Jalankan server…
node blockEventLoop.js_Lakukan ping, dan periksa pong
curl 127.0.0.1:3000/ping_Mari tambahkan rute lain dengan tugas yang mahal…
Ayo lari …
// in Terminal 1node blocking_event_loop.js// in Terminal 2
while true; do date; curl 127.0.0.1:3000/ping; sleep 1; echo \n; done;// in Terminal 3
date && curl 127.0.0.1:3000/block-event-loop && date
Segera setelah node blockEventLoop.js1 dipanggil… node blockEventLoop.js2 panggil kios. Periksa juga penggunaan CPU dari proses node
Jika misalnya kita menggunakan rute node blockEventLoop.js2 sebagai healthCheck sederhana di lingkungan k8 sebagai probe liveness, ini akan tampak seperti server yang tidak sehat dan pod dimulai ulang
Apa yang sebenarnya terjadi di sini?
IO yang berjalan lama (di sini fungsi crypto) memblokir loop acara
Sekarang, Mari buka blokir loop acara
Mari tambahkan rute node blockEventLoop.js_4 …
dan lari
// in Terminal 1node blocking_event_loop.js// in Terminal 2
while true; do date; curl 127.0.0.1:3000/ping; sleep 1; echo \n; done;// in Terminal 3
date && curl 127.0.0.1:3000/block-event-loop && date// in Terminal 3
date && curl 127.0.0.1:3000/async-block-event-loop && date
Tidak ada banyak perubahan meskipun kami memanggil fungsi yang dibungkus dengan paradigma node blockEventLoop.js5
Mengapa?
Kami masih memblokir pengulangan acara dengan membuat terlalu banyak tugas mikro
Mari tambahkan rute node blockEventLoop.js_6 …
Ayo lari …
// in Terminal 1node blocking_event_loop.js// in Terminal 2
while true; do date; curl 127.0.0.1:3000/ping; sleep 1; echo \n; done;// in Terminal 3
date && curl 127.0.0.1:3000/unblock-event-loop && date
Sekarang node blockEventLoop.js_2 panggilan berhasil tanpa terhenti dan kami siap menangani permintaan yang masuk. Pemanfaatan CPU juga relatif rendah
Tapi ada peringatan…. butuh waktu lebih lama untuk menyelesaikan node blockEventLoop.js_6 panggilan. Itulah pengorbanan yang kami bayar tetapi kami tidak memblokir panggilan lain
Mengapa memanggil node blockEventLoop.js9 (/ setImmedidate/ process. nextTick) buka blokir loop acara?
Karena urutan di mana curl 127.0.0.1:3000/ping_0 dipanggil dalam loop acara
Sedang
Edit deskripsi
sedang. com
Dalam contoh microtask kami (fungsi curl 127.0.0.1:3000/ping_1) menambahkan lebih banyak tugas ke antrean, dengan loop acara ini tidak mendapat kesempatan untuk menangani permintaan masuk (IO jaringan). Dengan memanggil setTimeout, kami memberikan ruang bernapas untuk menjalankan tugas makro seperti (panggilan IO jaringan) untuk mengeksekusi dengan menyelesaikan loop acara untuk setiap curl 127.0.0.1:3000/ping1 panggilan yang dilakukan
Bagaimana cara menghindari pemblokiran peristiwa-loop?
Dengan menghindari ini
- Operasi asinkron panjang (http, db, file ops. Dll)
Mantan. 1. Sinkronkan file membaca file besar, memblokir panggilan db - Penggunaan pengatur waktu (setImmediate, setTimeout, proses. nextTick ) Ex. Timer Dibuat tetapi tidak dihancurkan
- Soket terbuka/terlalu banyak pembuatan soket
Mantan. 1 Membuat koneksi baru untuk setiap panggilan API untuk komunikasi intra MS => memperbaiki penggunaan header tetap hidup dengan koneksi maksimal
2. Kumpulan koneksi Db - kueri DNS. Mantan. DNS TTL terlalu rendah dan kami sangat sering membutuhkan penyelesaian baru
- Fungsi kripto. Mantan. Fungsi crypto yang sudah lama ditunggu dengan data besar
- Mengompresi fungsi
- Operasi Sinkronisasi besar seperti JSON. parse(…data besar…)
- Operasi regex yang panjang
Bacaan tambahan
- https. //nodejs. org/en/docs/guides/dont-block-the-event-loop/
Loop Peristiwa di Node JS. setTimeout, setImmediate vs proses. nextCentang
Mendokumentasikan perjalanan belajar saya
sedang. com
Loop Peristiwa di Node JS. MacroTasks dan MicroTasks
Dokumen ini berbicara tentang berbagai antrean terkait EventLoop untuk lebih memahami cara terbaik menggunakan Promises, Timer…
sedang. com
Arsitektur NodeJS. Hubungan antara libUV, V8 dan JS
Mendokumentasikan perjalanan belajar saya
sedang. com
Suka mengotak-atik dan belajar melalui coba-coba? . Bergabunglah dengan kami dalam membuat solusi digital yang berpusat pada kehidupan berikutnya