Nodejs mendeteksi pemblokiran loop acara

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_PID

Dan 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

Interpretasi Event Loop (Kiri. JS di browser, Benar. nodejs)

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 requisites 
touch 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 1
node 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.js
1 dipanggil…
node blockEventLoop.js
2 panggil kios. Periksa juga penggunaan CPU dari proses node

Jika misalnya kita menggunakan rute

node blockEventLoop.js
2 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 1
node 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.js
5

Mengapa?

Kami masih memblokir pengulangan acara dengan membuat terlalu banyak tugas mikro

Mari tambahkan rute

node blockEventLoop.js
_6 …

Ayo lari …

// in Terminal 1
node 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.js
9 (/ 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/ping
1 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