Permintaan intensif Node.js dan CPU

215

Saya sudah mulai bermain-main dengan server HTTP Node.js dan sangat suka menulis Javascript sisi server tetapi ada sesuatu yang menghalangi saya untuk mulai menggunakan Node.js untuk aplikasi web saya.

Saya mengerti keseluruhan konsep I / O async tapi saya agak khawatir tentang kasus tepi di mana kode prosedural sangat intensif CPU seperti manipulasi gambar atau mengurutkan set data besar.

Seperti yang saya pahami, server akan sangat cepat untuk permintaan halaman web sederhana seperti melihat daftar pengguna atau melihat posting blog. Namun, jika saya ingin menulis kode intensif CPU (di bagian belakang admin misalnya) yang menghasilkan gambar atau mengubah ukuran ribuan gambar, permintaan akan sangat lambat (beberapa detik). Karena kode ini bukan async, setiap permintaan yang datang ke server selama beberapa detik itu akan diblokir sampai permintaan lambat saya selesai.

Satu saran adalah menggunakan Pekerja Web untuk tugas-tugas intensif CPU. Namun, saya khawatir pekerja web akan kesulitan untuk menulis kode bersih karena ini bekerja dengan memasukkan file JS yang terpisah. Bagaimana jika kode intensif CPU terletak di metode objek? Agak menyebalkan untuk menulis file JS untuk setiap metode yang intensif CPU.

Saran lain adalah menelurkan proses anak, tetapi itu membuat kodenya semakin tidak terpelihara.

Adakah saran untuk mengatasi hambatan (yang dirasakan) ini? Bagaimana Anda menulis kode berorientasi objek bersih dengan Node.js sambil memastikan tugas berat CPU dijalankan async?

Olivier Lalonde
sumber
2
Olivier, Anda mengajukan pertanyaan identik yang saya pikirkan (baru ke simpul) dan secara khusus berkaitan dengan pemrosesan gambar. Di Jawa saya dapat menggunakan ExecutorService dengan benang tetap dan meneruskan semua pekerjaan pengubahan ukuran dan menunggu untuk menyelesaikan dari semua koneksi, dalam node, saya belum menemukan cara untuk mengacak pekerjaan ke modul eksternal yang membatasi (mari katakanlah) jumlah maksimum operasi simultan menjadi 2 sekaligus. Apakah Anda menemukan cara yang elegan untuk melakukan ini?
Riyad Kalla

Jawaban:

55

Yang Anda butuhkan adalah antrian tugas! Memindahkan tugas jangka panjang Anda dari server web adalah hal yang BAIK. Menyimpan setiap tugas dalam file js "terpisah" mempromosikan modularitas dan penggunaan kembali kode. Ini memaksa Anda untuk berpikir tentang bagaimana menyusun program Anda dengan cara yang akan membuatnya lebih mudah untuk debug dan pemeliharaan dalam jangka panjang. Manfaat lain dari antrian tugas adalah para pekerja dapat ditulis dalam bahasa yang berbeda. Lakukan saja tugas, kerjakan, dan tulis kembali jawabannya.

sesuatu seperti ini https://github.com/resque/resque

Berikut adalah artikel dari github tentang mengapa mereka membangunnya http://github.com/blog/542-introducing-resque

Tim
sumber
35
Mengapa Anda menautkan ke pustaka Ruby dalam pertanyaan yang secara khusus didasarkan pada dunia simpul?
Jonathan Dumaine
1
@ JonathanDumaine Ini adalah implementasi antrian tugas yang baik. Rad kode ruby ​​dan tulis ulang dalam javascript. KEUNTUNGAN!
Simon Stender Boisen
2
Saya penggemar berat tukang gigi untuk ini, pekerja tukang gosok tidak memilih server tukang gosok untuk pekerjaan baru - pekerjaan baru langsung didorong ke pekerja. Sangat responsif
Casey Flynn
1
Bahkan, seseorang telah mengirimnya ke dunia simpul: github.com/technoweenie/coffee-resque
FrontierPsycho
@pacerier, mengapa Anda mengatakan itu? Apa yang Anda usulkan?
luis.espinal
289

Ini adalah kesalahpahaman tentang definisi server web - itu hanya digunakan untuk "berbicara" dengan klien. Tugas berat harus didelegasikan ke program mandiri (yang tentu saja dapat juga ditulis dalam JS).
Anda mungkin mengatakan bahwa itu kotor, tetapi saya jamin proses server web macet dalam mengubah ukuran gambar hanya lebih buruk (bahkan untuk katakanlah Apache, ketika itu tidak memblokir pertanyaan lain). Namun, Anda dapat menggunakan perpustakaan umum untuk menghindari redundansi kode.

EDIT: Saya telah membuat analogi; aplikasi web harus sebagai restoran. Anda memiliki pelayan (server web) dan koki (pekerja). Pelayan berhubungan dengan klien dan melakukan tugas-tugas sederhana seperti menyediakan menu atau menjelaskan jika beberapa hidangan vegetarian. Di sisi lain mereka mendelegasikan tugas yang lebih sulit ke dapur. Karena pelayan hanya melakukan hal-hal sederhana, mereka merespons dengan cepat, dan koki dapat berkonsentrasi pada pekerjaan mereka.

Node.js di sini akan menjadi pelayan tunggal tetapi sangat berbakat yang dapat memproses banyak permintaan pada suatu waktu, dan Apache akan menjadi sekelompok pelayan bodoh yang hanya memproses satu permintaan saja. Jika pelayan Node.js yang satu ini akan mulai memasak, itu akan menjadi bencana langsung. Tetap saja, memasak juga bisa melelahkan bahkan pasokan besar pelayan Apache, tidak menyebutkan kekacauan di dapur dan penurunan responsif secara progresif.

mbq
sumber
6
Nah, dalam lingkungan di mana server web multi-threaded atau multi-proses dan dapat menangani lebih dari satu permintaan bersamaan, sangat umum untuk menghabiskan beberapa detik pada satu permintaan. Orang-orang datang untuk mengharapkan itu. Saya akan mengatakan bahwa kesalahpahaman itu adalah node.js adalah server web "biasa". Menggunakan node.js Anda harus menyesuaikan model pemrograman Anda sedikit, dan itu termasuk mendorong "berjalan lama" bekerja untuk beberapa pekerja yang tidak sinkron.
Thilo
13
Jangan menelurkan proses anak untuk setiap permintaan (yang mengalahkan tujuan node.js). Munculkan pekerja dari dalam permintaan berat Anda saja. Atau rutekan pekerjaan latar belakang Anda yang berat ke sesuatu selain node.js.
Thilo
47
Analogi yang bagus, mbq!
Lance Fisher
6
Ha, saya sangat suka itu. "Node.js: membuat praktik buruk berfungsi buruk"
ethan
7
@ MBB Saya suka analoginya tetapi bisa menggunakan beberapa pekerjaan. Model multithreaded tradisional akan menjadi orang yang sekaligus pelayan dan juru masak. Setelah pesanan diambil, orang itu harus kembali dan memasak makanan sebelum dapat menangani pesanan lain. Model node.js memiliki node sebagai pelayan, dan pekerja web sebagai koki. Para pelayan menangani mengambil / menyelesaikan permintaan sementara para pekerja mengelola tugas-tugas yang lebih intensif waktu. Jika Anda perlu skala yang lebih besar Anda hanya membuat server utama sebuah cluster node dan membalikkan proxy tugas intensif CPU ke server lain yang dibangun untuk pemrosesan milti-threaded.
Evan Plaice
16

Anda tidak ingin kode intensif CPU Anda untuk menjalankan async, Anda ingin menjalankannya secara paralel . Anda perlu menyelesaikan pekerjaan pemrosesan dari utas yang melayani permintaan HTTP. Ini satu-satunya cara untuk mengatasi masalah ini. Dengan NodeJS jawabannya adalah modul cluster, untuk proses melahirkan anak untuk melakukan angkat berat. (AFAIK Node tidak memiliki konsep utas / memori bersama; prosesnya atau tidak sama sekali). Anda memiliki dua opsi untuk bagaimana Anda menyusun aplikasi Anda. Anda bisa mendapatkan solusi 80/20 dengan menelurkan 8 server HTTP dan menangani tugas komputasi intensif secara serempak pada proses anak. Melakukan itu cukup sederhana. Anda dapat mengambil satu jam untuk membacanya di tautan itu. Bahkan, jika Anda hanya merobek kode contoh di bagian atas tautan itu, Anda akan mendapatkan 95% dari perjalanan ke sana.

Cara lain untuk menyusun ini adalah dengan mengatur antrian pekerjaan dan mengirim tugas komputasi besar melalui antrian. Perhatikan bahwa ada banyak overhead yang terkait dengan IPC untuk antrian pekerjaan, jadi ini hanya berguna ketika tugas-tugasnya jauh lebih besar daripada overhead.

Saya terkejut bahwa tidak ada jawaban lain yang menyebutkan cluster.

Latar Belakang: Kode asinkron adalah kode yang ditangguhkan hingga sesuatu terjadi di tempat lain , di mana kode tersebut bangun dan melanjutkan eksekusi. Satu kasus yang sangat umum di mana sesuatu yang lambat harus terjadi di tempat lain adalah I / O.

Kode asinkron tidak berguna jika prosesor Anda yang bertanggung jawab untuk melakukan pekerjaan. Itulah yang terjadi dengan tugas-tugas "komputasi intensif".

Sekarang, sepertinya kode asinkron adalah niche, tetapi sebenarnya itu sangat umum. Kebetulan tidak berguna untuk tugas-tugas intensif komputasi.

Menunggu pada I / O adalah pola yang selalu terjadi di server web, misalnya. Setiap klien yang terhubung ke server Anda mendapatkan soket. Sebagian besar soketnya kosong. Anda tidak ingin melakukan apa pun sampai soket menerima beberapa data, pada titik mana Anda ingin menangani permintaan tersebut. Di bawah tenda server HTTP seperti Node menggunakan perpustakaan acara (libev) untuk melacak ribuan soket terbuka. OS memberi tahu libev, dan kemudian libev memberi tahu NodeJS ketika salah satu soket mendapatkan data, dan kemudian NodeJS menempatkan suatu peristiwa pada antrian acara, dan kode http Anda mulai pada saat ini dan menangani peristiwa satu per satu. Acara tidak dimasukkan ke dalam antrian sampai soket memiliki beberapa data, sehingga acara tidak pernah menunggu data - itu sudah ada untuk mereka.

Server web berbasis peristiwa tunggal berulir masuk akal sebagai paradigma ketika kemacetan menunggu pada sekelompok koneksi soket yang sebagian besar kosong dan Anda tidak ingin seluruh utas atau proses untuk setiap koneksi idle dan Anda tidak ingin polling 250k Anda soket untuk menemukan yang berikutnya yang memiliki data di dalamnya.

tukang batu
sumber
harus jawaban yang benar .... adapun solusi di mana Anda menelurkan 8 cluster, Anda akan membutuhkan 8 core kan? Atau memuat penyeimbang dengan beberapa server.
Muhammad Umer
juga apa cara yang baik untuk belajar tentang solusi ke-2, mengatur antrian. Konsep antrian cukup sederhana, tapi itu bagian pesan antara proses dan antrian yang asing.
Muhammad Umer
Betul sekali. Anda perlu mendapatkan pekerjaan ke inti lain, entah bagaimana. Untuk itu, Anda membutuhkan inti lain.
masonk
Re: antrian. Jawaban praktisnya adalah menggunakan antrian pekerjaan. Ada beberapa tersedia untuk simpul. Saya tidak pernah menggunakan mereka jadi saya tidak bisa membuat rekomendasi. Jawaban rasa ingin tahu adalah bahwa proses pekerja dan proses antrian pada akhirnya akan berkomunikasi melalui soket.
masonk
7

Beberapa pendekatan yang bisa Anda gunakan.

Sebagai catatan @Tim, Anda dapat membuat tugas asinkron yang berada di luar atau sejajar dengan logika penyajian utama Anda. Tergantung pada kebutuhan Anda yang sebenarnya, tetapi bahkan cron dapat bertindak sebagai mekanisme antrian.

WebWorkers dapat bekerja untuk proses async Anda tetapi mereka saat ini tidak didukung oleh node.js. Ada beberapa ekstensi yang memberikan dukungan, misalnya: http://github.com/cramforce/node-worker

Anda masih dapat melakukannya, Anda masih dapat menggunakan kembali modul dan kode melalui mekanisme "wajib" standar. Anda hanya perlu memastikan bahwa pengiriman awal ke pekerja melewati semua informasi yang diperlukan untuk memproses hasilnya.

Toby Hede
sumber
0

Penggunaan child_processadalah salah satu solusi. Tetapi setiap proses anak yang dilahirkan dapat mengkonsumsi banyak memori dibandingkan dengan Gogoroutines

Anda juga dapat menggunakan solusi berbasis antrian seperti kue

neo
sumber