Apacheのロードバランシングモジュールである mod_proxy_balancer
の振り分けアルゴリズムの bybusyness
について調べた内容です。
byrequestsとbybusyness
語弊を恐れずに1行で書けばいかのような感じかと思います。
メソッド | 1行説明 |
---|---|
byrequests | ワーカー毎の処理リクエスト数が均等になるように配分 |
bybusyness | ワーカー毎の処理キュー長が均等になるように配分 |
bybusynessの使いどころ
シンプルなサーバで、常に安定して同じような時間で処理をできる類いのシステムなら byrequests
でも問題無いと思われます。
bybusyness
が有用だと思われるケースとして以下の二つを挙げてみます。
特定サーバが遅くなっている場合 複数の役割をこなしているサーバなら裏の処理の影響がある時など、単一の役割のサーバでもH/Wの不具合等の影響がある時など、特定のサーバが遅延してしまう時、単純にリクエスト数で配分されてしまうと非効率な場合があります。
・・・・・・・・・・・・・・・・・・・・・・・・・・・
━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━
以下のようにリクエスト毎に処理時間がバラバラだった場合、
━━・・━・━━━・・━・━・・・・━━━━━・━・・・
単純にリクエスト数で振り分けた場合、特定のWEBサーバにだけ長い処理がかかるが振られてしまい処理効率が悪くなってしまう場合があります。
例えばこれらシナリオの場合に bybusyness
が役立つ場合があります。
検証による動きの比較
イメージつけるために実際に動かしてみます。デフォルトの振り分け方式である byrequests
との動きを比較しながら、上記のbybusyness
が役に立つ可能性があるシナリオを検証してみます。
以下のように簡易な検証できる環境を構築してみます。
LB (localhost:8080) ├1号機 (localhost:55081) └2号機 (localhost:55082)
シナリオ① 特定のサーバが遅くなる時を再現
まずは片方のサーバが重かった時の動きを簡易再現してみます。複数の役割をこなしているサーバなら裏の処理の影響があったり、単一の役割のサーバでも単純にシステムリソースの障害等で同じ状況は発生する可能性があるかと思います。
シナリオ① 検証環境
- 1号機の処理 こちらは通常のサーバを再現しています。
<?php echo "1号機"; ?>
- 2号機の処理 逆にこちらは重いサーバを再現しています。
<?php sleep(5); echo "2号機"; ?>
abコマンド使ってテストしてみます。
ab -c 10 -n 50 http://localhost:8080/index.php
シナリオ① 結果(byrequests)
まず byrequests
の結果から。
50件のリクエストが1号機と2号機に均等に25件ずつ割り振られているのがわかります。
abコマンドのの実行結果を見ると、早い1号機にも遅い2号機にも同じだけ割り振られてしまったため、全体としての処理時間も大きくなってしまっています。
Time taken for tests: 27.430 seconds
シナリオ① 結果(bybusyness)
次に bybusyness
です。今度は早い1号機側に多く割り振られています。
※この画面はbalancer-managerのものなのですが、Loadの項目が-40
と 40
という数字になっています。これは後述するlbstatus
という変数の値なのですが、 bybusyness
アルゴリズムの場合はほぼ関係無い(ゼロでは無い)ので、ここでは無視して問題ありません。
abコマンドのの実行結果を見ても byrequests
よりも効率良く処理されているのがわかります。
Time taken for tests: 5.018 seconds
シナリオ② リクエスト毎の処理時間にバラつきがある時
シナリオ② 検証環境
今度は1号機と2号機共通で wait
パラメータをURLに渡すことで処理時間のバラツキを再現してみます。
<?php if(isset($_GET['wait'])) { $wait = $_GET['wait']; sleep($wait); } echo "1号機"; ?>
WAIT時間として異なるURLパラメータを渡したいので拡張abコマンドでテストします。
abコマンドの拡張方法などはこちらに。
Apache Bench(abコマンド)で変数使ったり複数種類のURLリクエストを送りたい - YOMON8.NET
以下の list.txt
ファイルを使って、待ち時間がバラバラにしてリクエストを投げてみます。
0 0 1 0 2 0 5 0 2 1
abコマンドを拡張した -R
オプションを使います。
ab -c 10 -n 50 -R list.txt http://localhost:8080/index.php?wait=
シナリオ② 結果(byrequests)
byrequestsの場合は変わらずリクエスト数で振り分けます。
処理毎にレスポンスまでの処理時間が異なるので、片方のサーバに重い処理が偏ってしまうことがあり、全体としてのパフォーマンスを十分に活かせない場合があります。
3回テストしてみましたが今回のケースでは15秒ほどでした。
Time taken for tests: 15.45550 seconds Time taken for tests: 15.45509 seconds Time taken for tests: 15.41334 seconds
シナリオ② 結果(bybusyness)
bybusynessの場合はサーバ毎のタスクキューを見ているので、あるサーバが忙しければ他のサーバに割り振るようなことが可能です。
結果としてLB配下のサーバの能力を上手く活用できることがあります。簡易的なテストですが実際に全体の処理時間が短縮されているのがわかります。
3回テストしてみましたが、byrequestsが15秒ほどだったのと比べると早いです。
Time taken for tests: 9.50075 seconds Time taken for tests: 9.40181 seconds Time taken for tests: 8.41857 seconds
振り分けロジック
byrequestsのロジック
bybusyness
のロジックを理解するためにも、まず簡単に byrequests
のロジックを見ておきます。
ロードバランサーがどのサーバに振り分けるかは lbfactor
という変数で決まります。
ロジックはApacheの公式サイトに説明があるので引用します。
for each worker in workers worker lbstatus += worker lbfactor total factor += worker lbfactor if worker lbstatus > candidate lbstatus candidate = worker candidate lbstatus -= total factor
実際にどのように動くのか書いてみます。
4台のWEBサーバをロードバランシングすることとします。上のコードで言えば workersは4つのworkerが入っていることになります。
lbfactor
は設定ファイルで設定可能な振り分け重み付け変数です。デフォルトは1で、今回は25を設定したことにします。
このロジックで回していった場合の、 lbstatus
変数の値の遷移と、振り分け先( candidate
)として選ばれるworkerの遷移です。
loop\worker | a | b | c | d | candidate |
---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | a |
1 | -75 | 25 | 25 | 25 | b |
2 | -50 | -50 | 50 | 50 | c |
3 | -25 | -25 | -25 | 75 | d |
4 | 0 | 0 | 0 | 0 | a |
5 | -75 | 25 | 25 | 25 | b |
6 | -50 | -50 | 50 | 50 | c |
実際のコードの該当部分も貼っておきます。
httpd/modules/proxy/balancers/mod_lbmethod_byrequests.c
/* Take into calculation only the workers that are * not in error state or not disabled. */ if (PROXY_WORKER_IS_USABLE(*worker)) { (*worker)->s->lbstatus += (*worker)->s->lbfactor; total_factor += (*worker)->s->lbfactor; if (!mycandidate || (*worker)->s->lbstatus > mycandidate->s->lbstatus) mycandidate = *worker; }
bybusynessのロジック
次にbybusyness見ます。パッと見はbyrequestsと似ています。ただif文を見ると (*worker)->s->busy < mycandidate->s->busy
と書いてあってしっかりとbusyかどうかで振り分けられます。
lbfactor
lbstatus
は busy
が同じ場合の次の判断基準として使われています。
busy
の項目はworkerに割り当てられたタスクのキューの長さなのですが、その値は上位モジュールである mod_proxy_balancer.c
にて管理されています。なので、byrequestsのアルゴリズムを使ってもbalancer-managerにはBusyの項目が計算されていることになります。
httpd/modules/proxy/balancers/mod_lbmethod_bybusyness.c
/* Take into calculation only the workers that are * not in error state or not disabled. */ if (PROXY_WORKER_IS_USABLE(*worker)) { (*worker)->s->lbstatus += (*worker)->s->lbfactor; total_factor += (*worker)->s->lbfactor; if (!mycandidate || (*worker)->s->busy < mycandidate->s->busy || ((*worker)->s->busy == mycandidate->s->busy && (*worker)->s->lbstatus > mycandidate->s->lbstatus)) mycandidate = *worker; }
参考
httpd/mod_lbmethod_bybusyness.c at trunk · apache/httpd · GitHub