nginxのHttpLimitReqModuleを試してみる

※この記事は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、優秀ですね。


カヤックではnginxを使いこなせる技術者も募集しています!