2015年06月01日

リアルタイム通信環境の(一部)構成紹介

はてなブックマークに登録

こんにちは。
今回は、当社で稼働させているリアルタイム通信環境について、ご紹介させて頂きます。

ご紹介する環境に対する要件は、以下となります。

・ゲーム内の期間限定イベントで使用し、イベント開催中のみサーバを稼働
・リアルタイム通信。プロトコルは、websocket を使用
・同じチームに所属するユーザを同じサーバへ接続
・とりあえずいっぱいスケールできるように(笑

最後の要件は冗談で、実際にはちゃんとした数値を頂いているのですが、このような環境構築を依頼されましたので、AWS 上で以下にあるような構成を考えてみました。

構成図


websocket
※ 主要なサーバのみを抜粋

ELB


外部のクライアントから、websocket な接続を受け付けます。
http(s) モードでは、websocket の通信確立に必要なヘッダが消去されてしまうため、tcp モードを使用しています。
tcp モードを有効にすると、ELB 配下のサーバから、送信元 ip アドレスが読み取れないという問題があるのですが、こちらは proxy protocol (※1) を使用する形で対処しています。
また、ssl に関する処理をお任せする意図でも ELB を使用しています。

proxy server


EC2 インスタンスを使用しています。
このサーバは、ELB を介して届いたリクエストを、内部ルールに従って特定の application サーバに転送する必要があります。
内部では、nginx を使用しており、先述した proxy protocol に対応しています。
特定の application サーバ にリクエストを転送する点は、nginx-lua を用いて、ローカルに設置されたルールファイルをもとに proxy の宛先を動的に変更する形を取っています。
1台あたりの接続数は、socket buffer (※2)や、proxy 時の送信元 port 番号を考慮し、概ね 20000 常時接続程度を許容する設定になっています。
nginx で、proxy protocol を使用する場合や、socket buffer のサイズ変更を行う場合は、以下のようにして、server ディレクティブから調整可能です。(※3)

# sample
server {
    listen 8080 proxy_protocol sndbuf=20480 rcvbuf=20480;

・この環境を構築する上で、考慮したこと

proxy server から、application server へリクエストを転送する箇所については、送信元ポート番号が消費されますので、その上限値に注意する必要があります。
送信元ポート番号の範囲は、/proc/sys/net/ipv4/ip_local_port_range から確認可能です。
本環境でこの値を確認すると、全体で使用可能なポート番号数はデフォルトで、28232 ポートあります。
$ cat /proc/sys/net/ipv4/ip_local_port_range 
32768	61000

このうち、20000 ポートを proxy の送信元ポートに割り当て、20000 接続を許容することを考えた場合、ソケットバッファの値をどの程度にすべきかを考えます。
送受信ソケットバッファサイズは、以下のファイルから確認可能です。左から、最小、標準、最大サイズとなります。
$ cat /proc/sys/net/ipv4/tcp_{r,w}mem
4096	87380	1721312
4096	16384	1721312

最大サイズに関しては、/proc/sys/net/core/{w,r}mem_max が優先されるということなので、そちらも確認します。
$ cat /proc/sys/net/core/{r,w}mem_max
131071
131071
この設定では、標準の送信ソケットバッファサイズは16384byte、受信ソケットバッファサイズは 87380 byte となります。
今回のケースでは、外部から接続があるたびに、[elb] <==> [ proxy server ] <==> [ application server ] の 2 接続が作成されます。
標準バッファサイズを使用して試算をすると、受信は 349520 byte (87380 * 2 * 2 session)、送信は 65536byte (16384 * 2 * 2 session) となり、20000 接続を受け入れるには、約 8.3 Gbyte のメモリが必要な計算となります。
上記計算は、/proc/sys/net/ipv4/tcp_{r,w}mem の 標準サイズ を参考にしていますが、メモリサイズが最小〜最大サイズでスケールする(はず)なので、必ずしも試算通りのメモリ消費とはならない可能性があります。
/proc/sys/net/ipv4/tcp_{r,w}mem の値に手を入れて、送受信ソケットバッファサイズを調整することもできますが、これは nginx 以外のプロセスにも影響を及ぼしますので、あまり好ましくないように思えます。
プロセス単位で、ソケットバッファサイズを固定するには、SO_RCVBUFや、SO_SNDBUFを使用する必要がありますが、nginx では、sndbuf rcvbuf option を使用することで同様の設定が可能です。
今回使用するインスタンスは、メモリサイズが約7Gbyte でしたので、20000 接続を受け入れつつ、全体の約半分程度のメモリを使用するようにソケットバッファサイズを調整した結果が、先に記載した nginx の sndbuf、rcvbuf の設定となります。

application server


EC2 インスタンスを使用しています。
このサーバは、イベント開催期間中のみ稼働するようになっており、イベントへの参加人数規模に応じて、台数が変動します。これらはイベント開催期間のみ稼働し、概ね 1 - 2 時間で削除されます。
起動時に、自身に紐づけられたタグデータをもとに、S3 から最新のソースコード(python) を取得します。
内部では、このコードを使用したデーモンが稼働しており、ELB、proxy サーバを介して届いた websocket な接続を取り扱います。
また、デーモン起動後は、以下の様なフォーマットで、外部の監視サーバからステータスを確認できるようになっています。

(health check)$ curl http://10.0.13.167/state.json
{
  "ip": "10.0.13.167",
  "host": "appserver001",
  "process_num": 1,
  "rooms": [
    {
      "port": 10000,
      "state": 0
    }
  ]
}

これらのサーバが連携し、(すごくざっくりですが)以下のフローでイベントの準備が実施されます。

0. イベント開始前に参加人数がきまる
1. イベント開始直前に 必要なだけ application サーバを立ち上げる
2. 立ち上がった application サーバの情報をもとに、proxy サーバ上にルールファイルを生成
3. 準備完了

書いてしまえば、本当にシンプルなフローなのですが、これらは全て自動化されているため、

・application サーバの起動/削除リクエストに成功したか
・application サーバを削除する前に、ログが正しく回収できたか
・application サーバを自動生成する際に、AZ別に立ち上げ、冗長性を考慮する
・プログラム(python) が正しく起動/終了したか
・問題発生時のアラート処理

等の処理を泥臭く実装しています。

また、実際に運用してみて、いくつかの問題がありました。

・ELB
負荷試験時は、問題なく裁けていたのにも関わらず、本番でいざサービスを開始すると、ELB がネックとなり、外部からのリクエストが ELB 配下のサーバへ届かなくなりました。
負荷試験時は、連日負荷をかけていたために自動スケールしていたようです。そのため、暖気設定をせずに想定リクエストを裁けると考えてしまったのが失敗。
この後、何度かAWSさんと暖気規模の調整を行い、今現在は問題なく動作しています。

・api
AWSの api 呼び出しも、時々失敗することがあります。
当たり前といえばそうなのですが、api の例外処理を正しく実装しましょう。

今後の課題


・application サーバしかスケールできていない
現構成では、proxy サーバの台数は固定されています。接続数の相場が見えてきた点と、1台でそれなりの数を裁く事ができるので、手を入れる事は少ないのですが、今後は application サーバ 同様の仕組みでスケールさせることを考えています。

・構成
ELBがボトルネックとなったお話をさせていただきましたが、こちら にあるような、application サーバ と、client が直接通信をするような構成例も多く見受けられます。
このような構成をもとにしてあげれば、ELB がボトルネックになる可能性も低かったように思えますので、本環境、次期構築時の構成に活かせればと考えています

求人(本題)


今回は、当社で扱ったリアルタイム環境の一部分をご紹介させて頂きましたが、他にも色々な構成のインフラ環境を取り扱っています。 当社ではネイティブゲームへのシフトと共に、インフラ構成が多様化しつつあり、これまでソーシャルゲームで活用してきた DSAS もその対応に迫られています。

この記事をみて、

・ここ、もっと詳しく知りたい
・もっと、良い構成・楽な構成にできるかも(?)
・KLabに(ほんの少しだけでも)興味を持てた
・(これから現れるであろう)新しいインフラ構成を一緒に考えてみたい


という方は、是非こちらからのご応募お待ちしております(切実)

以上

インフラ構成の一例紹介と求人のご案内でした。

※1:http://docs.aws.amazon.com/ja_jp/ElasticLoadBalancing/latest/DeveloperGuide/enable-proxy-protocol.html
※2:http://linuxjm.osdn.jp/html/LDP_man-pages/man7/socket.7.html
※3:http://nginx.org/en/docs/http/ngx_http_core_module.html#listen
klab_gijutsu2 at 19:15│Comments(1)TrackBack(0)

トラックバックURL

この記事へのコメント

1. Posted by o   2015年09月17日 10:56
(1)application サーバ と、client が直接通信をするような構成例...

にした場合、ELBを使用しないので、

(2)ssl に関する処理をお任せする意図でも ELB を使用...

(2)のようにSSL処理をELBに任せられなくなると思います。

(1)にした場合、applicationサーバそれぞれにSSL証明書を持たせること、
AutoScalingした場合、増えるサーバのためのSSL証明書も用意しないといけない、
などが課題になると思います。
この点に関して、いい考えがあれば、うかがいたいです。

この記事にコメントする

名前:
URL:
  情報を記憶: 評価: 顔   
 
 
 
Blog内検索
Archives
このブログについて
DSASとは、KLab が構築し運用しているコンテンツサービス用のLinuxベースのインフラです。現在5ヶ所のデータセンタにて構築し、運用していますが、我々はDSASをより使いやすく、より安全に、そしてより省力で運用できることを目指して、日々改良に勤しんでいます。
このブログでは、そんな DSAS で使っている技術の紹介や、実験してみた結果の報告、トラブルに巻き込まれた時の経験談など、広く深く、色々な話題を織りまぜて紹介していきたいと思います。
最新コメント