
NGINX je sjajan ... ali otkrio sam da je njegova dokumentacija o ograničenju brzine donekle ... ograničena. Stoga sam napisao ovaj vodič za limitiranje brzine i oblikovanje prometa s NGINX-om.
Idemo na:
- opisati NGINX direktive
- objasniti NGINX-ovu logiku prihvaćanja / odbijanja
- pomažu vam vizualizirati kako se stvarni nalet prometa obrađuje pomoću različitih postavki: ograničavanje brzine, prometna politika i dopuštanje malih naleta
Kao bonus, uključio sam repo GitHub i rezultirajuću Dockerovu sliku tako da možete eksperimentirati i reproducirati testove. Uvijek je lakše naučiti radeći!
NGINX smjernice za ograničenje brzine i njihove uloge

Ovaj post fokusira na ngx_http_limit_req_module, koji vam pruža limit_req_zone
i limit_req
direktivama. Također pruža limit_req_status
i limit_req_level
. Zajedno vam omogućuju upravljanje statusnim kodom HTTP odgovora za odbijene zahtjeve i način na koji se ta odbijanja bilježe.
Većina zabune proizlazi iz logike odbijanja.
Prvo, morate razumjeti limit_req
direktivu, koja treba zone
parametar, a također nudi opcijuburst
i nodelay
parametre.
Ovdje je u igri više koncepata:
zone
omogućuje vam definiranje segmenta, zajedničkog 'prostora' u kojem se broje dolazni zahtjevi. Svi zahtjevi koji dolaze u isti segment računat će se u isto ograničenje stope. To vam omogućuje ograničavanje po URL-u, IP-u ili bilo čemu neobičnom.burst
nije obavezna. Ako je postavljeno, definira koliko prekoračenja zahtjeva možete prihvatiti preko osnovne stope. Ovdje treba napomenuti jednu važnu stvar: rafal je apsolutna vrijednost, nije stopa .nodelay
je također neobavezna i korisna je samo kada također postaviteburst
vrijednost, a zašto ćemo vidjeti u nastavku.
Kako NGINX odlučuje hoće li se zahtjev prihvatiti ili odbiti?
Kada postavite zonu, definirate brzinu, poput 300r/m
dopuštanja 300 zahtjeva u minuti ili 5r/s
dopuštanja 5 zahtjeva svake sekunde.
Na primjer:
limit_req_zone $request_uri zone=zone1:10m rate=300r/m;
limit_req_zone $request_uri zone=zone2:10m rate=5r/s;
Važno je shvatiti da ove 2 zone imaju iste granice. rate
Postavka se koristi Nginx izračunati frekvenciju: što je vremenski interval prije prihvatiti novi zahtjev? NGINX će primijeniti algoritam nepropusnog segmenta s ovom brzinom osvježavanja tokena.
Za Nginx, 300r/m
te 5r/s
se tretiraju na isti način: dopustiti jedan zahtjev za svakih 0,2 sekundi za tu zonu. U ovom slučaju, svake 0,2 sekunde, NGINX će postaviti zastavicu kako bi zapamtio da može prihvatiti zahtjev. Kada dođe zahtjev koji stane u ovu zonu, NGINX postavlja zastavicu na false i obrađuje je. Ako se pojavi još jedan zahtjev prije nego što tajmer otkuca, bit će odbijen odmah s statusnim kodom 503. Ako timer otkuca i zastavica je već postavljena da prihvaća zahtjev, ništa se neće promijeniti.
Trebate li ograničenje brzine ili oblikovanje prometa?
Unesite burst
parametar. Da biste ga razumjeli, zamislite da zastavica koju smo gore objasnili više nije logička vrijednost, već cijeli broj: maksimalan broj zahtjeva koje NGINX može dopustiti u nizu.
Ovo više nije algoritam nepropusnog segmenta, već token toketa. U rate
kontrolira koliko brzo timer krpelja, ali to više nije točno / netočno znak, ali brojač ide od 0
do 1+burst value
. Svaki put kad tajmer otkuca, brojač se uvećava, osim ako već nije na svojoj maksimalnoj vrijednosti b+1
. Sada biste trebali razumjeti zašto je burst
postavka vrijednost, a ne stopa.
Kada stigne novi zahtjev, NGINX provjerava je li token dostupan (tj. Brojač je> 0), ako nije, zahtjev se odbija. Ako postoji žeton, tada se zahtjev prihvaća i bit će tretiran, a taj će se žeton potrošiti (brojač se smanjuje).
Ok, tako da će NGINX prihvatiti zahtjev ako je dostupan burst token. Ali kada će NGINX obraditi ovaj zahtjev?
Zatražili ste od NGINX-a da primijeni maksimalnu stopu od 5r/s
, NGINX prihvaća prekoračene zahtjeve ako su dostupni burst tokeni, ali pričekat će da ih neka soba obradi unutar tog ograničenja maksimalne brzine. Stoga će se ovi rafalni zahtjevi obraditi s određenim kašnjenjem ili će doći do isteka vremena.
Drugim riječima, NGINX neće prijeći ograničenje brzine postavljeno u deklaraciji zone, pa će stoga staviti dodatne zahtjeve u red i obrađivati ih s određenim kašnjenjem, budući da token-timer otkucava i prima manje zahtjeva.
Da se poslužimo jednostavnim primjerom, recimo da imate stopu 1r/s
i rafal 3
. NGINX istovremeno prima 5 zahtjeva:
- Prva se prihvaća i obrađuje
- Budući da dopuštate 1 + 3, postoji 1 zahtjev koji se odmah odbija sa statusnim kodom 503
- Još će se 3 liječiti, jedan po jedan, ali ne odmah. Tretirat će se po stopi
1r/s
da ostanu u granicama koje ste postavili. Ako ne stigne nijedan drugi zahtjev, već trošite ovu kvotu. Nakon što se red isprazni, brojač praska ponovno će se početi povećavati (segment tokena počinje se ponovno puniti)
Ako koristite NGINX kao proxy, uzvodno će zahtjev primiti maksimalnom brzinom 1r/s
i neće biti svjestan niza pristiglih zahtjeva, sve će biti ograničeno tom brzinom.
Upravo ste napravili neko oblikovanje prometa, uveli ste neko kašnjenje kako biste regulirali praske i stvorili pravilniji tok izvan NGINX-a.
Unesite nodelay
nodelay
kaže NGINX-u da zahtjeve koje prihvati u probojnom prozoru treba odmah obraditi, poput uobičajenih zahtjeva.
Kao posljedica, skokovi će se proširiti na NGINX uzvodno, ali s određenim ograničenjem, definiranim burst
vrijednošću.
Vizualizacija ograničenja brzine
Budući da vjerujem da je najbolji način da se to zapamtim iskusiti na praktičan način, postavio sam malu Dockerovu sliku s NGINX konfiguracijom izlažući različite postavke ograničenja brzine kako bih vidio odgovore na osnovno ograničeno mjesto, na burst
-omogućena lokacija burst
s nodelay
ograničenom stopom , a na lokaciji s ograničenom stopom, poigrajmo se s tim.
Ovi uzorci koriste ovu jednostavnu konfiguraciju NGINX (za koju ćemo na kraju ovog posta pružiti Dockerovu sliku kako biste je lakše mogli testirati):
limit_req_zone $request_uri zone=by_uri:10m rate=30r/m; server { listen 80; location /by-uri/burst0 { limit_req zone=by_uri; try_files $uri /index.html; } location /by-uri/burst5 { limit_req zone=by_uri burst=5; try_files $uri /index.html; } location /by-uri/burst5_nodelay { limit_req zone=by_uri burst=5 nodelay; try_files $uri /index.html; } }
Počevši od ove konfiguracije, svi dolje navedeni uzorci odjednom će poslati 10 istodobnih zahtjeva. Da vidimo:
- koliko ih odbije ograničenje stope?
- koja je brzina obrade prihvaćenih?
Sending 10 parallel requests to a rate-limited endpoint

That config allows 30 requests per minute. But 9 out of 10 requests are rejected in that case. If you followed the previous steps, this should make sense: The 30r/m
means that a new request is allowed every 2 seconds. Here 10 requests arrive at the same time, one is allowed, the 9 other ones are seen by NGINX before the token-timer ticks, and are therefore all rejected.
But I’m OK to tolerate some burst for some client/endpoints
Ok, so let’s add the burst=5
argument to let NGINX handle small bursts for this endpoint of the rate-limited zone:

What’s going on here? As expected with the burst
argument, 5 more requests are accepted, so we went from 1 /10 to 6/10 success (and the rest is rejected). But the way NGINX refreshed its token and processed the accepted requests is quite visible here: the outgoing rate is capped at 30r/m
which is equivalent to 1 request every 2 seconds.
The first one is returned after 0.2 seconds. The timer ticks after 2 seconds, and one of the pending requests is processed and returned, with a total roundtrip time of 2.02 seconds. 2 seconds later, the timer ticks again, processing another pending request, which is returned with a total roundtrip time of 4.02 seconds. And so on and so forth…
The burst
argument just lets you turn NGINX rate-limit from some basic threshold filter to a traffic shaping policy gateway.
My server has some extra capacity. I want to use a rate-limit to prevent it from going over this capacity.
In this case, the nodelay
argument will be helpful. Let’s send the same 10 requests to a burst=5 nodelay
endpoint:

As expected with the burst=5
we still have the same number of status 200 and 503. But now the outgoing rate is no longer strictly constrained to the rate of 1 requests every 2 seconds. As long as some burst tokens are available, any incoming request is accepted and processed immediately. The timer tick rate is still as important as before to control the refresh/refill rate of these burst tokens, but accepted requests no longer suffer any additional delay.
Note: in this case, the zone
uses the $request_uri
but all the following tests work exactly the same way for a $binary_remote_addr
config which would rate-limit by client IP. You’ll be able to play with this in the Docker image.
Let’s recap
If we try to visualize how NGINX accepts the incoming requests, then processes them depending on the rate
, burst
, and nodelay
parameter, here’s a synthetic view.
To keep things simple, we’ll show the number of incoming requests (then accepted or rejected, and processed) per time step, the value of the time step depending on the zone-defined rate limit. But the actual duration of that step doesn’t matter in the end. What is meaningful is the number of requests NGINX has to process within each of these steps.
So here is the traffic we’ll send through various rate limit settings:


Without using the burst (i.e. burst=0
), we saw that NGINX acts as a pure rate-limit/traffic-policy actor. All requests are either immediately processed, if the rate timer has ticked, or immediately rejected otherwise.
Now if we want to allow the small burst to use the unused capacity under the rate-limit, we saw that adding a burst
argument lets use do that, which implies some additional delay in processing the requests consuming the burst tokens:

We can see that the overall number of rejected requests is lower, and NGINX processes more requests. Only the extra requests when no burst tokens are available are rejected. In this setup, NGINX performs some real traffic-shaping.
Finally, we saw that NGINX can be used to either do some traffic-policy or to limit the size of the burst, but still propagates some of these bursts to the processing workers (upstreams or local), which, in the end, does generate less stable outgoing rate, but with a better latency, if you can process these extra requests:

Playing with the rate limit sandbox yourself
Now you can go explore the code, clone the repo, play with the Docker image, and get your hands on it real quick to better solidify your understanding of these concepts. //github.com/sportebois/nginx-rate-limit-sandbox
Update (June 14th, 2017)
NGINX published a few days ago their own detailed explanation of their rate-limiting mechanism. You can now learn more about it in their Rate Limiting with NGINX and NGINX Plus blog post.