Prestanda för Linux-nätverksapplikationer. Introduktion

Webbapplikationer används nu överallt, och bland alla transportprotokoll upptar HTTP lejonparten. När man studerar nyanserna i utveckling av webbapplikationer ägnar de flesta människor väldigt lite uppmärksamhet åt operativsystemet där dessa applikationer faktiskt körs. Separationen av utveckling (Dev) och operationer (Ops) gjorde bara situationen värre. Men med framväxten av DevOps-kulturen blir utvecklare ansvariga för att köra sina applikationer i molnet, så det är mycket användbart för dem att bli ordentligt bekanta med operativsystemets backend. Detta är särskilt användbart om du försöker distribuera ett system för tusentals eller tiotusentals samtidiga anslutningar.

Begränsningarna i webbtjänster är mycket lika dem i andra applikationer. Oavsett om det är lastbalanserare eller databasservrar har alla dessa applikationer liknande problem i en högpresterande miljö. Att förstå dessa grundläggande begränsningar och hur man kan övervinna dem i allmänhet hjälper dig att utvärdera prestanda och skalbarhet för dina webbapplikationer.

Jag skriver denna serie artiklar som svar på frågor från unga utvecklare som vill bli välinformerade systemarkitekter. Det är omöjligt att tydligt förstå Linux-applikationsoptimeringstekniker utan att dyka ner i grunderna för hur de fungerar på operativsystemnivå. Även om det finns många typer av applikationer vill jag i den här serien utforska webbaserade applikationer snarare än skrivbordsapplikationer som en webbläsare eller textredigerare. Detta material är avsett för utvecklare och arkitekter som vill förstå hur Linux- eller Unix-program fungerar och hur man strukturerar dem för hög prestanda.

Linux är server rum operativsystem, och oftast körs dina applikationer på detta operativsystem. Även om jag säger "Linux" kan man för det mesta lugnt anta att jag menar alla Unix-liknande operativsystem i allmänhet. Jag har dock inte testat den medföljande koden på andra system. Så om du är intresserad av FreeBSD eller OpenBSD kan dina resultat variera. När jag provar något Linux-specifikt påpekar jag det.

Även om du kan använda denna kunskap för att bygga en app från grunden och den kommer att vara perfekt optimerad, är det bäst att inte göra det. Om du skriver en ny webbserver i C eller C++ för din organisations affärsapplikation kan detta vara din sista dag på jobbet. Men att känna till strukturen för dessa applikationer kommer att hjälpa till att välja befintliga program. Du kommer att kunna jämföra processbaserade system med trådbaserade system såväl som händelsebaserade. Du kommer att förstå och uppskatta varför Nginx presterar bättre än Apache httpd, varför en Tornado-baserad Python-applikation kan tjäna fler användare jämfört med en Django-baserad Python-applikation.

ZeroHTTPd: Lärverktyg

NollHTTPd är en webbserver som jag skrev från grunden i C som ett läromedel. Den har inga externa beroenden, inklusive tillgång till Redis. Vi kör våra egna Redis-procedurer. Se nedan för mer information.

Även om vi skulle kunna diskutera teori långt, finns det inget bättre än att skriva kod, köra den och jämföra alla serverarkitekturer med varandra. Detta är den mest uppenbara metoden. Därför kommer vi att skriva en enkel ZeroHTTPd-webbserver med varje modell: processbaserad, trådbaserad och händelsebaserad. Låt oss kolla in var och en av dessa servrar och se hur de presterar jämfört med varandra. ZeroHTTPd implementeras i en enda C-fil. Den händelsebaserade servern inkluderar uthash, en fantastisk hashtabellimplementering som kommer i en enda rubrikfil. I andra fall finns det inga beroenden, för att inte komplicera projektet.

Det finns många kommentarer i koden som hjälper dig att förstå. Eftersom ZeroHTTPd är en enkel webbserver med några rader kod, är ZeroHTTPd också ett minimalt ramverk för webbutveckling. Den har begränsad funktionalitet, men kan betjäna statiska filer och mycket enkla "dynamiska" sidor. Jag måste säga att ZeroHTTPd är bra för att lära sig att skapa högpresterande Linux-applikationer. I stort sett väntar de flesta webbtjänster på förfrågningar, kontrollerar dem och behandlar dem. Detta är precis vad ZeroHTTPd kommer att göra. Detta är ett verktyg för lärande, inte produktion. Det är inte bra på felhantering och kommer sannolikt inte att ståta med bästa säkerhetspraxis (oh yeah, jag använde strcpy) eller C-språkets smarta knep. Men jag hoppas att det gör sitt jobb bra.

Prestanda för Linux-nätverksapplikationer. Introduktion
ZeroHTTPd hemsida. Det kan mata ut olika filtyper inklusive bilder

Ansökan om gästbok

Moderna webbapplikationer är vanligtvis inte begränsade till statiska filer. De har komplexa interaktioner med olika databaser, cacher, etc. Så vi kommer att skapa en enkel webbapplikation som heter "Gästbok" där besökare lämnar poster under sina namn. Gästboken lagrar inlägg som lämnats tidigare. Det finns även en besöksräknare längst ner på sidan.

Prestanda för Linux-nätverksapplikationer. Introduktion
Webbapplikation "Gästbok" ZeroHTTPd

Besöksräknaren och gästboksanteckningarna lagras i Redis. För kommunikation med Redis implementeras egna rutiner, de är inte beroende av det externa biblioteket. Jag är inte ett stort fan av att rulla ut homebrew-kod när det finns allmänt tillgängliga och väl beprövade lösningar. Men syftet med ZeroHTTPd är att studera Linux-prestanda och tillgång till externa tjänster, medan servering av HTTP-förfrågningar har en allvarlig inverkan på prestanda. Vi måste helt kontrollera kommunikationen med Redis i var och en av våra serverarkitekturer. I vissa arkitekturer använder vi blockerande anrop, i andra använder vi händelsebaserade procedurer. Att använda ett externt Redis-klientbibliotek ger inte denna kontroll. Dessutom utför vår lilla Redis-klient bara ett fåtal funktioner (hämta, ställa in och öka en nyckel; hämta och lägga till en array). Dessutom är Redis-protokollet extremt elegant och enkelt. Du behöver inte ens lära ut det speciellt. Just det faktum att protokollet gör allt arbete i ungefär hundra rader kod visar hur genomtänkt det är.

Följande bild visar vad applikationen gör när klienten (webbläsaren) begär /guestbookURL.

Prestanda för Linux-nätverksapplikationer. Introduktion
Hur gästboksansökan fungerar

När en gästbokssida behöver utfärdas finns det ett anrop till filsystemet för att läsa in mallen i minnet och tre nätverksanrop till Redis. Mallfilen innehåller det mesta av HTML-innehållet för sidan i skärmdumpen ovan. Det finns också speciella platshållare för den dynamiska delen av innehållet: inlägg och besöksräknare. Vi tar emot dem från Redis, infogar dem på sidan och förser kunden med färdigformat innehåll. Det tredje anropet till Redis kan undvikas eftersom Redis returnerar det nya nyckelvärdet när det ökas. Men för vår server, som har en asynkron händelsebaserad arkitektur, är många nätverksanrop ett bra test för inlärningsändamål. Så vi kasserar Redis returvärde för antalet besökare och frågar det med ett separat samtal.

Serverarkitekturer ZeroHTTPd

Vi bygger sju versioner av ZeroHTTPd med samma funktionalitet men olika arkitekturer:

  • Iterativ
  • Gaffelserver (en underordnad process per begäran)
  • Pre-fork server (pre-forking av processer)
  • Server med exekveringstrådar (en tråd per begäran)
  • Server med förtrådsskapande
  • Arkitektur baserad poll()
  • Arkitektur baserad epoll

Vi mäter prestandan för varje arkitektur genom att ladda servern med HTTP-förfrågningar. Men när man jämför mycket parallella arkitekturer ökar antalet frågor. Vi testar tre gånger och räknar ut medelvärdet.

Testmetodik

Prestanda för Linux-nätverksapplikationer. Introduktion
ZeroHTTPd belastningstestinställning

Det är viktigt att inte alla komponenter körs på samma maskin när man kör tester. I det här fallet medför operativsystemet ytterligare schemaläggningskostnader eftersom komponenter konkurrerar om CPU. Att mäta operativsystemets overhead för var och en av de valda serverarkitekturerna är ett av de viktigaste målen med denna övning. Att lägga till fler variabler kommer att bli skadligt för processen. Därför fungerar inställningen på bilden ovan bäst.

Vad gör var och en av dessa servrar?

  • load.unixism.net: Det är här vi kör ab, Apache Benchmark-verktyg. Den genererar den belastning som behövs för att testa våra serverarkitekturer.
  • nginx.unixism.net: Ibland vill vi köra mer än en instans av ett serverprogram. För att göra detta fungerar Nginx-servern med lämpliga inställningar som en lastbalanserare som kommer från ab till våra serverprocesser.
  • zerohttpd.unixism.net: Här kör vi våra serverprogram på sju olika arkitekturer, en i taget.
  • redis.unixism.net: Denna server kör Redis-demonen, där gästboksinlägg och besöksräknare lagras.

Alla servrar körs på samma processorkärna. Tanken är att utvärdera den maximala prestandan för varje arkitektur. Eftersom alla serverprogram testas på samma hårdvara är detta en baslinje för jämförelse. Min testuppsättning består av virtuella servrar som hyrs från Digital Ocean.

Vad mäter vi?

Du kan mäta olika indikatorer. Vi utvärderar prestandan för varje arkitektur i en given konfiguration genom att ladda servrarna med förfrågningar på olika nivåer av parallellitet: belastningen växer från 20 till 15 000 samtidiga användare.

Testresultat

Följande diagram visar prestanda för servrar på olika arkitekturer på olika nivåer av parallellitet. Y-axeln är antalet förfrågningar per sekund, x-axeln är parallella anslutningar.

Prestanda för Linux-nätverksapplikationer. Introduktion

Prestanda för Linux-nätverksapplikationer. Introduktion

Prestanda för Linux-nätverksapplikationer. Introduktion

Nedan finns en tabell med resultaten.

förfrågningar per sekund

parallellism
iterativ
gaffel
förgaffel
strömning
förströmning
enkät
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
stor spridning
2138

5000
-
stor spridning
1600
1100
2519
-
2235

8000
-
-
1200
stor spridning
2451
-
2100

10 000
-
-
stor spridning
-
2200
-
2200

11 000
-
-
-
-
2200
-
2122

12 000
-
-
-
-
970
-
1958

13 000
-
-
-
-
730
-
1897

14 000
-
-
-
-
590
-
1466

15 000
-
-
-
-
532
-
1281

Från grafen och tabellen kan man se att över 8000 samtidiga förfrågningar har vi bara två spelare kvar: pre-fork och epoll. När belastningen ökar presterar en omröstningsbaserad server sämre än en strömmande. Arkitekturen för att skapa trådar före skapande är en värdig konkurrent till epoll, ett bevis på hur väl Linux-kärnan schemalägger ett stort antal trådar.

ZeroHTTPd källkod

ZeroHTTPd källkod här. Det finns en separat katalog för varje arkitektur.

ZeroHTTPd │ ├── 01_iterativ │ ├── main.c ├── 02_forking │ ├── main.c ├── 03_preforking ───├ │ _ gänga │ ├── main.c ├── 04_prethreading │ ├── main.c ├── 05_poll │ ├── main.c ├── 06_epoll │ └── ── ─e main.c ├ ├── index .html │ └── tux . png └── mallar └── gästbok └── index.html

Förutom sju kataloger för alla arkitekturer, finns det ytterligare två i katalogen på toppnivå: offentliga och mallar. Den första innehåller filen index.html och bilden från den första skärmdumpen. Du kan lägga in andra filer och mappar där, och ZeroHTTPd bör tjäna dessa statiska filer utan problem. Om sökvägen i webbläsaren matchar sökvägen i den offentliga mappen, letar ZeroHTTPd efter filen index.html i den här katalogen. Innehållet i gästboken genereras dynamiskt. Den har bara en hemsida och dess innehåll är baserat på filen 'templates/guestbook/index.html'. ZeroHTTPd lägger enkelt till dynamiska sidor för förlängning. Tanken är att användare kan lägga till mallar i den här katalogen och utöka ZeroHTTPd efter behov.

För att bygga alla sju servrarna, kör make all från katalogen på översta nivån - och alla byggen kommer att visas i den här katalogen. Körbara filer letar efter publika kataloger och mallar i katalogen från vilken de startas.

Linux API

Du behöver inte vara väl insatt i Linux API för att förstå informationen i den här artikelserien. Jag rekommenderar dock att du läser mer om detta ämne, det finns många referensresurser på Internet. Även om vi kommer att beröra flera kategorier av Linux API:er, kommer vårt fokus i första hand att ligga på processer, trådar, händelser och nätverksstacken. Förutom böcker och artiklar om Linux API rekommenderar jag även att läsa mana för systemanrop och biblioteksfunktioner som används.

Prestanda och skalbarhet

En anmärkning om prestanda och skalbarhet. Teoretiskt sett finns det inget samband mellan dem. Du kan ha en webbtjänst som fungerar väldigt bra, med en svarstid på några millisekunder, men den skalas inte alls. På samma sätt kan det finnas en dåligt presterande webbapplikation som tar några sekunder att svara, men den skalas med tiotals för att hantera tiotusentals samtidiga användare. Kombinationen av hög prestanda och skalbarhet är dock en mycket kraftfull kombination. Högpresterande applikationer använder i allmänhet resurser sparsamt och betjänar därmed effektivt fler samtidiga användare på servern, vilket minskar kostnaderna.

CPU- och I/O-uppgifter

Slutligen, inom datorer finns det alltid två möjliga typer av uppgifter: för I/O och CPU. Att ta emot förfrågningar över Internet (nätverks-I/O), betjäna filer (nätverk och disk I/O), kommunicera med databasen (nätverk och disk I/O) är alla I/O-aktiviteter. Vissa databasfrågor kan vara lite CPU-intensiva (sortering, i genomsnitt en miljon resultat, etc.). De flesta webbapplikationer begränsas av maximalt möjliga I/O, och processorn används sällan med full kapacitet. När du ser att någon I/O-uppgift använder mycket CPU, är det troligen ett tecken på dålig applikationsarkitektur. Detta kan innebära att CPU-resurser slösas bort på processhantering och kontextväxling – och det är inte helt användbart. Om du gör något som bildbehandling, ljudfilkonvertering eller maskininlärning, kräver applikationen kraftfulla CPU-resurser. Men för de flesta applikationer är detta inte fallet.

Läs mer om serverarkitekturer

  1. Del I: Iterativ arkitektur
  2. Del II. Gaffelservrar
  3. Del III. Pre-fork-servrar
  4. Del IV. Servrar med utförandetrådar
  5. Del V. Förtrådade servrar
  6. Del VI. Pol-baserad arkitektur
  7. Del VII. epoll-baserad arkitektur

Källa: will.com

Lägg en kommentar