Performanța aplicațiilor de rețea Linux. Introducere

Aplicațiile web sunt acum utilizate pe scară largă, iar dintre toate protocoalele de transport, HTTP ocupă o pondere majoritară. Studiind nuanțele dezvoltării aplicațiilor web, majoritatea acordă foarte puțină atenție sistemului de operare pe care aceste aplicații rulează efectiv. Separarea între dezvoltare (Dev) și operațiuni (Ops) a agravat doar situația. Însă, odată cu răspândirea culturii DevOps, dezvoltatorii încep să își asume responsabilitatea pentru desfășurarea aplicațiilor lor în cloud, așa că este foarte util pentru ei să-și cunoască în detaliu backend-ul sistemului de operare. Acest lucru este deosebit de important dacă încercați să implementați un sistem pentru mii sau zeci de mii de conexiuni simultane.

Limitările din serviciile web sunt foarte asemănătoare cu cele din alte aplicații. Fie că este vorba despre balance-uri de sarcină sau servere de baze de date, toate aceste aplicații se confruntă cu probleme similare într-un mediu de înaltă performanță. Înțelegerea acestor limitări fundamentale și a modurilor în care pot fi depășite va permite, în general, evaluarea performanței și scalabilității aplicațiilor web.

Scriu această serie de articole ca răspuns la întrebările tinerilor dezvoltatori care doresc să devină arhitecți de sistem bine informați. Este imposibil să înțelegi clar metodele de optimizare a aplicațiilor Linux fără a te aprofunda în fundamentele modului în care acestea funcționează la nivelul sistemului de operare. Deși există multe tipuri de aplicații, în acest ciclu vreau să explorez aplicațiile de rețea, nu aplicațiile desktop, precum browserul sau editorul de text. Acest material este destinat dezvoltatorilor și arhitecților care doresc să înțeleagă cum funcționează programele Linux sau Unix și cum să le structureze pentru o performanță ridicată.

Linux este un sistem de operare server, iar cel mai adesea aplicațiile dvs. funcționează pe acest sistem de operare. Deși spun «Linux», în cea mai mare parte a timpului, puteți presupune cu încredere că se referă la toate sistemele de operare de tip Unix în general. Totuși, nu am testat codul care însoțește pe alte sisteme. Așadar, dacă vă interesează FreeBSD sau OpenBSD, rezultatul poate varia. Când încerc ceva specific Linux, voi specifica acest lucru.

Deși puteți folosi cunoștințele dobândite pentru a crea o aplicație de la zero, care va fi optimizată excelent, este mai bine să nu faceți astfel. Dacă veți scrie un nou server web în C sau C++ pentru aplicația de afaceri a organizației dvs., s-ar putea să fie ultima zi de muncă pentru dumneavoastră. Cu toate acestea, cunoașterea structurii acestor aplicații va ajuta în alegerea programelor deja existente. Veți putea compara sistemele bazate pe procese cu sistemele bazate pe fire, dar și cu cele bazate pe evenimente. Veți înțelege și aprecia de ce Nginx funcționează mai bine decât Apache httpd, de ce o aplicație Python bazată pe Tornado poate deservi mai mulți utilizatori comparativ cu o aplicație Python bazată pe Django.

ZeroHTTPd: instrument de învățare

ZeroHTTPd — server web pe care l-am scris de la zero în C ca instrument educațional. Nu are dependențe externe, inclusiv acces la Redis. Rulăm propriile proceduri Redis. Detalii mai jos.

Deși am putea discuta mult teorie, nu există nimic mai bun decât să scriem cod, să-l rulăm și să comparăm toate arhitecturile serverelor între ele. Aceasta este cea mai ilustrativă metodă. Prin urmare, vom scrie un server web simplu ZeroHTTPd, aplicând fiecare model: bazat pe procese, fire și evenimente. Vom verifica fiecare dintre acești serveri și vom observa cum funcționează comparativ unul cu celălalt. ZeroHTTPd este implementat într-un singur fișier C. Serverul bazat pe evenimente include uthash, o implementare excelentă a tabelului hash, care vine într-un singur fișier header. În celelalte cazuri, nu există dependențe pentru a nu complica proiectul.

Codul conține multe comentarii pentru a ajuta la înțelegere. Fiind un server web simplu format din câteva linii de cod, ZeroHTTPd reprezintă, de asemenea, un cadru minim pentru dezvoltarea web. Are funcționalitate limitată, dar este capabil să livreze fișiere statice și pagini „dinamice” foarte simple. Trebuie să spun că ZeroHTTPd este potrivit pentru învățarea creării aplicațiilor Linux de înaltă performanță. În esență, majoritatea serviciilor web așteaptă solicitări, le verifică și le procesează. Exact aceasta va face ZeroHTTPd. Este un instrument de învățare, nu pentru producție. Nu excelează în gestionarea erorilor și este puțin probabil să se laude cu cele mai bune practici de securitate (oh da, am folosit strcpy) sau metode complexe ale limbajului C. Dar sper că își va îndeplini bine sarcina.

Performanța aplicațiilor de rețea Linux. Introducere
Pagina principală ZeroHTTPd. Poate livra diferite tipuri de fișiere, inclusiv imagini

Aplicația de carte de oaspeți

Aplicațiile web moderne nu sunt în general limitate la fișiere statice. Acestea au interacțiuni complexe cu diverse baze de date, cache-uri etc. Așadar, vom crea o aplicație web simplă numită „Cartea de Oaspeți”, unde vizitatorii pot lăsa mesaje sub numele lor. În cartea de oaspeți se păstrează mesajele anterioare. De asemenea, există un contor de vizitatori în partea de jos a paginii.

Performanța aplicațiilor de rețea Linux. Introducere
Aplicația web „Cartea de Oaspeți” ZeroHTTPd

Contorul vizitatorilor și mesajele din cartea de oaspeți sunt stocate în Redis. Pentru comunicarea cu Redis sunt implementate proceduri proprii, care nu depind de biblioteci externe. Nu sunt un mare fan al scrierii codului de mână, când există soluții publice bine testate. Dar scopul ZeroHTTPd este de a explora performanța Linux și accesul la servicii externe, în timp ce gestionarea cererilor HTTP afectează semnificativ performanța. Trebuie să controlăm complet comunicările cu Redis în fiecare dintre arhitecturile noastre server. Într-o arhitectură folosim apeluri blocate, iar în altele – proceduri bazate pe evenimente. Folosirea unei biblioteci client externe Redis nu ne va oferi un astfel de control. În plus, clientul nostru mic Redis execută doar câteva funcții (recuperare, setare și incrementare a cheii; recuperare și adăugare într-un array). De asemenea, protocolul Redis este extrem de elegant și simplu. Nici nu trebuie să-l înveți special. Faptul că toată munca este realizată de protocol în aproximativ o sută de linii de cod arată cât de bine a fost gândit.

În figura următoare sunt prezentate acțiunile aplicației atunci când clientul (browserul) face o cerere /guestbookURL.

Performanța aplicațiilor de rețea Linux. Introducere
Mecanismul de funcționare al aplicației cărții de oaspeți

Când este nevoie să livrăm pagina cărții de oaspeți, se face un apel la sistemul de fișiere pentru a citi șablonul în memorie și trei apeluri rețea către Redis. Fișierul șablon conține cea mai mare parte a conținutului HTML pentru pagina din captura de ecran de mai sus. De asemenea, există locuri rezervate speciale pentru partea dinamică a conținutului: înregistrările și contorul vizitatorilor. Le obținem din Redis, le inserăm pe pagină și oferim clientului conținutul complet generat. Apelul al treilea către Redis poate fi evitat, deoarece Redis returnează o nouă valoare a cheii la incrementare. Totuși, pentru serverul nostru cu arhitectură asincronă bazată pe evenimente, numeroase apeluri rețea sunt o provocare bună în scopuri educaționale. Astfel, abandonăm valoarea returnată de Redis despre numărul de vizitatori și o cerem printr-un apel separat.

Arhitecturi server ZeroHTTPd

Construim șapte versiuni ZeroHTTPd cu aceeași funcționalitate, dar cu arhitecturi diferite:

  • Iterativ
  • Server fork (un proces fiu pe cerere)
  • Server pre-fork (forking proces pe anticipat)
  • Server cu fire de execuție (un fir pe cerere)
  • Server cu fire pre-create
  • Arhitectură bazată pe poll()
  • Arhitectură bazată pe epoll

Măsurăm performanța fiecărei arhitecturi încărcând serverul cu cereri HTTP. Dar când comparăm arhitecturi cu un grad înalt de concurență, numărul de cereri crește. Testăm de trei ori și calculăm media.

Metodologia de testare

Performanța aplicațiilor de rețea Linux. Introducere
Configurarea pentru testarea încărcării ZeroHTTPd

Este important ca, în timpul testării, toate componentele să nu funcționeze pe aceeași mașină. În acest caz, OS suportă costuri suplimentare de planificare, deoarece componentele concurează pentru CPU. Măsurarea costurilor de suprafață ale sistemului de operare cu fiecare dintre arhitecturile server alese este unul dintre cele mai importante obiective ale acestui exercițiu. Adăugarea de variabile suplimentare va fi dăunătoare procesului. Drept urmare, configurarea din imaginea de mai sus funcționează cel mai bine.

Ce face fiecare dintre aceste servere

  • load.unixism.net: aici rulăm ab, utilitarul Apache Benchmark. Acesta generează încărcarea necesară pentru a testa arhitecturile noastre server.
  • nginx.unixism.net: uneori dorim să rulăm mai multe instanțe ale programului server. Pentru aceasta, serverul Nginx cu setările corespunzătoare acționează ca un balansor de încărcare pentru ab procesele noastre de server.
  • zerohttpd.unixism.net: aici rulăm programele noastre server pe șapte arhitecturi diferite, câte una de fiecare dată.
  • redis.unixism.net: pe acest server rulează demonul Redis, unde sunt stocate înregistrările din cartea de vizită și numărătorul vizitatorilor.

Toate serverele rulează pe un singur nucleu de procesor. Ideea este de a evalua performanța maximă a fiecărei arhitecturi. Deoarece toate programele server sunt testate pe același echipament, aceasta este baza de comparare. Configurația mea de testare constă în servere virtuale închiriate de la Digital Ocean.

Ce măsurăm?

Se pot măsura diferite metrici. Evaluăm performanța fiecărei arhitecturi în această configurație, încărcând serverele cu cereri la diferite niveluri de paralelism: încărcarea crește de la 20 la 15.000 de utilizatori simultan.

Rezultatele testelor

În următorul grafic este prezentată performanța serverelor pe arhitecturi diferite la diferite niveluri de paralelism. Pe axa y – numărul de cereri pe secundă, pe axa x – conexiuni paralele.

Performanța aplicațiilor de rețea Linux. Introducere

Performanța aplicațiilor de rețea Linux. Introducere

Performanța aplicațiilor de rețea Linux. Introducere

Mai jos este un tabel cu rezultatele.

cereri pe secundă

paralelism
iterativ
fork
pre-fork
pe flux
pre-pe flux
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
variabilitate mare
2138

5000
–
variabilitate mare
1600
1100
2519
–
2235

8000
–
–
1200
variabilitate mare
2451
–
2100

10 000
–
–
variabilitate mare
–
2200
–
2200

11 000
–
–
–
–
2200
–
2122

12 000
–
–
–
–
970
–
1958

13 000
–
–
–
–
730
–
1897

14 000
–
–
–
–
590
–
1466

15 000
–
–
–
–
532
–
1281

Din grafic și tabel se observă că peste 8000 de cereri simultane, ne rămân doar doi jucători: pre-fork și epoll. Pe măsură ce încărcarea crește, serverul bazat pe poll funcționează mai rău decât pe flux. Arhitectura cu crearea anticipată a firelor se află într-o competiție demnă cu epoll: aceasta arată cât de bine kernelul Linux planifică un număr mare de fire.

Codul sursă ZeroHTTPd

Codul sursă ZeroHTTPd aici. Pentru fiecare arhitectură există un catalog separat.

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.html

Pe lângă cele șapte directoare pentru toate arhitecturile, în directorul de nivel superior se află încă două: public și templates. În primul se află fișierul index.html și imaginea din primul screenshot. Acolo se pot plasa alte fișiere și foldere, iar ZeroHTTPd ar trebui să livreze fără probleme aceste fișiere statice. Dacă path-ul din browser corespunde căii din folderul public, ZeroHTTPd caută în acest director fișierul index.html. Conținutul pentru cartea de oaspeți este generat dinamic. Are doar pagina principală, iar conținutul acesteia se bazează pe fișierul 'templates/guestbook/index.html'. În ZeroHTTPd se pot adăuga cu ușurință pagini dinamice pentru extindere. Ideea este că utilizatorii pot adăuga șabloane în acest director și pot extinde ZeroHTTPd pe măsură ce este necesar.

Pentru a construi toate cele șapte servere, rulați make all din directorul de nivel superior - și toate construcțiile vor apărea în acest director. Fișierele executabile caută directoarele public și templates în directorul din care sunt lansate.

API-ul Linux

Pentru a înțelege informațiile din acest ciclu de articole, nu este necesar să aveți o cunoștință profundă despre API-ul Linux. Totuși, recomand să citiți mai multe despre acest subiect, există multe resurse de referință online. Deși vom aborda câteva categorii ale API-ului Linux, atenția noastră se va concentra în principal asupra proceselor, firelor de execuție, evenimentelor și stivei de rețea. Pe lângă cărțile și articolele despre API-ul Linux, vă recomand de asemenea să consultați man-urile pentru apelurile sistem și funcțiile bibliotecilor utilizate.

Performanță și scalabilitate

O observație despre performanță și scalabilitate. Teoretic, nu există nicio legătură între ele. Puteți avea un serviciu web care funcționează foarte bine, cu un timp de răspuns de câteva milisecunde, dar care nu se scală deloc. De asemenea, poate exista o aplicație web care funcționează prost, care necesită câteva secunde pentru a răspunde, dar care se scalează pe zeci pentru a gestiona zeci de mii de utilizatori simultan. Cu toate acestea, combinația de performanță ridicată și scalabilitate este o combinație foarte puternică. Aplicațiile de înaltă performanță folosesc, în general, resursele într-un mod economic și, prin urmare, servesc eficient mai mulți utilizatori simultani pe server, reducând costurile.

Sarcinile CPU și I/O

În sfârșit, în calcule există întotdeauna două tipuri posibile de sarcini: pentru I/O și CPU. Primele cereri prin internet (input-output de rețea), gestionarea fișierelor (input-output de rețea și de pe disc), comunicările cu baza de date (input-output de rețea și de pe disc) — toate acestea sunt acțiuni I/O. Unele cereri către baza de date pot solicita puțin din CPU (sortare, calcularea mediei a unui milion de rezultate etc.). Majoritatea aplicațiilor web sunt limitate de I/O-ul maxim posibil, iar procesorul este rareori utilizat la capacitate maximă. Când observați că o sarcină de input-output utilizează mult CPU, probabil că acesta este un semn al unei arhitecturi slabe a aplicației. Acest lucru poate însemna că resursele CPU sunt consumate pentru gestionarea proceselor și schimbarea contextului — și acest lucru nu este foarte util. Dacă faceți ceva de genul prelucrării imaginilor, conversiei fișierelor audio sau învățării automate, atunci aplicația necesită resurse CPU puternice. Dar pentru majoritatea aplicațiilor, nu este cazul.

Mai multe detalii despre arhitecturile serverului

  1. Partea I. Arhitectura iterativă
  2. Partea II. Servere de tip fork
  3. Partea III. Servere pre-fork
  4. Partea IV. Servere cu fire de execuție
  5. Partea V. Servere cu crearea anticipată a firelor
  6. Partea VI. Arhitectura bazată pe poll
  7. Partea VII. Arhitectura bazată pe epoll

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster