Opened 6 years ago

Closed 6 years ago

#1603 closed enhancement (invalid)

ngx_http_limit_req_module 模块限制不准确

Reported by: huifeidexingyuner@… Owned by:
Priority: critical Milestone:
Component: other Version: 1.13.x
Keywords: Cc:
uname -a:
nginx -V: 1.13.6

Description

limit_req_zone $binary_remote_addr zone=qps1:1m rate=1000r/s;

location / {

limit_req zone=qps1 burst=1000;
root /home/soft/www/;
index index.html index.htm index.php;
try_files $uri $uri/ =404;

}

这样配置,访问一个首页为31个请求的页面,查看后台日志如下:
2018/08/02 11:18:47 [warn] 52328#0: *4 delaying request, excess: 1.000, by zone "qps1", client: 10.50.10.88, server: , request: "GET /mutillidae/styles/ddsmoothmenu/ddsmoothmenu-v.css HTTP/1.1", host: "10.50.10.87", referrer: "http://10.50.10.87/mutillidae/"
2018/08/02 11:18:47 [warn] 52328#0: *3 delaying request, excess: 2.000, by zone "qps1", client: 10.50.10.88, server: , request: "GET /mutillidae/styles/ddsmoothmenu/ddsmoothmenu.css HTTP/1.1", host: "10.50.10.87", referrer: "http://10.50.10.87/mutillidae/"
2018/08/02 11:18:47 [warn] 52328#0: *4 delaying request, excess: 1.000, by zone "qps1", client: 10.50.10.88, server: , request: "GET /mutillidae/images/pdf-icon-48-48.png HTTP/1.1", host: "10.50.10.87", referrer: "http://10.50.10.87/mutillidae/"
2018/08/02 11:18:47 [warn] 52327#0: *1 delaying request, excess: 2.000, by zone "qps1", client: 10.50.10.88, server: , request: "GET /mutillidae/images/new-icon-48-48.png HTTP/1.1", host: "10.50.10.87", referrer: "http://10.50.10.87/mutillidae/"
2018/08/02 11:18:47 [warn] 52328#0: *4 delaying request, excess: 1.000, by zone "qps1", client: 10.50.10.88, server: , request: "GET /mutillidae/images/question-mark-40-61.png HTTP/1.1", host: "10.50.10.87", referrer: "http://10.50.10.87/mutillidae/"
2018/08/02 11:18:47 [warn] 52328#0: *6 delaying request, excess: 1.000, by zone "qps1", client: 10.50.10.88, server: , request: "GET /mutillidae/images/installation-icon-48-48.png HTTP/1.1", host: "10.50.10.87", referrer: "http://10.50.10.87/mutillidae/"
2018/08/02 11:18:47 [warn] 52328#0: *5 delaying request, excess: 1.000, by zone "qps1", client: 10.50.10.88, server: , request: "GET /mutillidae/images/help-icon-48-48.png HTTP/1.1", host: "10.50.10.87", referrer: "http://10.50.10.87/mutillidae/"
2018/08/02 11:18:47 [warn] 52328#0: *3 delaying request, excess: 1.000, by zone "qps1", client: 10.50.10.88, server: , request: "GET /mutillidae/javascript/jQuery/jquery.balloon.js HTTP/1.1", host: "10.50.10.87", referrer: "http://10.50.10.87/mutillidae/"
2018/08/02 11:18:48 [warn] 52328#0: *6 delaying request, excess: 1.000, by zone "qps1", client: 10.50.10.88, server: , request: "GET /mutillidae/javascript/jQuery/colorbox/images/controls.png HTTP/1.1", host: "10.50.10.87", referrer: "http://10.50.10.87/mutillidae/javascript/jQuery/colorbox/colorbox.css"

说明nginx延迟了部分请求,太不准确了,理论上1秒钟并发只有31而已。

Change History (10)

comment:1 by Sergey Kandaurov, 6 years ago

请用英语

comment:2 by Maxim Dounin, 6 years ago

Resolution: wontfix
Status: newclosed

Since limit_req limits instantaneous request rate, and time is tracked by nginx with one millisecond resolution, with rate=1000r/s it is possible that some requests will be delayed even if the average request rate is less than 1000r/s. This is because if two requests come within a single millisecond, the instantaneous request rate will be infinite, and to keep the rate under 1000r/s nginx will have to delay the second request till the next millisecond.

If you don't want requests to be delayed, consider using limit_req ... nodelay, see docs for details.

in reply to:  2 comment:3 by huifeidexingyuner@…, 6 years ago

Why doesn't nginx calculate how many requests come in a second?By doing so, you can also prevent cc attacks by simply rejecting the extra requests for a second. It's easier to understand。

Last edited 6 years ago by huifeidexingyuner@… (previous) (diff)

comment:4 by huifeidexingyuner@…, 6 years ago

Resolution: wontfix
Status: closedreopened

Why doesn't nginx calculate how many requests come in a second?By doing so, you can also prevent cc attacks by simply rejecting the extra requests for a second. It's easier to understand。

Last edited 6 years ago by huifeidexingyuner@… (previous) (diff)

comment:5 by Maxim Dounin, 6 years ago

Resolution: invalid
Status: reopenedclosed

While it might be easier to understand, counting requests in a second has various downsides:

  • There are edge cases which allow much higher rates than configured. For example, with rate=1000r/s one can do 1000 requests in one millisecond, or even 2000 requests in just 2 milliseconds if these two milliseconds are carefully choosen. This is 1000 times higher rate than configured, and can be easily enough to overload the backend server.
  • This scales badly to different periods of time. For example, to limit things to 1000 requests per minute, one have to either allow (again) 2000 requests in just 2 milliseconds on the edges (this will be 60000 times higher rate than configured), or maintain request counts for last 60 seconds.

Moreover, this generally won't allow to delay requests properly, but will require to reject them (as previously suggested, using limit_req ... nodelay will do the same if you need to only reject requests without delaying them). Also, it won't be able to tolerate traffic bursts, which is often a required feature.

Due to the above downsides, nginx instead uses the Leaky bucket algorithm to limit requests. This algorithm allows maintaining configured request rate with very effectively, without the mentioned edge effects, can be configured to tolerate traffic bursts, and can be configured to delay requests down to the specified rate. If you want to better understand the algorithm, consider reading this Wikipedia article:

https://en.wikipedia.org/wiki/Leaky_bucket

in reply to:  5 ; comment:6 by huifeidexingyuner@…, 6 years ago

Resolution: invalid
Status: closedreopened

Leaky_bucket aside, this algorithm is classic。
If I just want to reject more than 1000 requests in a second。I have my doubts about your two downsides:
(1)2000 requests per millisecond, which also mutexes the req_count(ngx_shmtx_lock), so that if only 1,000 requests are allowed per second, the next 1,000 will be rejected。And the update request count is as follows:

If (now-last)/1000*limit_count is greater than req_count, then req_count=0; otherwise, req_count-(now-last)/1000*limit_count 。 Is that wrong?

(2)Is it because (now-last) time results may be too short,one millisecond of concurrency is too much and this one-second calculation may not work and result in server overload?

It doesn't feel like it. If 10,000 requests come in a millisecond, I'll reject the remaining 9,000.

Last edited 6 years ago by huifeidexingyuner@… (previous) (diff)

in reply to:  6 comment:7 by Maxim Dounin, 6 years ago

Resolution: invalid
Status: reopenedclosed

If I just want to reject more than 1000 requests in a second。I have my doubts about your two downsides:
(1)2000 requests per millisecond, which also mutexes the req_count(ngx_shmtx_lock), so that if only 1,000 requests are allowed per second, the next 1,000 will be rejected。And the update request count is as follows:

If (now-last)/1000*limit_count is greater than req_count, then req_count=0; otherwise, req_count-(now-last)/1000*limit_count 。 Is that wrong?

If you'll count requests in a second, as you suggested in the comment:4, these calculations won't apply. Instead, you'll have to count all requests which come in the second N, and then reset the counter when current time changes to the next second, N+1s. While this approach might be easier to understand, it has an edge when the N changes to (N+1s). Consider that 1000 requests come at (N+999ms) and then additional 1000 requests come at (N+1000ms). All requests will be allowed - because the counter is reset at (N+1000ms). These 2000 requests will be processed in just 2 milliseconds, so this corresponds to 1000 requests per millisecond rate. This is 1000 times higher than the rate configured, and can overload the backend server.

in reply to:  4 comment:8 by Valentin V. Bartenev, 6 years ago

Replying to huifeidexingyuner@…:

Why doesn't nginx calculate how many requests come in a second?By doing so, you can also prevent cc attacks by simply rejecting the extra requests for a second. It's easier to understand。

Think about it like a speed limit. When speed limit is 80 km/h, that doesn't mean that you have to drive 1 hour before you get a fine. You may get a fine as soon as your current speed exceed this limit.

comment:9 by huifeidexingyuner@…, 6 years ago

Resolution: invalid
Status: closedreopened

Thank you,I see.

comment:10 by Maxim Dounin, 6 years ago

Resolution: invalid
Status: reopenedclosed
Note: See TracTickets for help on using tickets.