※この記事はnginxの現時点での最新stable 0.8.54を使っています。
2回目の投稿になります、sugyanです こんにちは。
最近、jsdo.itでちょっとしたAPIを作ってみているのですが、連続で大量のリクエストが来るのはちょっと困るので、防御策としてnginxのリクエスト制御モジュール"HttpLimitReqModule"を導入してみることにしました。
http://wiki.nginx.org/HttpLimitReqModule
何も設定しない場合
まずは普通のnginx設定でhttpサーバを立ち上げて、動かしてみます。
worker_processes 1;
error_log logs/error.log info;
events {
worker_connections 256;
}
http {
log_format test '$remote_addr - [$time_local] "$request" $status "$http_user_agent" $request_time';
access_log logs/access.log test;
server {
listen 80;
server_name localhost;
}
}
パフォーマンス測定にはhttp_loadコマンドを使ってみます。
http://acme.com/software/http_load/
$ cat urls
http://localhost/
$ http_load -parallel 10 -fetches 1000 urls
1000 fetches, 10 max parallel, 151000 bytes, in 0.258776 seconds
151 mean bytes/connection
3864.35 fetches/sec, 583516 bytes/sec
msecs/connect: 0.132611 mean, 0.44 max, 0.05 min
msecs/first-response: 2.44852 mean, 31.4 max, 0.285 min
HTTP response codes:
code 200 -- 1000
あっという間に1000リクエストくらい捌いてしまいますね。
limit_reqを設定する
設定ファイルのhttpディレクティブに、HttpLimitReqModuleの設定を追加してみます。
http {
log_format test '$remote_addr - [$time_local] "$request" $status "$http_user_agent" $request_time';
access_log logs/access.log test;
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
limit_req_log_level info;
server {
listen 80;
server_name localhost;
location = / {
limit_req zone=one;
}
}
}
$ http_load -parallel 10 -fetches 1000 urls 2> /dev/null
1000 fetches, 10 max parallel, 212938 bytes, in 0.068365 seconds
212.938 mean bytes/connection
14627.4 fetches/sec, 3.11472e+06 bytes/sec
msecs/connect: 0.215024 mean, 0.574 max, 0.05 min
msecs/first-response: 0.448167 mean, 1.291 max, 0.173 min
999 bad byte counts
HTTP response codes:
code 200 -- 1
code 503 -- 999
処理自体はすぐに終わりますが、1回だけ200が返り 残りのリクエストはすべて503になっています。error.logは以下のように出力されました。
2011/03/25 17:03:35 [info] 6607#0: *2 limiting requests, excess: 1.000 by zone "one", client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.0", host: "localhost"
2011/03/25 17:03:35 [info] 6607#0: *3 limiting requests, excess: 1.000 by zone "one", client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.0", host: "localhost"
2011/03/25 17:03:35 [info] 6607#0: *4 limiting requests, excess: 1.000 by zone "one", client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.0", host: "localhost"
...
"zone=one"で指定した通りに制限されているのが確認できますね。試しに3秒間リクエストを送り続けてみます。
$ http_load -parallel 10 -seconds 3 urls 2> /dev/null
16254 fetches, 10 max parallel, 3.4617e+06 bytes, in 3.00011 seconds
212.975 mean bytes/connection
5417.8 fetches/sec, 1.15386e+06 bytes/sec
msecs/connect: 0.783652 mean, 980.029 max, 0.043 min
msecs/first-response: 0.548379 mean, 123.302 max, 0.059 min
16251 bad byte counts
HTTP response codes:
code 200 -- 3
code 503 -- 16250
"rate=1r/s"(秒間1リクエスト)に対し3秒間なので3回だけ200が返り、あとはやはりすべて503です。
limit_reqのburstを設定する
"limit_req"の設定では"burst"という値を指定できます。制限を超える数のリクエストが来た際にその値の数だけ遅延してレスポンスを返すようにしてくれるようです。デフォルト値は0です。
limit_req zone=one burst=5;
$ http_load -parallel 10 -fetches 1000 urls 2> /dev/null
1000 fetches, 10 max parallel, 212628 bytes, in 5.00064 seconds
212.628 mean bytes/connection
199.974 fetches/sec, 42520.2 bytes/sec
msecs/connect: 0.138747 mean, 0.357 max, 0.064 min
msecs/first-response: 15.1978 mean, 5000.32 max, 0.072 min
994 bad byte counts
HTTP response codes:
code 200 -- 6
code 503 -- 994
200が返ってくる回数が増えました。error.logは以下のようになっています。
2011/03/25 17:37:49 [info] 7316#0: *2 delaying request, excess: 1.000, by zone "one", client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.0", host: "localhost"
2011/03/25 17:37:49 [info] 7316#0: *3 delaying request, excess: 2.000, by zone "one", client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.0", host: "localhost"
2011/03/25 17:37:49 [info] 7316#0: *4 delaying request, excess: 3.000, by zone "one", client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.0", host: "localhost"
2011/03/25 17:37:49 [info] 7316#0: *5 delaying request, excess: 3.999, by zone "one", client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.0", host: "localhost"
2011/03/25 17:37:49 [info] 7316#0: *6 delaying request, excess: 4.999, by zone "one", client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.0", host: "localhost"
2011/03/25 17:37:49 [info] 7316#0: *7 limiting requests, excess: 5.999 by zone "one", client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.0", host: "localhost"
2011/03/25 17:37:49 [info] 7316#0: *8 limiting requests, excess: 5.999 by zone "one", client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.0", host: "localhost"
...
制限を超えていても最初の5リクエストはいきなり503を返さず、レスポンスを遅延させているようです。access.logを見ると遅延して返ってきているのが確認できます。
...
127.0.0.1 - [25/Mar/2011:17:37:49 +0900] "GET / HTTP/1.0" 503 "http_load 12mar2006" 0.000
127.0.0.1 - [25/Mar/2011:17:37:49 +0900] "GET / HTTP/1.0" 503 "http_load 12mar2006" 0.000
127.0.0.1 - [25/Mar/2011:17:37:49 +0900] "GET / HTTP/1.0" 503 "http_load 12mar2006" 0.000
127.0.0.1 - [25/Mar/2011:17:37:50 +0900] "GET / HTTP/1.0" 200 "http_load 12mar2006" 1.000
127.0.0.1 - [25/Mar/2011:17:37:51 +0900] "GET / HTTP/1.0" 200 "http_load 12mar2006" 2.001
127.0.0.1 - [25/Mar/2011:17:37:52 +0900] "GET / HTTP/1.0" 200 "http_load 12mar2006" 3.000
127.0.0.1 - [25/Mar/2011:17:37:53 +0900] "GET / HTTP/1.0" 200 "http_load 12mar2006" 3.999
127.0.0.1 - [25/Mar/2011:17:37:54 +0900] "GET / HTTP/1.0" 200 "http_load 12mar2006" 4.999
nodelay
burst値指定の後に"nodelay"をつけるとこの遅延がなくなり、すぐにレスポンスが返ってくるようになります。
limit_req zone=one burst=5 nodelay;
$ http_load -parallel 10 -fetches 1000 urls 2> /dev/null
1000 fetches, 10 max parallel, 212628 bytes, in 0.066109 seconds
212.628 mean bytes/connection
15126.5 fetches/sec, 3.21632e+06 bytes/sec
msecs/connect: 0.226904 mean, 0.496 max, 0.052 min
msecs/first-response: 0.410101 mean, 1.007 max, 0.142 min
994 bad byte counts
HTTP response codes:
code 200 -- 6
code 503 -- 994
結果は先ほどと同じですが、レスポンスは一瞬で返ってきています。わざわざ遅延させたくない、5回くらいの制限オーバーは大目に見る、というときに使えば良いのでしょうか。
まとめ
HttpLimitReqModuleを使うことで受け付けるリクエスト量を制限できることが確認できました。APIを提供するサーバのフロントなどで使用すると有用だと思います。
デフォルトでこんな便利機能が付いているnginx、優秀ですね。