夏休みの宿題/SO_REUSEPORTを使ってみた

昨日作った宿題のランダムな数字を返すデーモンですが、ちょっと困った問題があります。
それは、同時に複数のリクエストを処理できないこと。

もったいぶって待つ秒数が0.2秒なのでわかりにくいですが、例えば5クラアントが同時にアクセスすると、
最初に接続されたクライアントが0.2秒後にレスポンスを受け取り切断、
次のクライアントが接続し0.2秒後にレスポンスを受け取り切断、
…..
…..
5番目のクライアントが接続し0.2秒後にレスポンスを受け取り切断。

結果として1秒間にどう頑張っても5リクエストしか処理できないのです。

5並列で合計100リクエスト投げた場合にかかる時間はこんな感じ。

1
2
3
4
5
6
7
[rhykw@builder11 ~]$ time ( n=0;while [[ $((n++)) -lt 100 ]];do echo 1;done|xargs -I% -n1 -P5 nc 127.0.0.1 8888 > log.txt)

real 0m20.027s
user 0m0.031s
sys 0m0.164s
[rhykw@builder11 ~]$ wc -l log.txt
100 log.txt

ほぼ計算上の数字通り、という感じです。
今回はサーバーがクライアントへランダムな数字を送りつけるだけなのであまり問題は無いですが
これがHTTPのようなプロトコルなら大問題。

誰かがアクセスしている間は他のクライアントは待たされる、では困ります。

で、教科書的な実装については68userさんのネットワークプログラミングの基礎知識といったサイトで言及されているように
selectなりforkなりを使って同時処理を受付出来るようにするのが良いと思います。

が、、、
今日はあえて以前 SO_REUSEADDR を調べた際に気になっていた SO_REUSEPORT を実際に使ってみます。

ざっっっっくり言うと SO_REUSEPORT は、既に誰かがListenしているポートを別のプロセスもListenするときに使うフラグ。
最初にListenした誰かも SO_REUSEPORT である必要がある。

変更後のソースは rndserver2.c

実質追加したのは下記の部分です。

1
2
3
#ifdef SO_REUSEPORT
setsockopt(server_fd, SOL_SOCKET, SO_REUSEPORT, (const char *)&optval, sizeof(optval));
#endif

併せて、ホントに複数のプロセスに処理が分散されているか確認しやすくするため、
ランダムな数字の前に、プロセスIDも出力します。

で、測定結果。

まずサーバー側を起動。xargsで10プロセス立ち上げ。

1
2
3
4
5
6
7
8
9
10
11
[rhykw@builder11 tmp]$ n=0;while [[ $((n++)) -lt 10 ]];do echo 1;done|xargs -I% -n1 -P10 ./rndserver2
pid=1598
pid=1599
pid=1600
pid=1604
pid=1605
pid=1601
pid=1606
pid=1607
pid=1602
pid=1603

次にクライアント側。まず5並列。

1
2
3
4
5
[rhykw@builder11 ~]$ time ( n=0;while [[ $((n++)) -lt 100 ]];do echo 1;done|xargs -I% -n1 -P5 nc 127.0.0.1 8888 > log.txt)

real 0m4.846s
user 0m0.043s
sys 0m0.151s

出力も見てみます。
リクエスト数が少ないので少しばらつきがありますが分散されているのがわかります。

1
2
3
4
5
6
7
8
9
10
11
[rhykw@builder11 ~]$ awk -F, '{print $1}' log.txt|sort|uniq -c
17 1598
7 1599
10 1600
12 1601
11 1602
10 1603
11 1604
5 1605
8 1606
9 1607

今回は、雑な例を挙げちゃいましたが、うまく使えばマルチスレッド化されているデーモンでも
リロード/リスタートの際に新しいプロセスを追加起動し、古い方のデーモンはkillする、といった
使い方が出来るのかもしれません。

もう少し触ってみようと思います。

このエントリーをはてなブックマークに追加