Load balancer – šta će nam to?

Kao što vodimo računa da ljudske kadrove ne preopteretimo poslom, jer postaju manje produktivni, isto važi i za servere. Tu nastupaju tzv. Load Balancer-i.

Povezani članci

Kako bi se najbolje ispratili primeri iz ovog članka, neophodno je ispratiti povezane objave:

Šta je podela opterećenja?

Podela opterećenja ili load balancing predstavlja odličan način skaliranja aplikacije i povećanja performansi. Na pitanje kako se realizuje, odgovor je relativno jednostavan. Ideja je da postoji više servera koji hostuju aplikaciju i da postoji jedan raspoređivač koji će, u zavisnosti od opterećenosti servera da prosleđuje zahteve. Korisnika aplikacije ne zanima kako u pozadini to funkcioniše i ne sme postojati razlika u izvršavanju zahteva u odnosu na server na koji se poziv prosleđuje.

Ilustracija rada Load balancer-a.
Ilustracija rada Load balancer-a.

Postoji više implementacija load balancer-a, a mi ćemo u ovom članku koristiti Nginx Load Balancer.

Nginx Load Balancer

Instalacija

Instalacija Nginx-a i postavljanje web aplikacije detaljno je opisano u članku o Apache Bench alatu za testiranje performansi aplikacije, tako da ćemo taj deo preskočiti i koristićemo se arhitekturom tog sistema koji ćemo proširiti još jednim serverom, kako bismo mogli da raspoređujemo saobraćaj prilikom testiranja aplikacije. Jedino što ćemo uraditi jeste kloniranje virtuelne mašine na kojoj se nalazi ASP.NET Core Web API aplikacija, podesiti IP adresu za Nginx konfiguraciju i nakon toga raditi na load balanceru.

Virtuelne mašine

Koraci prilikom kreiranja kopije postojeće virtuelne mašine:

Za demonstraciju se koristi Oracle VM VirtualBox. Desni klik na virtuelnu mašinu koju želimo klonirati i biramo opciju clone.

Kloniranje virtuelne mašine
Kloniranje virtuelne mašine

Sledeći korak jeste izabrati gde će se virtuelna mašina nalaziti i koja podešavanja originalne mašine će sačuvati.

Podešavanja klonirane virtuelne mašine
Podešavanja klonirane virtuelne mašine

Poslednji korak jeste izbor opcije da želimo punu kopiju mašine, a nakon toga preostaje nam da sačekamo da se proces završi.

Full clone opcija i proces kloniranja.
Full clone opcija i proces kloniranja.

Kada pokrenemo virtuelnu mašinu, potrebno je saznati IP adresu (komanda ifconfig ili ip addr) i istu uneti u fajlu na lokaciji /etc/nginx/sites-available/default.

$ ifconfig
$ sudo nano /etc/nginx/sites-available/default # uneti odgovarajuću IP adresu u server delu
$ sudo nginx -t # verifikovati izmene
$ sudo nginx -s reload # restartovati nginx

Na ovaj način dobili smo još jedan server na kome se nalazi ista aplikacija kao na virtuelnoj mašini node-01.

Kako bi sistem izgledao što realnije, potrebno je podići još jednu virtuelnu mašinu na kojoj će biti instaliran samo Nginx i koji će biti konfigurisan kao load balancer.

Rezime okruženja

U ovom primeru imamo dve virutelne mašine sa koje hostuju web aplikaciju sa IP adresama koje slede, zatim mašinu za testiranje i mašinu gde se nalazi sam load balancer:

  • Node-01: 192.168.0.18 | 1 CPU | 1GB RAM
  • Node-02: 192.168.0.14 | 1 CPU | 1GB RAM
  • Node-Bench: 192.168.0.17 | 1 CPU | 1GB RAM
  • Node-Balancer: 192.168.0.16 | 2 CPU | 2GB RAM
Izgled okruženja
Izgled okruženja

NAPOMENA: Veliki problem predstavlja činjenica da se sve testira na jednom računaru koji hostuje 4 virutelne mašine, tako da je iz tog razloga postavljena navedena konfiguracija virtuelnih mašina.

Konfiguracija Nginx Load Balancera

Kako bi se Nginx ponašao kao Load Balancer potrebno je izmeniti fajl /etc/nginx/sites-available/default. Isti ćemo kopirati u default-backup, za slučaj da dođe do nekih nepredviđenih okolnosti, pa da lako možemo vratiti podešavanja koja rade.

$ cp /etc/nginx/sites-available/default /etc/nginx/sites-available/default-backup      # Kopiranje

Nakon ovog dela fajl možemo menjati. Neophodno je podesiti dve sekcije: upstream i server.

  • upstream sekciju koristimo da navedemo sve servere na kojima ćemo prosleđivati saobraćaj.
  • server sekciju koristimo da naglasimo koji port slušamo i kakvo preusmeravanje vršimo. U ovom slučaju usmeravamo na upstream deo.

Pomenute sekcije izgledaju ovako:

upstream backend {
   server 192.168.0.18; 
   server 192.168.0.14;
}

server {
   listen 80; 

   location / {
      proxy_pass http://backend;
   }
}

Poslednji korak jeste restartovanje Nginx-a.

$ sudo systemctl restart nginx

Načini podele opterećenja

Round-robin

Round-robin je podrazumevani algoritam raspoređivanja opterećenja. Zahtevi se redom prosleđuju izlistanim serverima u krug na osnovu redosleda kojim su upisani u upstream backend sekciju. Algoritam je odličan ukoliko su svi serveri iste ili približne konfiguracije.

Najmanje konekcija

Algoritam je takođe veoma jednostavan, kao što i samo ime kaže. Zahtev se prosleđuje serveru koji ima najmanje otvorenih konekcija. Što se tiče dodele zahteva, ovaj algoritam je više fer u odnosu na Round-robin iz razloga što ukoliko neki server duže obrađuje zahteve, isti će se preskočiti, a zahtev proslediti serveru koji ima manje zahteva na obradi.

Da bi se koristio opisani algoritam, neophodno je uneti sledeću izmenu u upstream backend sekciju:

upstream backend {
   least_conn;
   server 192.168.0.18; 
   server 192.168.0.14;
}

IP heširanje

Prethodno opisani algoritmi su dobri, vrše relativno fer raspodelu opterećenja, ali se javlja i jedan problem. Šta ako iz nekog razloga moramo omogućiti da isti korisnici pristupaju uvek istim serverima? U tom slučaju pomenuti algoritmi nam nisu od koristi. Rešenje se ogleda u IP hashing algoritmu. Više o heširanju možete pročitati ovde. Ideja je da se korisnikova IP adresa koristi kao ključ za određivanje kom serveru treba da se prosledi zahtev.

Da bi se koristio opisani algoritam, neophodno je uneti sledeću izmenu u upstream backend sekciji:

upstream backend {
   ip_hash;
   server 192.168.0.18; 
   server 192.168.0.14;
}

Definisanje težine/prioriteta

U slučaju da performanse servera, tj. njihovi resursi nisu isti, možemo odrediti koji server treba da opsluži najveći broj zahteva, a koji ostatak, tako što ćemo svakom dodeliti težinu. Što je veća težina, server će dobijati više zahteva.

upstream backend {
   least_conn;
   server 192.168.0.18 weight=4; 
   server 192.168.0.14 weight=2;
}

Provera dostupnosti servera (Health checks)

Kako bi Nginx Load Balancer znao koji serveri su dostupni, vrši takozvane health provere. Ukoliko server ne odgovori ili odgovori sa greškom, nginx će to zabeležiti i ubuduće će nastojati da izbegne slanje zahteva tom serveru. Zato je moguće podesiti dva polja za provere dostupnosti: max_fails i fail_timeout.

  • max_fails polje označava koliko puta će nginx pokušati da pošalje zahtev serveru. Ukoliko isto nije navedeno, podrazumevana vrednost je 1.
  • fail_timeout je polje koje označava posle koliko vremena će se pokušati sa ponovnim slanjem zahteva serveru koji je odgovorio greškom ili nije odgovorio uopšte. Ovo polje ima smisla podesiti samo ukoliko je vrednost polja max_fails veća od 1. Ukoliko polje nije postavljeno, podrazumevana vrednost jeste 10 sekundi.

Ova podešavanja vrše se u upstream sekciji. U konfiguraciji ispod naveli smo da server može tri puta da ne odgovori ili vrati grešku i nakon 30 sekundi ponovo pokušati sa slanjem zahteva:

upstream backend {
   least_conn;
   server 192.168.0.18 weight=4; 
   server 192.168.0.14 max_fails=3 fail_timeout=30s;
}

Podešavanje je korisno ukoliko se u određenom trenutku dođe u situaciju da su serveri veoma opterećeni.

Testiranje performansi

U test primeru šaljemo 10 000 zahteva, pri čemu se njih 100 izvršava konkurentno.

Test sa jednim serverom

$ ab -n 10000 -c 100 -k http://192.168.0.18/api/values
izlaz
Concurrency Level:      100
Time taken for tests:   7.956 seconds
Complete requests:      10000
Failed requests:        0
Keep-Alive requests:    0
Total transferred:      1810000 bytes
HTML transferred:       280000 bytes
Requests per second:    1256.91 [#/sec] (mean)
Time per request:       79.560 [ms] (mean)
Time per request:       0.796 [ms] (mean, across all concurrent requests)
Transfer rate:          222.17 [Kbytes/sec] received
 
Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    1   2.9      0      28
Processing:    18   78   9.3     78     153
Waiting:       18   78   9.3     77     150
Total:         43   79   8.6     79     153
 
Percentage of the requests served within a certain time (ms)
  50%     79
  66%     82
  75%     84
  80%     85
  90%     89
  95%     92
  98%     96
  99%    105
 100%    153 (longest request)

Test sa Load Balancer-om

$ ab -n 10000 -c 100 -k http://192.168.0.16/api/values
Izlaz
Concurrency Level:      100
Time taken for tests:   6.542 seconds
Complete requests:      10000
Failed requests:        0
Keep-Alive requests:    0
Total transferred:      1810000 bytes
HTML transferred:       280000 bytes
Requests per second:    1528.61 [#/sec] (mean)
Time per request:       65.419 [ms] (mean)
Time per request:       0.654 [ms] (mean, across all concurrent requests)
Transfer rate:          270.19 [Kbytes/sec] received
 
Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    7   9.3      2      47
Processing:    12   58  15.7     59     152
Waiting:       11   56  15.4     57     138
Total:         26   65  13.7     63     153
 
Percentage of the requests served within a certain time (ms)
  50%     63
  66%     68
  75%     72
  80%     74
  90%     82
  95%     91
  98%    102
  99%    109
 100%    153 (longest request)

Poređenje rezultata

Dijagrami poređenja rezultata

Grafikoni ispod predstavljaju poređenja performansi sa i bez Load Balancer-a. Naravno, neophodno je imati u vidu da se sve pokreće na virtuelnim mašinama i izvršava na jednom istom fizičkom računaru, tako da u realnom sistemu performanse moraju biti značajno bolje.

Test sa Load Balancer-om i malim brojem zahteva daje performanse iste ili za nijansu lošije u odnosu na test sa samo jednim serverom. Razlog tome je što je potrebno sa Load balancer-a proslediti zahtev jednom od servera, a pošto je mali broj zahteva, serveri uspevaju na vreme da opsluže zahteve i ostaje da se vreme gubi prilikom dvostrukog prosleđivanja zahteva. Međutim, razlika je minimalna, tako da se u praksi može zanemariti. Prava korist od postojanja više servera kojima Load balancer prosleđuje zahteve vidi se u situaciji kada postoji veliki broj zahteva.

Konkretno, jedan server bio je u mogućnosti da obradi 1256.91 zahteva u sekundi, dok je uz pomoć Load balancer-a broj zahteva u sekundi 1528.61. Što se tiče vremena potrebnog za obradu jednog zahteva, ono je sa 79.560 milisekundi palo na 65.419 milisekundi. Protok podataka se povećao sa 222.17 Kbytes/sec na 270.19 Kbytes/sec. Što se tiče vremena obrade najsporijeg zahteva, on je isti za oba primera – 153 milisekundi.

Zaključak

U situacijama kada je vertikalno skaliranje nemoguće, rešenje se javlja u vidu Load balancer-a, gde je dovoljno samo postaviti novu mašinu i konfigurisati kako će se saobraćaj preusmeravati. Kao što se na osnovu testa može primetiti, uz određeno ulaganje benefiti mogu biti veoma veliki.

Korisni linkovi

Autor: Bojan Piskulić

Svestrana osoba koja voli tehnologiju, trenutno se pretežno bavim web-om, u slobodno vreme trudim se da naučim što više novih stvari.

Bojan Piskulić

Svestrana osoba koja voli tehnologiju, trenutno se pretežno bavim web-om, u slobodno vreme trudim se da naučim što više novih stvari.