負荷分散の方法

チャットを作るとハリきっていたのに現在のところ放置していますが、これ、来年からやることします。引越し作業などがあるのと、今は夏休み終盤のようなノリだからです。

で、まだ実装はしないけど負荷分散について。
もともと1台でいかにうまくかやるかということを言っていましたが、はっきりいってソフトウェアの性能を2倍にするよりも安いパソコンを1台買ってきたほうが負荷の問題は簡単に解決します。だからといって、ソフトをテキトウにやるわけというわけではなくて、単に無理に避ける必要は無いから最初からやっとこうかというだけです。

スタティックなサーバーの負荷分散は正直やったことがないのですが、普通に考えて雰囲気は分かるので、とりあえずはそれでやろうと思います。アドバイスなどあればお願いします。

構成

[チャットサーバー1]   ←→   
[チャットサーバー2]   ←→   [ブロードキャストマシン(仮)]
[チャットサーバー3]   ←→   
[チャットサーバーN]   ←→   

テキトウですが、こんな感じです。
チャットサーバーは、ルームやクライアントのセッションなんかを管理しているマシンで、ルーム作成コマンドや発言コマンドなど全サーバーに伝える必要のあるコマンドはブロードキャストマシン(仮)へ送ります。ブロードキャストマシン(仮)は受け取ったメッセージを全チャットサーバーへ送信します。一見するとブロードキャストマシンに負荷が再集中しているようですが、『来たもの返す』という単純動作だけなのでCPUさえあればどうにかなると思っています。(仮)なのは、のちにデータ保存サーバーになるかもしれないからです。

接続先の分散

サーバーを分けると、クライアントの接続先も分散してもらわないといけません。
今の時点で一般的に使われていることを知っている方法としては、次のようなものがあります。

  1. ロードバランサー
  2. DNSラウンドロビン

ロードバランサーというのは、サーバーサイドに設置して、各サーバーのアクセス数や転送量によって、TCPセッションをうまいこと割り振るマシンやソフトです。
これは、パッと考えてボツです。次の理由からです。

  1. HTTPのようにセッションが短くないのでオーバヘッドが大きい
  2. プロトコルがTCPではないので既存のものが使えない

ロードバランサーは、作ったことはないし、ソースコードを見たこともないのですが、TCPストリーム単位でサーバーへ割り振れることを考えると、内部でTCPセッションテーブルを持っていて、SYNで追加、FINやRSTで削除という感じの操作と、DATAやACKではIPアドレスとポート番号をキーにTCPセッションテーブルから転送対象のサーバーアドレスを参照して、そこへ転送というようなことをやっていることが想像できます。これは、ウェブのような寿命の短いセッションが大量にくるような場合には有効かもしれませんが、チャットのように長い間大量のセッションを保持するようになると、パケットごとにテーブルを参照することになって、こいつ自体の負荷がとんでもない事になりそうなので(1.テーブルのサイズが大きくなること、2.テーブルを参照するアルゴリズムはO(log n)だとしても、それをn回行うとn倍になること)オーバヘッドが大きいと思います。
と、叩かれそうな理由を云々言いましたが、第2の理由として、TCPを使わないので既存のものが使えない(→自分でつくらないかん)という致命的な問題があります。ああダメだ……それはめんどうすぎる……ボツ……というわけです。

もうひとつのDNSラウンドロビンというのは、1つのドメインに対して複数のIPアドレスを割り当てることで、クライアント側で接続先IPアドレスを分散させる方法です。これは、正直いうと複数固定IPアドレスの月料金が嫌なのでボツなんですけど、それらしい理由をつけるとするなら偏りがありそうだからです。DSNの問い合わせはISPのDNSキャッシュサーバーにキャッシュされます。このとき、各DNSキャッシュサーバーに均等に割り振れたとしても、東京のような都会と、うちの実家のような田舎では、アクセスの密度が全く違うので、DNSキャッシューサーバーのカバーする地方によってかなりの偏りが出るのではないかということです。ということでボツです。

オワタ\(^o^)/知っていることが全部なくなった! と思ってしまいますが、なくなったら新たに思いつけばいいだけで思いつきました。

方法としては既にあると思いますけど、名前を知らないので勝手に命名します。

です。
名前のとおりDNSラウンドロビンに近いです。違うところは、IPアドレスではなく、ポート番号をかき混ぜるという点です。これなら1つのIPアドレスでアクセス分散が簡単にできます。

Socket connect(String hostname)
{
    return new ClientSocket(
        new InetAddress(hostname, BASE_PORT + random.nextInt() % N)
    );
}

クライアント側でこんな処理をして、BASE_PORT + (0〜N)にランダムに接続するように作ります。サーバーサイドでは、ルーターを挟んで、各ポート番号に対するチャットサーバーへスタティックにポートフォーワードします。
これで、安上がりでオーバヘッドも少なくアクセス分散ができるのではないかと思っています。回線は分散できないですけど、それはいいです。いまのところ。


ウーヌ、ダメでしょうか……
で、ここまできて、やっぱり、マウスコンピュータで、CPU:3GHz、RAM:2GBくらいの安いマシンを1台買えばそれで十分事足りるのではないかと思えてきます。
たぶん、やりますけど。