Le applicazioni web sono ormai onnipresenti e tra tutti i protocolli di trasporto, l'HTTP ne rappresenta la maggior parte. Durante lo studio delle sfide nello sviluppo delle applicazioni web, molti prestano poca attenzione al sistema operativo su cui queste applicazioni vengono effettivamente eseguite. La separazione tra sviluppo (Dev) e operazioni (Ops) ha solo aggravato la situazione. Tuttavia, con la diffusione della cultura DevOps, gli sviluppatori iniziano a prendersi la responsabilità di far funzionare le proprie applicazioni nel cloud, quindi risulta molto utile conoscere a fondo il backend del sistema operativo. Questo è particolarmente vantaggioso se si cerca di implementare un sistema per migliaia o decine di migliaia di connessioni simultanee.
Le limitazioni nei servizi web sono molto simili a quelle riscontrate in altre applicazioni. Che si tratti di bilanciatori di carico o server di database, tutte queste applicazioni presentano problemi analoghi in ambienti ad alte prestazioni. Comprendere queste limitazioni fondamentali e i modi per superarle consente di valutare le prestazioni e la scalabilità delle vostre applicazioni web.
Scrivo questa serie di articoli in risposta alle domande di giovani sviluppatori che desiderano diventare architetti di sistemi ben informati. È impossibile comprendere chiaramente i metodi di ottimizzazione delle applicazioni Linux senza immergersi nelle basi e capire come funzionano a livello di sistema operativo. Sebbene esistano vari tipi di applicazioni, in questo ciclo voglio esplorare le applicazioni di rete, piuttosto che quelle desktop come browser o editor di testi. Questo materiale è rivolto a sviluppatori e architetti che desiderano capire come funzionano i programmi Linux o Unix e come strutturarli per alte prestazioni.
Linux è un sistema operativo server e, più spesso, le vostre applicazioni funzionano proprio su questo sistema operativo. Anche se parlo di «Linux», per la maggior parte del tempo potete presumere con sicurezza che mi riferisca a tutti i sistemi operativi Unix-like in generale. Tuttavia, non ho testato il codice accompagnatorio su altri sistemi. Quindi, se siete interessati a FreeBSD o OpenBSD, i risultati potrebbero variare. Quando provo qualcosa di specifico per Linux, lo indico.
Sebbene tu possa utilizzare le conoscenze acquisite per creare un'applicazione da zero, e questa possa risultare straordinariamente ottimizzata, è meglio evitarlo. Se scrivi un nuovo server web in C o C++ per un'applicazione aziendale della tua organizzazione, probabilmente sarà il tuo ultimo giorno di lavoro. Tuttavia, conoscere la struttura di queste applicazioni ti aiuterà a scegliere programmi già esistenti. Potrai confrontare sistemi basati su processi con sistemi basati su thread, oltre che con quelli basati su eventi. Comprenderai e valuterai perché Nginx funziona meglio di Apache httpd e perché un'applicazione Python basata su Tornado può gestire più utenti rispetto a un'applicazione Python basata su Django.
ZeroHTTPd: strumento di apprendimento
— un server web che ho scritto da zero in C come strumento didattico. Non ha dipendenze esterne, incluso l'accesso a Redis. Eseguiamo procedure Redis personalizzate. Maggiori dettagli di seguito.
Anche se potremmo discutere a lungo sulla teoria, non c'è niente di meglio che scrivere del codice, eseguirlo e confrontare tutte le architetture server. Questo è il metodo più visibile. Perciò, scriveremo un semplice server web ZeroHTTPd, applicando ogni modello: basato su processi, thread ed eventi. Testiamo ciascuno di questi server e vediamo come funzionano in confronto l'uno con l'altro. ZeroHTTPd è implementato in un unico file C. Il server basato su eventi include , una straordinaria implementazione di una tabella hash, fornita in un solo file di intestazione. Negli altri casi non ci sono dipendenze, per non complicare il progetto.
Il codice contiene numerosi commenti per facilitare la comprensione. Essendo un semplice server web in poche righe di codice, ZeroHTTPd rappresenta anche un framework minimale per lo sviluppo web. Ha funzionalità limitate, ma è in grado di servire file statici e pagine "dinamiche" molto semplici. Posso dire che ZeroHTTPd è adatto per imparare a creare applicazioni Linux ad alte prestazioni. Fondamentalmente, la maggior parte dei servizi web attende le richieste, le verifica e le elabora. Proprio questo farà ZeroHTTPd. È uno strumento formativo, non per l'uso in produzione. Non è forte nella gestione degli errori e difficilmente può vantare le migliori pratiche di sicurezza (oh sì, ho usato strcpy) o trucchi complicati del linguaggio C. Ma spero che svolga bene il suo compito.

Pagina principale di ZeroHTTPd. Può servire vari tipi di file, comprese le immagini.
Applicazione per il libro degli ospiti
Le moderne applicazioni web non si limitano solitamente a file statici. Hanno interazioni complesse con vari database, cache, ecc. Pertanto, creeremo una semplice applicazione web chiamata «Libro degli ospiti», in cui i visitatori possono lasciare messaggi con i loro nomi. Nel libro degli ospiti vengono conservati i messaggi lasciati in precedenza. C'è anche un contatore dei visitatori nella parte inferiore della pagina.

Applicazione web «Libro degli ospiti» ZeroHTTPd
Il contatore delle visite e il registro della guestbook sono memorizzati in Redis. Sono state implementate procedure proprie per la comunicazione con Redis, che non dipendono da librerie esterne. Non sono un grande sostenitore dell'implementazione di codice homemade quando ci sono soluzioni pubbliche e ben testate. Ma l'obiettivo di ZeroHTTPd è studiare le prestazioni di Linux e l'accesso a servizi esterni, mentre la gestione delle richieste HTTP influisce notevolmente sulle prestazioni. Dobbiamo avere un controllo completo sulle comunicazioni con Redis in ciascuna delle nostre architetture server. In un'architettura utilizziamo chiamate bloccanti, in altre procedure basate su eventi. L'uso di una libreria client Redis esterna non fornisce tale controllo. Inoltre, il nostro piccolo client Redis esegue solo alcune funzioni (recupero, impostazione e incremento della chiave; recupero e aggiunta a un array). Inoltre, il protocollo Redis è straordinariamente elegante e semplice. Non c'è nemmeno bisogno di impararlo appositamente. Il fatto che tutta la logica del protocollo sia realizzata in circa cento righe di codice dimostra quanto sia ben progettato.
Nell'immagine successiva sono mostrati i comportamenti dell'applicazione quando il cliente (browser) effettua una richiesta /guestbookURL.

Meccanismo di funzionamento dell'applicazione del guestbook
Quando è necessario generare la pagina del guestbook, viene effettuata una chiamata al file system per leggere il template in memoria e tre chiamate di rete a Redis. Il file template contiene la maggior parte del contenuto HTML per la pagina nello screenshot sopra. Ci sono anche speciali segnaposto per la parte dinamica del contenuto: post e contatore di visitatori. Otteniamo questi dati da Redis, li inseriamo nella pagina e restituiamo contenuti completamente formati al cliente. La terza chiamata a Redis può essere evitata, poiché Redis restituisce il nuovo valore della chiave all'aumento. Tuttavia, per il nostro server con architettura asincrona basata su eventi, molte chiamate di rete sono una buona sfida a scopi didattici. Pertanto, ignoriamo il valore restituito da Redis riguardo al numero di visitatori e lo richiediamo con una chiamata separata.
Architetture server di ZeroHTTPd
Costruiamo sette versioni di ZeroHTTPd con funzionalità identiche, ma architetture diverse:
- Iterativa
- Server fork (un processo figlio per richiesta)
- Server pre-fork (forking processes in advance)
- Server with execution threads (one thread per request)
- Server with pre-created threads
- Architecture based on
poll() - Architecture based on
epoll
We measure the performance of each architecture by loading the server with HTTP requests. However, when comparing architectures with a high degree of parallelism, the number of requests increases. We conduct the tests three times and calculate the average.
Metodologia di testing

Setup for load testing ZeroHTTPd
It is important that during the tests, all components do not run on the same machine. In this case, the OS incurs additional scheduling overhead as components compete for CPU. Measuring the operating system overhead with each server architecture chosen is one of the most important goals of this exercise. Adding more variables will be detrimental to the process. Therefore, the configuration shown above works best.
What each of these servers does
- load.unixism.net: here we run
ab, the Apache Benchmark utility. It generates the required load to test our server architectures. - nginx.unixism.net: a volte desideriamo eseguire più istanze di un programma server. Per questo il server Nginx, con le impostazioni appropriate, funge da bilanciatore di carico per ab i nostri processi server.
- zerohttpd.unixism.net: qui eseguiamo i nostri programmi server su sette architetture diverse, una alla volta.
- redis.unixism.net: su questo server è in esecuzione un demone Redis, dove vengono memorizzati i messaggi nel guestbook e il conteggio dei visitatori.
Tutti i server operano su un singolo core del processore. L'idea è di valutare le massime prestazioni di ciascuna architettura. Poiché tutti i programmi server vengono testati su un'unica attrezzatura, questo rappresenta il livello base per il loro confronto. La mia impostazione di test consiste in server virtuali affittati da Digital Ocean.
Cosa misuriamo?
Possiamo misurare diversi parametri. Valutiamo le prestazioni di ciascuna architettura in questa configurazione caricando i server con richieste a diversi livelli di parallelismo: il carico aumenta da 20 a 15.000 utenti simultanei.
Risultati dei test
Nel diagramma seguente viene mostrata la performance dei server su diverse architetture a vari livelli di parallelismo. L'asse y rappresenta il numero di richieste al secondo, mentre l'asse x indica le connessioni parallele.



Di seguito la tabella con i risultati.
richieste al secondo
parallelismo
iterativo
fork
pre-fork
threading
pre-threading
poll
epoll
20
7
112
2100
1800
2250
1900
2050
50
7
190
2200
1700
2200
2000
2000
100
7
245
2200
1700
2200
2150
2100
200
7
330
2300
1750
2300
2200
2100
300
–
380
2200
1800
2400
2250
2150
400
–
410
2200
1750
2600
2000
2000
500
–
440
2300
1850
2700
1900
2212
600
–
460
2400
1800
2500
1700
2519
700
–
460
2400
1600
2490
1550
2607
800
–
460
2400
1600
2540
1400
2553
900
–
460
2300
1600
2472
1200
2567
1000
–
475
2300
1700
2485
1150
2439
1500
–
490
2400
1550
2620
900
2479
2000
–
350
2400
1400
2396
550
2200
2500
–
280
2100
1300
2453
490
2262
3000
–
280
1900
1250
2502
ampia varietà
2138
5000
–
ampia varietà
1600
1100
2519
–
2235
8000
–
–
1200
ampia varietà
2451
–
2100
10 000
–
–
ampia varietà
–
2200
–
2200
11 000
–
–
–
–
2200
–
2122
12 000
–
–
–
–
970
–
1958
13 000
–
–
–
–
730
–
1897
14 000
–
–
–
–
590
–
1466
15 000
–
–
–
–
532
–
1281
Dal grafico e dalla tabella si evince che oltre 8000 richieste simultanee rimangono solo due contendenti: pre-fork ed epoll. Con l'aumento del carico, il server basato su poll funziona peggio di quello in threading. L'architettura con creazione anticipata di thread costituisce una valida concorrenza per epoll: questo dimostra quanto bene il kernel Linux pianifichi un gran numero di thread.
Codice sorgente di ZeroHTTPd
Codice sorgente di ZeroHTTPd . Per ogni architettura c'è una cartella separata.
ZeroHTTPd
│
├── 01_iterative
│ ├── main.c
├── 02_forking
│ ├── main.c
├── 03_preforking
│ ├── main.c
├── 04_threading
│ ├── main.c
├── 05_prethreading
│ ├── main.c
├── 06_poll
│ ├── main.c
├── 07_epoll
│ └── main.c
├── Makefile
├── public
│ ├── index.html
│ └── tux.png
└── templates
└── guestbook
└── index.htmlOltre alle sette directory per tutte le architetture, nella cartella principale ci sono altre due directory: public e templates. Nella prima si trova il file index.html e l'immagine del primo screenshot. È possibile inserire altri file e cartelle, e ZeroHTTPd dovrebbe servire questi file statici senza problemi. Se il percorso nel browser corrisponde al percorso nella cartella public, ZeroHTTPd cerca il file index.html in questa directory. Il contenuto per il libro degli ospiti viene generato dinamicamente. Ha solo una homepage, e il suo contenuto si basa sul file ‘templates/guestbook/index.html’. In ZeroHTTPd è facile aggiungere pagine dinamiche per l'espansione. L'idea è che gli utenti possano aggiungere modelli in questa directory ed espandere ZeroHTTPd secondo necessità.
Per costruire tutti e sette i server, esegui make all dalla cartella principale — e tutte le build appariranno in questa directory. I file eseguibili cercano le cartelle public e templates nella directory da cui vengono avviati.
Linux API
Per comprendere le informazioni in questo ciclo di articoli, non è necessario avere una buona conoscenza dell'API di Linux. Tuttavia, consiglio di approfondire l'argomento; ci sono molte risorse di riferimento disponibili online. Anche se toccheremo diverse categorie di API di Linux, ci concentreremo principalmente su processi, thread, eventi e sullo stack di rete. Oltre a libri e articoli sull'API di Linux, è utile consultare anche le pagine man per le chiamate di sistema e le funzioni di libreria utilizzate.
Prestazioni e scalabilità
Una nota sulla performance e la scalabilità. Teoricamente, non c'è alcuna connessione tra di loro. Può esistere un servizio web che funziona molto bene, con un tempo di risposta nell'ordine di pochi millisecondi, ma che non è affatto scalabile. Allo stesso modo, può esserci un'applicazione web che funziona male, richiedendo diversi secondi per rispondere, ma che scala per gestire decine di migliaia di utenti simultanei. Tuttavia, una combinazione di alta performance e scalabilità è molto potente. Le applicazioni ad alte prestazioni, in generale, utilizzano le risorse in modo parsimonioso e, in questo modo, servono più utenti simultanei su un server, riducendo i costi.
Compiti CPU e I/O
Infine, ci sono sempre due tipi di problemi nelle computazioni: per I/O e CPU. Ricevere richieste via internet (input/output di rete), gestire file (input/output di rete e disco), comunicare con il database (input/output di rete e disco) — tutto ciò è considerato operazioni I/O. Alcune richieste al database possono caricare un po' la CPU (ordinamento, calcolo della media di un milione di risultati, ecc.). La maggior parte delle applicazioni web è limitata per ciò che riguarda l'I/O massimo possibile, mentre la CPU è raramente utilizzata al massimo della sua potenza. Quando si osserva che una certa operazione di input/output utilizza molta CPU, è probabile che ciò indichi una cattiva architettura dell'applicazione. Questo potrebbe significare che le risorse della CPU vengono spese per la gestione dei processi e il passaggio di contesto — e ciò non è molto utile. Se stai facendo qualcosa come l'elaborazione di immagini, la conversione di file audio o l'apprendimento automatico, allora l'applicazione richiede risorse CPU potenti. Ma per la maggior parte delle applicazioni non è così.
Maggiore dettaglio sulle architetture server
Fonte: habr.com
