この記事ではAWS ec2上のCentOS7にインストールしたnginx 1.10.2を使っています。go1.7を追加しました。
連載一覧
[第1回 REST-WebAPIの特徴に合わせてチューニングしよう] (http://qiita.com/toast-uz/items/828bbb48d58eb65f1f9f)
[第2回 nginx基本チューニング(有効なのはtcp_nopushだけ)] (http://qiita.com/toast-uz/items/48754a95aa0c60db67f5)
[第3回 バックエンドはコネクションプーリングで] (http://qiita.com/toast-uz/items/b459100189efb995b9c5)
[第4回 DB接続もコネクションプーリングで] (http://qiita.com/toast-uz/items/9c1b97bee8f4a7c13bfc)
今回からは、testサーバ〜webサーバ〜apサーバの構成にして、apサーバにgo1.7をインストールしました。全てt2.microを利用しています。
goのインストールについては、How To Install Go 1.7 on CentOS 7を参考にしました。
1. 準備
nginxのバックエンド接続は、FastCGI方式、http_proxy方式があり、さらにhttp_proxy方式はkeepaliveありなしの区別があります。この3方式のapサーバを準備します。また、比較のためにwebサーバのデフォルトindex.htmlも参照できるようにするとともに、3方式のapサーバは、いずれも、index.htmlと同じコンテンツを返すようにします。
これらの3方式+1(index.html参照)が、任意に試せるような環境構築をします。
1.1 testサーバ
忘れずに遅延印加しておきます。
$ sudo tc qdisc add dev eth0 root netem delay 100ms
1.2 webサーバ(nginx.conf)
3方式+1を一度に表現します。
アクセス先 | 動作 |
---|---|
/ | index.html参照 |
/api1 | FastCGI接続 |
/api2 | http_proxy接続 |
/api2k | http_proxy接続(KeepAliveあり) |
nginx.confは第2回までの内容として、nginx.confにincludeされているdefault.confを修正することで、上記を実現します。
upstream api1 {
server 172.31.24.119:9000;
}
upstream api2 {
server 172.31.24.119:8080;
}
upstream api2k {
server 172.31.24.119:8080;
keepalive 100;
}
server {
# 中略(元々記載されている内容 ※index.htmlを含む)
location /api1 {
fastcgi_pass api1;
include fastcgi_params;
}
location /api2 {
proxy_pass http://api2/;
}
location /api2k {
proxy_pass http://api2k/;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
なお、このまま実行すると、SELinuxに起因するセキュリティにより、http_proxyが動作しません。以下の設定により、動作できるようになります。
# setsebool -P httpd_can_network_relay on
1.3 apサーバ(Go言語)
apサーバのファイルディスクリプタを増やしておきます。これをしないと、同時接続が1000を超えたあたりで、アプリがダウンします。
$ sudo vi /etc/security/limits.conf
/* 以下を追記 */
* soft nofile 64000
* hard nofile 64000
$ sudo reboot now
FastCGI用のアプリapi1.goと、HTTP用のアプリapi2.goを作成します。
package main
import (
"fmt"
"net"
"net/http"
"net/http/fcgi"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `
/* nginxのデフォルトのindex.htmlを貼り付ける*/
`)
}
func main() {
l, err := net.Listen("tcp", ":9000")
if err != nil {
return
}
http.HandleFunc("/", handler)
fcgi.Serve(l, nil)
}
package main
import (
"fmt"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `
/* nginxのデフォルトのindex.htmlを貼り付ける*/
`)
}
func main() {
http.HandleFunc("/", handler)
err := http.ListenAndServe(":8080", nil);
if err != nil {
return
}
}
以下により、コンパイルして実行させておきます。
$ go build api1.go
$ go build api2.go
$ ./api1 &
$ ./api2 &
2. 測定結果
testサーバでabを実行させます。
$ ab -c 10000 -n 1000000 -r http://172.31.20.18/
$ ab -c 10000 -n 1000000 -r http://172.31.20.18/api1
$ ab -c 10000 -n 1000000 -r http://172.31.20.18/api2
$ ab -c 10000 -n 1000000 -r http://172.31.20.18/api2k
測定結果を以下に示します。
アクセス先 | 動作 | Requests/sec | エラー数 |
---|---|---|---|
/ | index.html参照 | 2,530 | 1,362 |
/api1 | FastCGI接続 | 2,097 | 2,765 |
/api2 | http_proxy接続 | 1,813 | 9,843 |
/api2k | http_proxy接続(KeepAliveあり) | 3,845 | 1,467 |
なんと、この条件では、webサーバのindex.htmlを参照するよりも、http_proxy(KeepAliveあり)でapサーバと通信した方が、速くなりました。
さらに、結果を分かりやすくするために、testサーバ〜webサーバのボトルネックの影響を外して、webサーバローカルでabをkeepaliveありでも測定してみます。
$ ab -c 100 -n 1000000 -k http://172.31.20.18/
$ ab -c 100 -n 1000000 -k http://172.31.20.18/api1
$ ab -c 100 -n 1000000 -k http://172.31.20.18/api2
$ ab -c 100 -n 1000000 -k http://172.31.20.18/api2k
測定結果を以下に示します。
アクセス先 | 動作 | Requests/sec | エラー数 |
---|---|---|---|
/ | index.html参照 | 38,754 | 0 |
/api1 | FastCGI接続 | 2,251 | 0 |
/api2 | http_proxy接続 | 2,150 | 0 |
/api2k | http_proxy接続(KeepAliveあり) | 10,378 | 0 |
さすがに、今度は、index.html参照が最速でしたが、これは比較対象用なので、apサーバとの通信に限定すると、http_proxy(KeepAliveあり)が最速となります。
以上の結果により、http_proxyでkeepaliveした状態が、明らかに性能が出ることがわかりました。keepaliveによりコネクションプーリング状態になり、コネクション接続切断のオーバーヘッドが無くなるためと考えられます。
なお、keepalive設定の数値は、コネクションプーリングの最小数ですが、最大値ではありません。最大値は別途、max_connsにより制限をする必要があります。詳細は、ngx_http_upstream_module モジュールを参照してください。