Gestione efficiente delle connessioni HTTP di lunga durata in un'architettura web nginx/gunicorn/django



performance web-services (1)

Sto rispondendo alla mia domanda , forse qualcuno ha una soluzione migliore.

Leggendo un po 'più avanti la documentazione di gunicorn e leggendo un po' di più su eventlet e gevent , penso che Gunicorn risponda perfettamente alla mia domanda. Gunicorn ha un processo di master che gestisce un pool di lavoratori. Ogni lavoratore può essere sincrono (singolo threaded, gestire una richiesta alla volta) o asincrono (ogni worker gestisce effettivamente più richieste quasi simultaneamente).

I lavoratori sincroni sono molto semplici da comprendere e da eseguire il debug e, in caso di errore di un operatore, viene persa solo una richiesta. Ma se un lavoratore è bloccato in una chiamata API esterna a lunga esecuzione, in pratica sta dormendo. Quindi, in caso di carico elevato, tutti i lavoratori potrebbero finire dormendo in attesa di risultati e le richieste finiranno per essere abbandonate.

Quindi la soluzione è di cambiare il tipo di lavoro predefinito da sincrono a asincrono (scegliendo eventlet o gevent, ecco un confronto ). Ora ogni lavoratore esegue più thread verdi , ognuno dei quali è estremamente leggero. Ogni volta che un thread deve attendere alcuni I / O, un altro thread verde riprende l'esecuzione. Questo è chiamato multitasking cooperativo . È molto veloce e molto leggero (un singolo operatore può gestire migliaia di richieste simultanee, se sono in attesa di I / O). Esattamente quello di cui ho bisogno.

Mi stavo chiedendo come dovrei cambiare il mio codice esistente, ma a quanto pare i moduli python standard sono rattoppati da gunicorn all'avvio (in realtà da eventlet o gevent) in modo che tutto il codice esistente possa funzionare senza modifiche e si comporti bene con altri thread.

Ci sono molti parametri che possono essere modificati in gunicorn, ad esempio il numero massimo di client simultanei che utilizzano il parametro worker_connections di worker_connections , il numero massimo di connessioni in sospeso usando il parametro backlog , ecc.

Questo è semplicemente fantastico, inizierò a testare subito!

https://ffff65535.com

Sto lavorando su un servizio web implementato su nginx + gunicorn + django . I client sono applicazioni per smartphone. L'applicazione deve effettuare chiamate a lungo termine a API esterne (Facebook, Amazon S3 ...), quindi il server accoda semplicemente il lavoro a un server di lavoro (utilizzando Celery su Redis ).

Quando possibile, una volta che il server ha messo in coda il lavoro, ritorna immediatamente e la connessione HTTP viene chiusa. Funziona bene e consente al server di sostenere un carico molto elevato.

client                   server                 job server
  .                        |                        |
  .                        |                        |
  |------HTTP request----->|                        |
  |                        |--------queue job------>|
  |<--------close----------|                        |
  .                        |                        |
  .                        |                        |

Ma in alcuni casi, il cliente ha bisogno di ottenere il risultato non appena il lavoro è finito. Sfortunatamente, non è possibile che il server possa contattare il client una volta chiusa la connessione HTTP. Una soluzione sarebbe affidarsi all'applicazione client che esegue il polling del server ogni pochi secondi fino al completamento del lavoro. Vorrei evitare questa soluzione, se possibile, soprattutto perché ostacolerebbe la reattività del servizio, e anche perché caricherebbe il server con molte richieste di polling non necessarie.

In breve, vorrei mantenere attiva la connessione HTTP, senza fare nulla (eccetto forse inviando uno spazio ogni tanto per mantenere in vita la connessione TCP, proprio come fa Amazon S3 ), finché il lavoro non è finito, e server restituisce il risultato.

client                   server                 job server
  .                        |                        |
  .                        |                        |
  |------HTTP request----->|                        |
  |                        |--------queue job------>|
  |<------keep-alive-------|                        |
  |         [...]          |                        |
  |<------keep-alive-------|                        |
  |                        |<--------result---------|
  |<----result + close-----|                        |
  .                        |                        |
  .                        |                        |

Come posso implementare connessioni HTTP di lunga durata in modo efficiente, assumendo che il server sia sottoposto a un carico molto elevato (non è ancora il caso, ma l'obiettivo è quello di essere in grado di sostenere il più alto carico possibile, con centinaia o migliaia di richieste al secondo )?

Lo scaricamento dei lavori effettivi su altri server dovrebbe garantire un basso utilizzo della CPU sul server, ma come posso evitare l'accumulo di processi e l'utilizzo di tutta la RAM del server o le richieste in entrata vengono interrotte a causa di troppe connessioni aperte?

Probabilmente si tratta principalmente di configurare correttamente nginx e gunicorn. Ho letto un po 'di lavoratori asincroni basati su greenlet in gunicorn : la documentazione dice che i lavoratori asincroni sono usati da " Applicazioni che fanno lunghe chiamate di blocco (ie, servizi web esterni) ", questo suona perfetto. Dice anche " In generale, un'applicazione dovrebbe essere in grado di utilizzare queste classi di lavoratori senza modifiche ". Questo suona alla grande. Qualche feedback su questo?

Grazie per i tuoi consigli.