Genel olarak, Node.js 10.000 eşzamanlı isteği nasıl ele alıyor?


394

Node.js tek bir iş parçacığı ve bir kerede yalnızca (engelleme olmayan) işleme istekleri işlemek için bir olay döngü kullandığını anlıyorum. Ama yine de, bu nasıl çalışır, 10.000 eşzamanlı istek söyleyelim. Olay döngüsü tüm istekleri işleyecek mi? Çok uzun sürmez mi?

Çok iş parçacıklı bir web sunucusundan nasıl daha hızlı olabileceğini (henüz) anlayamıyorum. Çok iş parçacıklı web sunucusunun kaynaklarda (bellek, CPU) daha pahalı olacağını anlıyorum, ancak yine de daha hızlı olmaz mı? Muhtemelen yanılıyorum; lütfen bu tek iş parçacığının çok sayıda istekte nasıl daha hızlı olduğunu ve 10.000 gibi çok sayıda talebe hizmet verirken tipik olarak (yüksek düzeyde) ne yaptığını açıklayın.

Ve ayrıca, bu tek iplik o kadar büyük ölçekli olacak mı? Lütfen sadece Node.js'yi öğrenmeye başladığımı unutmayın.


5
Çünkü işin çoğu (verileri taşımak) CPU'yu içermiyor.
OrangeDog

5
Ayrıca, Javascript'i çalıştıran tek bir iş parçacığı olduğu için, iş yaparken başka pek çok iş parçacığı olmadığı anlamına gelmez.
OrangeDog

Bu soru ya çok geniş ya da başka çeşitli soruların kopyası.
OrangeDog


Tek iş parçacığının yanı sıra, Node.js "engellemeyen G / Ç" olarak adlandırılan bir şey yapar. İşte tüm sihir burada yapılır
Anand N

Yanıtlar:


762

Bu soruyu sormanız gerekiyorsa, muhtemelen çoğu web uygulamasının / hizmetinin ne yaptığına aşina değilsinizdir. Muhtemelen tüm yazılımların bunu yaptığını düşünüyorsunuz:

user do an action
       
       v
 application start processing action
   └──> loop ...
          └──> busy processing
 end loop
   └──> send result to user

Ancak, web uygulamaları veya gerçekten arka uç olarak veritabanına sahip herhangi bir uygulama bu şekilde çalışmaz. Web uygulamaları bunu yapar:

user do an action
       
       v
 application start processing action
   └──> make database request
          └──> do nothing until request completes
 request complete
   └──> send result to user

Bu senaryoda, yazılım çalışma zamanının çoğunu veritabanının geri dönmesini bekleyerek% 0 CPU zamanı kullanarak geçirir.

Çok iş parçacıklı ağ uygulaması:

Çok iş parçacıklı ağ uygulamaları yukarıdaki iş yükünü şu şekilde işler:

request ──> spawn thread
              └──> wait for database request
                     └──> answer request
request ──> spawn thread
              └──> wait for database request
                     └──> answer request
request ──> spawn thread
              └──> wait for database request
                     └──> answer request

Böylece iş parçacığı zamanlarının çoğunu% 0 CPU kullanarak veritabanının veri döndürmesini bekler. Bunu yaparken, her bir iş parçacığı vb. İçin tamamen ayrı bir program yığını içeren bir iş parçacığı için gereken belleği ayırmak zorunda kalıyorlardı. Ayrıca, tam bir işlem başlatmak kadar pahalı olmayan bir iş parçacığını başlatmak zorunda kalacaklardır. ucuz.

Tekli iş parçacıklı olay döngüsü

Zamanımızın çoğunu% 0 CPU kullanarak harcadığımız için, neden CPU kullanmadığımızda bazı kodlar çalıştırmıyorsunuz? Bu şekilde, her istek yine de çok iş parçacıklı uygulamalarla aynı miktarda CPU zamanı alacaktır, ancak bir iş parçacığı başlatmamız gerekmez. Yani bunu yapıyoruz:

request ──> make database request
request ──> make database request
request ──> make database request
database request complete ──> send response
database request complete ──> send response
database request complete ──> send response

Uygulamada her iki yaklaşım da veriyi kabaca aynı gecikmeyle döndürür, çünkü işleme hakim olan veritabanı yanıt süresidir.

Buradaki ana avantaj, yeni bir iplik üretmemize gerek olmamasıdır, bu yüzden bizi yavaşlatacak çok fazla malloc yapmamız gerekmez.

Büyü, görünmez diş çekme

Görünüşte gizemli olan şey, yukarıdaki her iki yaklaşımın da iş yükünü "paralel" olarak çalıştırmayı nasıl başardığıdır? Cevap veritabanının iş parçacığı olmasıdır. Dolayısıyla, tek iş parçacıklı uygulamamız aslında başka bir işlemin çok iş parçacıklı davranışından yararlanıyor: veritabanı.

Tek kullanımlık yaklaşımın başarısız olduğu yerler

Verileri döndürmeden önce çok sayıda CPU hesaplaması yapmanız gerekiyorsa, tek iş parçacıklı bir uygulama büyük ölçüde başarısız olur. Şimdi, veritabanı sonucunu işlemek için bir döngü demek istemiyorum. Bu hala çoğunlukla O (n). Demek istediğim, Fourier dönüşümü (örneğin mp3 kodlaması), ışın izleme (3D render) vb.

Tek kullanımlık uygulamaların bir başka özelliği, yalnızca tek bir CPU çekirdeği kullanmasıdır. Yani dört çekirdekli bir sunucunuz varsa (günümüzde nadir değildir) diğer 3 çekirdeği kullanmıyorsunuzdur.

Çok iş parçacıklı yaklaşımın başarısız olduğu yerler

İş parçacığı başına çok fazla RAM ayırmanız gerekiyorsa, çok iş parçacıklı bir uygulama büyük ölçüde başarısız olur. Birincisi, RAM kullanımının kendisi, tek iş parçacıklı bir uygulama kadar çok istekte bulunamayacağınız anlamına gelir. Daha da kötüsü, malloc yavaştır. Çok sayıda nesne tahsis etmek (modern web çerçeveleri için ortaktır), potansiyel olarak tek başına işlenmiş uygulamalardan daha yavaş olabileceğimiz anlamına gelir. Bu, node.js'nin genellikle kazandığı yerdir.

Çok iş parçacığını daha da kötüleştiren bir kullanım örneği, iş parçacığınızda başka bir komut dosyası dili çalıştırmanız gerektiğidir. Öncelikle genellikle bu dil için tüm çalışma zamanını, sonra komut dosyanız tarafından kullanılan değişkenleri yanlış kullanmanız gerekir.

Bu nedenle, C veya go veya java'da ağ uygulamaları yazıyorsanız, iş parçacığı yükü genellikle çok kötü olmaz. PHP veya Ruby sunmak için bir C web sunucusu yazıyorsanız, javascript veya Ruby veya Python'da daha hızlı bir sunucu yazmak çok kolaydır.

Hibrit yaklaşım

Bazı web sunucuları karma bir yaklaşım kullanır. Örneğin Nginx ve Apache2, ağ işleme kodlarını olay döngülerinin iş parçacığı havuzu olarak uygular. Her iş parçacığı, aynı anda tek iş parçacıklı istekleri işleyen bir olay döngüsü çalıştırır, ancak istekler birden çok iş parçacığı arasında yük dengelidir.

Bazı tek iş parçacıklı mimariler de hibrit bir yaklaşım kullanır. Tek bir işlemden birden çok iş parçacığı başlatmak yerine birden çok uygulama başlatabilirsiniz - örneğin, dört çekirdekli bir makinede 4 node.js sunucusu. Sonra iş yükünü süreçler arasında yaymak için bir yük dengeleyici kullanırsınız.

Aslında iki yaklaşım teknik olarak birbirinin aynısıdır.


105
Bu şimdiye kadar okuduğum düğüm için en iyi açıklamadır. Bu "tek iş parçacıklı uygulama aslında başka bir işlemin çok iş parçacıklı davranış: veritabanı." İş yaptı
kenobiwan

istemcinin düğümde birden çok istekte bulunup bulunmadığı, örneğin bir ad alma ve değiştirme gibi ve bu işlemlerin çok sayıda istemci tarafından çok hızlı işlemek için sunucuya itildiğini söyleyin. böyle bir senaryoyu nasıl ele alabilirim?
Remario

3
@CaspainCaldion Çok hızlı ve çok sayıda müşteri ile ne demek istediğinize bağlı. Olduğu gibi, node.js saniyede 1000 isteği yukarı doğru işleyebilir ve hız yalnızca ağ kartınızın hızı ile sınırlıdır. Aynı anda bağlı istemciler değil, saniyede 1000 istek olduğunu unutmayın. 10000 eşzamanlı müşteriyi sorunsuz işleyebilir. Gerçek darboğaz ağ kartıdır.
slebetman

1
@slebetman, gelmiş geçmiş en iyi açıklama. bir şey olsa da, ben bazı bilgileri işleyen ve buna göre sonuç veren bir Makine Öğrenme algoritması varsa, ben Çok dişli yaklaşım veya tek dişli kullanmalısınız
Ganesh Karewad

5
@GaneshKarewad Algoritmalar CPU, hizmetler (veritabanı, REST API vb.) G / Ç kullanır. AI js ile yazılmış bir algoritma ise, başka bir iş parçacığında veya işlemde çalıştırmalısınız. AI başka bir bilgisayarda (Amazon veya Google veya IBM AI hizmetleri gibi) çalışan bir hizmetse, tek iş parçacıklı bir mimari kullanın.
slebetman

46

Düşündüğünüz şey, işlemin çoğunun düğüm olay döngüsünde ele alınmasıdır. Düğüm aslında G / Ç çalışmasını iş parçacıklarına gönderir. G / Ç işlemleri genellikle CPU işlemlerinden daha büyük boyutlar alır, bu yüzden CPU neden bunu bekledi? Ayrıca, işletim sistemi G / Ç görevlerini çok iyi bir şekilde halledebilir. Aslında, Düğüm beklemediğinden çok daha yüksek CPU kullanımı sağlar.

Benzetme yoluyla, NodeJS'yi G / Ç şefleri mutfakta hazırlarken müşteri siparişlerini alan bir garson olarak düşünün. Diğer sistemlerde, bir müşterinin siparişini alan, yemeği hazırlayan, masayı temizleyen ve ancak bir sonraki müşteriye katılan birden fazla şef vardır.


5
Restoran benzetmesi için teşekkürler! Analojileri ve gerçek dünya örneklerini öğrenmeyi çok daha kolay buluyorum.
LaVache

13

Node.js tek bir iş parçacığı ve bir kerede yalnızca (engelleme olmayan) işleme istekleri işlemek için bir olay döngü kullandığını anlıyorum.

Burada söylediklerinizi yanlış anlayabilirim, ancak "her seferinde bir tane" kulağa, olay tabanlı mimariyi tam olarak anlayamayacağınız gibi geliyor.

"Geleneksel" (olay güdümlü olmayan) bir uygulama mimarisinde, süreç etrafta bir şeylerin olmasını beklemek için çok zaman harcıyor. Node.js gibi olay tabanlı bir mimaride süreç sadece beklemekle kalmaz, diğer işlerle de devam edebilir.

Örneğin: bir istemciden bağlantı alırsınız, bunu kabul edersiniz, istek başlıklarını (http durumunda) okursunuz, daha sonra istek üzerinde işlem yapmaya başlarsınız. İstek gövdesini okuyabilirsiniz, genellikle istemciye bazı veriler gönderirsiniz (bu, yalnızca noktayı göstermek için prosedürün kasıtlı olarak basitleştirilmesidir).

Bu aşamaların her birinde, çoğu zaman diğer verilerin diğer uçtan gelmesini beklemek için harcanır - ana JS iş parçacığında işlem için harcanan gerçek zaman genellikle oldukça azdır.

Bir G / Ç nesnesinin durumu (ağ bağlantısı gibi) işlenmesi gerektiği şekilde değiştiğinde (örneğin, bir sokete veri alınır, bir soket yazılabilir hale gelir, vb.) Ana Node.js JS iş parçacığı bir listeyle uyandırılır işlenmesi gereken öğelerin yüzdesi.

İlgili veri yapısını bulur ve bu yapı üzerinde geri aramaların yürütülmesine, gelen verilerin işlenmesine veya bir sokete vb. Daha fazla veri yazmasına neden olan bir olay yayar. İşleme ihtiyacı olan tüm I / O nesneleri yerine getirildikten sonra işlendiğinde, ana Node.js JS iş parçacığı, daha fazla veri bulunduğunu (veya başka bir işlemin tamamlandığını veya zaman aşımına uğradığını) söylenene kadar tekrar bekleyecektir.

Bir sonraki uyandığında, işlenmesi gereken farklı bir G / Ç nesnesinden kaynaklanabilir - örneğin farklı bir ağ bağlantısı. Her seferinde, ilgili geri çağrılar çalıştırılır ve daha sonra başka bir şeyin olmasını bekleyerek uykuya geri döner.

Önemli olan, farklı isteklerin işlenmesinin araya eklenmesi, bir talebi baştan sona işleme koymaması ve ardından bir sonraki işleme geçmemesidir.

Benim düşünceme göre, bunun en büyük avantajı yavaş bir talebin (örneğin 2G veri bağlantısı üzerinden bir cep telefonu cihazına 1MB yanıt verisi göndermeye çalışıyorsunuz veya gerçekten yavaş bir veritabanı sorgusu yapıyorsunuz) kazandı ' t Daha hızlı olanları engelle.

Geleneksel çok iş parçacıklı bir web sunucusunda, işlenen her istek için genellikle bir iş parçacığınız olur ve SADECE bu isteği tamamlanıncaya kadar işleme koyacaktır. Çok yavaş istekleriniz varsa ne olur? Bu istekleri işlemek için bir çok iş parçacığına sahip olursunuz ve diğer istekler (çok hızlı bir şekilde ele alınabilecek çok basit istekler olabilir) arkalarında sıraya alınır.

Node.js dışında başka olay tabanlı sistemler de vardır ve geleneksel modele kıyasla benzer avantajlara ve dezavantajlara sahiptirler.

Olay tabanlı sistemlerin her durumda veya her iş yükünde daha hızlı olduğunu iddia etmem - I / O-bağlı iş yükleri için iyi çalışma eğilimindedirler, CPU-bağlı olanlar için çok iyi değildir.


12

Tek Dişli Olay Döngü Modeli İşleme Adımları:

  • İstemciler Web Sunucusuna istek gönderin.

  • Düğüm JS Web Sunucusu, İstemci İsteklerine hizmet sağlamak için dahili olarak Sınırlı İş Parçacığı havuzunu korur.

  • Düğüm JS Web Sunucusu bu istekleri alır ve bunları bir Kuyruğa yerleştirir. “Olay Kuyruğu” olarak bilinir.

  • Düğüm JS Web Sunucusu dahili olarak “Olay Döngüsü” olarak bilinen bir Bileşene sahiptir. Bu adı almasının nedeni, istekleri almak ve işlemek için belirsiz bir döngü kullanmasıdır.

  • Olay Döngüsü yalnızca Tek Konu kullanır. Node JS Platform İşleme Modelinin ana kalbidir.

  • Olay Döngüsü, Olay Kuyruğuna herhangi bir İstemci İsteği yerleştirilip yerleştirilmediğini denetler. Değilse, gelen istekleri süresiz olarak bekleyin.

  • Evetse, Etkinlik Kuyruğundan bir İstemci İsteği alın

    1. İstemci İsteğinin işleme koyulmasını başlatır
    2. Bu İstemci İsteği, herhangi bir Engelleme G / Ç İşlemini gerektirmiyorsa, her şeyi işleyin, yanıt hazırlayın ve istemciye geri gönderin.
    3. Bu İstemci İsteği, Veritabanı, Dosya Sistemi, Dış Hizmetler ile etkileşim gibi bazı Engelleme G / Ç İşlemlerini gerektiriyorsa, farklı bir yaklaşım izleyecektir.
  • İç İş Parçacığı Havuzundan İş Parçacıklarının kullanılabilirliğini kontrol
  • Bir Konu seçer ve bu İstemci İsteğini o konuya atar.
  • Bu iş parçacığı, bu isteği almaktan, işlemekten, G / Ç işlemlerini engellemekten, yanıt hazırlamaktan ve Olay Döngüsüne geri göndermekten sorumludur.

    @Rambabu Posa tarafından çok güzel açıklanmış daha fazla açıklama için bu bağlantıyı atın


o blog gönderisinde verilen diyagram yanlış görünüyor, bu makalede bahsettikleri şey tamamen doğru değil.
rranj

11

Slebetman yanıtına ekleme: Node.JS10.000 eşzamanlı isteği işleyebildiğini söylediğinizde , bunlar temel olarak engellemeyen isteklerdir, yani bu istekler büyük ölçüde veritabanı sorgusuyla ilgilidir.

Dahili olarak, event loopof Node.JS, thread poolher iş parçacığının a non-blocking requestve olay döngüsünü işlediği a işini yürütür thread pool. İpliklerden biri işi event looptamamladığında, aka olarak bitirdiğine bir sinyal gönderir callback. Event loopardından bu geri aramayı işleyin ve yanıtı geri gönderin.

NodeJS'de yeniyseniz, nextTickolay döngüsünün dahili olarak nasıl çalıştığını anlamak için daha fazla bilgi edinin. Blogları http://javascriptissexy.com adresinde okuyun , JavaScript / NodeJS ile başladığımda benim için gerçekten yardımcı oldular.


2

Kod yürütülürken neler olduğu hakkında daha fazla netlik için slebetman'ın cevabına ekleme .

NodeJs içindeki iç iş parçacığı havuzunda varsayılan olarak yalnızca 4 iş parçacığı vardır. ve tüm istek iş parçacığı havuzundan yeni bir iş parçacığına bağlı olduğu gibi değil, tüm istek yürütülmesi herhangi bir normal istek gibi (herhangi bir engelleme görevi olmadan) gerçekleşir, sadece bir istek uzun çalıştığında veya db gibi ağır bir işlem olduğunda çağrı, bir dosya işlemi veya http isteği görev libuv tarafından sağlanan iç iş parçacığı havuzuna sıraya alınır. Ve nodeJs varsayılan olarak dahili iş parçacığı havuzunda 4 iş parçacığı sağladığından her bir 5. veya bir sonraki eşzamanlı istek bir iş parçacığı serbest olana kadar ve bu işlemler bittiğinde geri arama kuyruğuna itildiğinde bekler. ve olay döngüsü tarafından alınır ve yanıtı geri gönderir.

Şimdi bir kez daha tek bir geri arama kuyruğu değil, birçok kuyruk var başka bir bilgi geliyor.

  1. NextTick kuyruğu
  2. Mikro görev kuyruğu
  3. Zamanlayıcılar Sırası
  4. IO geri arama kuyruğu (İstekler, Dosya ops, db ops)
  5. ES Anket sırası
  6. Faz sırasını veya SetImmediate'i kontrol edin
  7. işleyiciler kuyruğunu kapat

Bir istek geldiğinde, kod, bu geri çağrı sırasına göre yürütülür.

Yeni bir iş parçacığına bağlı bir engelleme isteği olduğunda böyle değildir. Varsayılan olarak yalnızca 4 iş parçacığı vardır. Orada başka bir kuyruk var.

Bir kodda dosya okuma gibi bir engelleme işlemi gerçekleştiğinde, iş parçacığı havuzundan iş parçacığını kullanan bir işlevi çağırır ve işlem tamamlandıktan sonra geri arama ilgili kuyruğa geçirilir ve ardından sırayla yürütülür.

Her şey geri arama türüne göre sıraya alınır ve yukarıda belirtilen sırayla işlenir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.