Molti lavoratori bloccanti VS singoli non bloccanti


9

Supponiamo che ci sia un server HTTP che accetta le connessioni e quindi ha in qualche modo aspettato che le intestazioni vengano inviate completamente. Mi chiedo quale sia il modo più comune di implementarlo e quali sono gli altri pro e contro. Posso solo pensare a questi:

Molti lavoratori bloccanti sono bravi perché:

  • È più reattivo.
  • È più facile introdurre nuove connessioni (i lavoratori le raccolgono da sole anziché dall'esterno in attesa che possa aggiungerlo a un elenco sincronizzato).
  • L'utilizzo della CPU si bilancia automaticamente (senza alcuno sforzo aggiuntivo) man mano che il numero di connessioni aumenta e diminuisce.
  • Minore utilizzo della CPU (i thread bloccati vengono estratti dal ciclo di esecuzione e non richiedono alcuna logica per saltare tra i client).

Il singolo lavoratore non bloccante è valido perché:

  • Usa meno memoria.
  • Meno vulnerabile ai client pigri (che si connettono al server e inviano le intestazioni lentamente o non inviano affatto).

Come probabilmente vedrai, a mio avviso più thread di lavoro sembrano una soluzione complessivamente migliore. L'unico problema è che è più facile attaccare tale server.

Modifica (altre ricerche): alcune risorse che ho trovato sul web ( Migliaia di thread e I / O di blocco - Il vecchio modo di scrivere Java Servers è New again (e molto meglio) di Paul Tyma) suggerisce che l'approccio di blocco è generalmente migliore ma Non so ancora come gestire le connessioni false.

PS Non suggerire di utilizzare alcune librerie o applicazioni per l'attività. Sono più interessato a sapere come funziona effettivamente o potrebbe funzionare piuttosto che farlo funzionare.

PSS Ho suddiviso la logica in più parti e questa gestisce solo l'accettazione delle intestazioni HTTP. Non li elabora.


Ecco, molti anni fa ho scritto un server threaded con I / O di blocco, perché era facile da scrivere. Un collega ha scritto l'altro tipo e ha funzionato egregiamente. Erano due forme della principale offerta di prodotti presso un'azienda in cui lavoravo. Per "client pigri" nello scenario di blocco è possibile avere un timeout sulla ricezione dei dati.

Risposte:


4

Non c'è proiettile d'argento

In pratica dipende ...

tl; dr - soluzione semplice, usa nginx ...

Blocco:

Ad esempio, Apache per impostazione predefinita utilizza uno schema di blocco in cui il processo è biforcuto per ogni connessione. Ciò significa che ogni connessione ha bisogno del proprio spazio di memoria e la quantità di overhead di commutazione di contesto aumenta di più all'aumentare del numero di connessioni. Ma il vantaggio è che, una volta chiusa una connessione, il contesto può essere eliminato e tutta / tutta la memoria può essere facilmente recuperata.

Un approccio multi-thread sarebbe simile in quanto l'overhead del cambio di contesto aumenta con il numero di connessioni ma può essere più efficiente in termini di memoria in un contesto condiviso. Il problema con un tale approccio è che è difficile gestire la memoria condivisa in modo sicuro. Gli approcci per superare i problemi di sincronizzazione della memoria includono spesso il proprio overhead, ad esempio il blocco può bloccare il thread principale su carichi ad uso intensivo di CPU e l'utilizzo di tipi immutabili aggiunge molte copie non necessarie di dati.

AFAIK, l'utilizzo di un approccio multi-processo su un server HTTP bloccante è generalmente preferito perché è più sicuro / semplice gestire / ripristinare la memoria in modo sicuro. La garbage collection diventa un problema quando il recupero della memoria è semplice come interrompere un processo. Per i processi di lunga durata (ovvero un demone) questa caratteristica è particolarmente importante.

Mentre un sovraccarico di cambio di contesto può sembrare insignificante con un piccolo numero di lavoratori, gli svantaggi diventano più rilevanti man mano che il carico aumenta fino a centinaia di migliaia di connessioni simultanee. Nella migliore delle ipotesi, il passaggio dal contesto alla scala O (n) al numero di lavoratori presenti, ma in pratica molto probabilmente è peggio.

Laddove i server che utilizzano il blocco potrebbero non essere la scelta ideale per carichi pesanti di I / O, sono ideali per il lavoro ad alta intensità di CPU e il passaggio dei messaggi è ridotto al minimo.

Non-blocking:

Il non blocco sarebbe qualcosa come Node.js o nginx. Questi sono particolarmente noti per il ridimensionamento a un numero molto maggiore di connessioni per nodo con carico intensivo di I / O. Fondamentalmente, una volta che le persone hanno raggiunto il limite superiore di ciò che i server basati su thread / processi potevano gestire, hanno iniziato a esplorare opzioni alternative. Questo è altrimenti noto come problema C10K (ovvero la capacità di gestire 10.000 connessioni simultanee).

I server asincroni non bloccanti generalmente condividono molte caratteristiche con un approccio multi-thread con blocco in quanto devi stare attento a evitare carichi ad alta intensità di CPU perché non vuoi sovraccaricare il thread principale. Il vantaggio è che l'overhead sostenuto dal cambio di contesto viene sostanzialmente eliminato e con un solo passaggio di messaggi di contesto diventa un problema.

Anche se potrebbe non funzionare per molti protocolli di rete, la natura senza stato HTTP funziona particolarmente bene per le architetture non bloccanti. Utilizzando la combinazione di un proxy inverso e più server HTTP non bloccanti, è possibile identificare e instradare i nodi che presentano carichi pesanti.

Anche su un server che ha un solo nodo, è molto comune che l'installazione includa un server per core del processore per massimizzare la produttività.

Tutti e due:

Il caso d'uso "ideale" sarebbe una combinazione di entrambi. Un proxy inverso nella parte anteriore dedicato alle richieste di routing nella parte superiore, quindi un mix di server bloccanti e non bloccanti. Non bloccante per attività di IO come pubblicazione di contenuto statico, contenuto cache, contenuto html. Blocco per attività pesanti della CPU come codifica di immagini / video, streaming di contenuti, crunching dei numeri, scritture di database, ecc.

Nel tuo caso:

Se stai solo controllando le intestazioni ma non stai effettivamente elaborando le richieste, quello che stai essenzialmente descrivendo è un proxy inverso. In tal caso, seguirei sicuramente un approccio asincrono.

Suggerirei di consultare la documentazione per il proxy inverso incorporato nginx .

A parte:

Ho letto il commento dal link che hai fornito e ha senso che async è stata una scelta sbagliata per la loro particolare implementazione. Il problema può essere riassunto in una dichiarazione.

È emerso che quando si passa da un client all'altro, il codice per il salvataggio e il ripristino di valori / stato era difficile

Stavano costruendo una piattaforma completa. In tal caso, un approccio asincrono significherebbe che dovresti salvare / caricare costantemente lo stato ogni volta che il contesto cambia (ovvero quando viene generato un evento). Inoltre, dal lato SMTP stanno facendo molto lavoro ad alta intensità di CPU.

Sembra che avessero una conoscenza piuttosto scarsa dell'asincrono e, di conseguenza, abbiano formulato molte ipotesi sbagliate.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.