Documentation/Clustering/EdgeOriginSolutiononTerracotta

Terracotta 上でのエッジ=オリジン・ソリューション

1 ソフトウェアブロック

汎用 HTTP ロードバランサ

汎用 HTTP ロードバランサには、ハードウェアバランサもソフトウェアバランサも使用できます。

エッジサーバーのクラスタリング

エッジサーバーとは、リバースプロキシとして特別にカスタマイズされ、接続状態と一部のルーティング情報のノード間での共有およびフェイルオーバーが行えるよう、Terracotta 上で実行される Red5 サーバー群です。

Terracotta サーバー

Terracotta サーバーは、エッジノードを管理するのに使われます。Terracotta サーバーは、Terracotta ソリューションの一部です。

オリジンサーバー

オリジンサーバーは互いに独立したサーバーで、望ましいのはノード間でまったく情報を共有していないことです。オリジンサーバーは、NAS 経由で共通のストレージにアクセスしてもかまいません。分離ポリシーはエッジサーバーによって定義され、テキストチャットルームなどのライブ会議およびリモート共有オブジェクト通信では、分離は URL ベースで行われ、すべての参加者が単一のオリジンサーバーにまとめられるようになっています。

クライアントとロードバランサの間のリンク

このリンクは RTMPT プロトコルなので標準の HTTP が使われ、ファイアウォールの問題はありません。

エッジレイヤとオリジンサーバーの間のリンク

エッジサーバーは、すべてのオリジンサーバーに接続することができます。クライアントからの RTMPT 接続は、特定のオリジンサーバーへの単一の接続へと多重化されます。

2 デプロイメント

ロードバランサは SAMP ブレードで共有されます。ロードバランサでは、Red5 関連リクエストをエッジサーバーに振り向けるために、適切なポリシーを定義する必要があります。

3 設計

接続の確立

正常な接続の確立

  1. クライアントがエッジとハンドシェイクします。この手順は 3 つのパケット (クライアントからサーバー、サーバーからクライアント、再度クライアントからサーバー) からなり、エッジはこれを受けて接続オブジェクトを作成して、ハンドシェイク状態を維持します。エッジは、Keep-alive ワーカ (スケジュールされたタスク) がチェックするローカルリストに接続オブジェクトを入れます。
  2. クライアントは、接続先のスコープを示す URL を添えて、"connect" RTMP 呼び出しパケットをエッジに送信します。
  3. エッジは "connect" リクエストを検証し、各種ロードバランスのルールに従ってオリジンを選択します。エッジは クライアント ID を含む "Connect Message" をオリジンに送信します。オリジンはこれを受けて接続オブジェクトを作成し、"connect" RTMP パケットを待機します。
  4. エッジは、クライアントから送信された URL を添えて、"connect" RTMP 呼び出しパケットをオリジンに送信します。
  5. オリジンは "connect" リクエストを検証し、"SUCCESS" RTMP パケットをエッジに送り返します。
  6. エッジは "SUCCESS" RTMP パケットをクライアントに送り返します。

エッジによって拒否される接続

  1. クライアントがエッジとハンドシェイクします。この手順は 3 つのパケット (クライアントからサーバー、サーバーからクライアント、再度クライアントからサーバー) からなり、エッジはこれを受けて接続オブジェクトを作成して、ハンドシェイク状態を維持します。エッジは、Keep-alive ワーカ (スケジュールされたタスク) がチェックするローカルリストに接続オブジェクトを入れます。
  2. クライアントは、接続先のスコープを示す URL を添えて、"connect" RTMP 呼び出しパケットをエッジに送信します。
  3. エッジは "connect" リクエストを検証し、"REJECT" RTMP パケットを送り返して、接続を拒否します。
  4. エッジは接続をクローズし、接続オブジェクトを破棄します。

オリジンによって拒否される接続

  1. クライアントがエッジとハンドシェイクします。この手順は 3 つのパケット (クライアントからサーバー、サーバーからクライアント、再度クライアントからサーバー) からなり、エッジはこれを受けて接続オブジェクトを作成して、ハンドシェイク状態を維持します。エッジは、Keep-alive ワーカ (スケジュールされたタスク) がチェックするローカルリストに接続オブジェクトを入れます。
  2. クライアントは、接続先のスコープを示す URL を添えて、"connect" RTMP 呼び出しパケットをエッジに送信します。
  3. エッジは "connect" リクエストを検証し、各種ロードバランスのルールに従ってオリジンを選択します。エッジは クライアント ID を含む "Connect Message" をオリジンに送信します。オリジンはこれを受けて接続オブジェクトを作成し、"connect" RTMP パケットを待機します。
  4. エッジは、クライアントから送信された URL を添えて、"connect" RTMP 呼び出しパケットをオリジンに送信します。
  5. オリジンは "connect" リクエストを検証し、"REJECT" RTMP パケットを送り返して、接続を拒否します。
  6. オリジンは "Close Message" を送信して接続をクローズします。オリジンは、作成された接続オブジェクトを破棄します。
  7. エッジは "REJECT" RTMP パケットをクライアントに渡し、接続をクローズします。

タイマーによってクローズされる接続

各エッジではタイマーが実行され、接続が alive かどうかがチェックされます。接続がエッジ上で初めて作成されたときに、その接続はローカルリスト (共有されないリスト) に追加され、このリストがタイマータスクによって定期的にチェックされます。エッジ上の接続オブジェクトは、接続を強制的にクローズしてもよいかどうかをタイマータスクがチェックできるよう、いくつかの API を公開することになっています。ここではいくつかの基準が定義されています。

  1. 接続 (connect) フェーズで、エッジがクライアントの最初のハンドシェイクパケットを受け取るまで時間がかかりすぎる。
  2. ハンドシェイク (handshake) フェーズで、エッジがクライアントの 2 番目のハンドシェイクパケットを受け取るまで時間がかかりすぎる。
  3. 接続完了 (connected) フェーズで、次のクライアントリクエストが来るまでにタイムアウトが発生する。接続がエッジによって明示的にクローズされるとき、オリジンは通知を受け取り、オリジン上の対応するリソースも解放されます。
  4. 接続がクローズ中の状態にあるにもかかわらず、フォローアップクライアントリクエストがエッジに届かない。接続は自動的にクローズされます。

接続タイムアウト

  1. クライアントは接続をオープンするリクエストを送信します。
  2. エッジはクライアントの最初のハンドシェイクのタイムアウトを待ちます。

ハンドシェイクタイムアウト

1. クライアントがエッジとハンドシェイクし、エッジがハンドシェイクに応答します。2. エッジはクライアントのハンドシェイクレスポンスのタイムアウトを待ち、接続をクローズします。

クライアントからの "Connect" 呼び出しを待機中のタイムアウト

  1. クライアントがエッジとハンドシェイクします。
  2. エッジはクライアントの "Connection" 呼び出しリクエストのタイムアウトを待ち、接続をクローズします。

クライアントのリクエストを待機中のタイムアウト

  1. クライアントのリクエストのタイムアウト。エッジは、オリジン上のリソースを解放するために "Close Message" をオリジンに送信します。
  2. エッジが接続をクローズします。

クローズ中状態で待機中のタイムアウト

  1. クローズ中状態でタイムアウトすると、接続が自動的にクローズされます。

オリジンによってクローズされる接続

  1. エッジに "Close Message" を送信して、関連するリソースを解放します。
  2. クライアントに "Close" パケットを送信し (パケットは実際にはクライアントのバッファに入れられます)、"closing" 状態へと遷移します。
  3. クライアントが "Close" パケットを受け取り、接続がクローズされます。

エッジによってクローズされる接続

  1. オリジンに "Close Message" を送信し、オリジン上で関連するリソースが解放されます。
  2. クライアントに "Close" パケットを送信し (パケットは実際にはクライアントのバッファに入れられます)、"closing" 状態へと遷移します。
  3. クライアントが "Close" パケットを受け取り、接続がクローズされます。

接続状態マシン

エッジ接続状態マシン

CONNECT

これはエッジ上での接続の初期状態です。クライアントの RTMPT "Open" リクエストに対して、接続が作成されます。

HANDSHAKE

クライアントから最初のハンドシェイクを受け取ると、エッジはサーバーハンドシェイクリプライを送信し、HANDSHAKE フェーズに遷移してクライアントの 2 番目のハンドシェイクを待ちます。

EDGE_CONNECTED

クライアントから 2 番目のハンドシェイクを受け取ると、エッジは EDGE_CONNECTED フェーズに遷移してクライアントからの "Connect" RTMP 呼び出しを待ちます。

EDGE_CONNECT_ORIGIN_SENT

"Connect" 呼び出しを受け取ると、エッジは接続先のオリジンを選択し、このオリジンに "Connect Message" を送信します。

ORIGIN_CONNECT_FORWARDED

"Connect Message" がオリジンによって受け入れられると、エッジは以後、"Connect" 呼び出しをオリジンに転送し続けます。

EDGE_ORIGIN_CONNECTED

"Connect" がオリジンによっても受け入れられ、これでエッジとオリジンが接続されます。

CLOSING

HTTP の性質上、サーバーはクライアントが "Close" を受け取るまで待機してから、接続を実際にクローズする必要があります。接続は、実際のクローズを待機する CLOSING 状態になります。接続が CLOSING 状態になると、オリジン上のすべての関連するリソースはクリーンアップされます。

CLOSED

接続は死んだ状態となり、削除されて GC を待ちます。

オリジンの接続状態マシン

CONNECT

"Connect Message" を受け取ると、接続オブジェクトが作成され、初期状態は CONNECT となります。

CONNECTED

"Connect" を受け取ると、接続が作成され、状態は CONNECTED に遷移します。

CLOSED

"Close Message" を受け取るか、明示的なクローズによって接続がクローズされると、接続はクローズされ、すべてのリソースは解放されます。

アプリケーションデータフロー

クライアントとエッジとの間、およびエッジとオリジンとの間で接続が確立されると、クライアントのアプリケーションリクエストがサーバーによって処理されます。

  1. クライアントは RTMPT リクエストをサーバーに送信します。
  2. このリクエストはロードバランサによってエッジのいずれかに割り振られます。
  3. エッジはリクエストの有効性を検証します。クライアント ID またはシーケンスナンバーのいずれかが適切でない場合は手順 8. に進み、そうでない場合は手順 4. に進みます。
  4. リクエストタイプが SEND の場合、エッジはリクエストから RTMP パケットを取り出します。リクエストタイプが SEND で、かつオリジンとの接続が存在する場合には手順 5. に進み、そうでない場合には手順 6. に進みます。
  5. エッジは接続オブジェクトから該当するオリジンを探し、このオリジンにパケットを転送します。(この手順は、リクエストタイプが SEND で、かつオリジンとの接続が存在する場合にのみ実行されます。)
  6. エッジは、未処理のすべての RTMP パケットを接続バッファから RTMPT コンテンツに入れ、クライアントに送り返します。
  7. 接続状態が CLOSING の場合は、接続がクローズされます。
  8. 完了。

オリジンは、エッジからのすべてのリクエストを処理し、プッシュモードで応答を送り返します。VOD はバックグラウンドスレッドによってプッシュされ、Live/RSO はその他のリクエストによってプッシュされます。エッジに届いた応答は、対応する接続バッファにプッシュされます。

多重化 RTMP - エッジとオリジンとの間のプロトコル

多重化 RTMP (Multiplexing RTMP) は、エッジとオリジンとの間のプロトコルです。このプロトコルは、特定のオリジンへの複数の接続をエッジとオリジンとの間の 1 つの接続にまとめることを目的としています。このプロトコルは TCP をベースにしています。オリジンはサーバーとしてふるまい、標準ポート 1945 でリスンします (ポートは構成可能で)。

パケットの多重化

プロトコルは、連続する一連の多重化パケット (以下「M パケット」) から構成されています。各 M パケットの構造は、次の図に示すとおりです。

M パケットには、パケットヘッダーと、ペイロードとしてのパケットボディがあります。ヘッダーは図で濃い青の部分、ボディは明るい灰色の部分です。パケットヘッダーには 3 つのフィールドがあります。"Message Type" は 32 ビットで、M パケットのタイプを示します。"Connection Id" は、RTMPT 接続の 64 ビットの ID に対応します。"Length" は 32 ビットで、ペイロードのサイズです。すべてのフィールドは、ビッグエンディアンの整数としてエンコードされます。ペイロードとしてのパケットボディの内容は、"Message Type" によって決定され、長さが 0 のこともあります。

Message Type

現在、次の 3 つのタイプのパケットが定義されています。

  1. OPEN, 0x01: 接続を作成します。長さは 0 です。
  2. CLOSE, 0x02: 接続をクローズします。長さは 0 です。
  3. RTMP, 0x03: RTMP エンコードされたパケット。

オリジンのロードバランシングのしくみ

リクエストのロードバランシングに使われるしくみは、URL パターンをベースにしたものです。接続は 2 つのグループに分類されます。具体的には、ライブクラスルームとライブチャットを対象としたグループ、そして VOD と録画を対象としたグループです。

ライブおよび RSO のバランシング

共通の URL パターンを持つ接続は、同じオリジンに割り当てられます。

VOD および録画のバランシング

VOD および録画の接続は、オリジン全体の総合的な負荷に基づいて、いずれかのオリジンに割り当てられます。たとえば、URL "vod/vod1.flv" への接続は、オリジン A とオリジン B の負荷によって、オリジン A に割り当てられることもあれば、オリジン B に割り当てられることもあります。負荷は、統計情報によって収集されている、オリジンの処理接続数によって測定されます。

エッジディスカバリ

新しいエッジサーバーが追加されると、クラスタ内のすべてのエッジに通知がブロードキャストされます。これは、Terracotta JMX によって提供されているしくみです。新しいエッジの参加はシームレスに行われます。

オリジンディスカバリ

エッジは起動時にマルチキャストグループに参加する必要があり、このグループはすべてのエッジとオリジンによって共有される構成である必要があります。オリジンは起動時にこのマルチキャストグループに通知を送信し、起動したことをすべてのエッジに知らせる必要があります。通知は、いずれかのエッジがオリジンに接続するまで送信され続けます。エッジはオリジンに関するすべての情報を Terracotta によって共有しており、いずれかのエッジが接続したときに、すべてのエッジがこの新しいオリジンについて知ることができるようになっています。通知メッセージには、オリジンの IP アドレス、M RTMP のポート、起動時間などが含まれています。

エッジアフィニティ

オリジンは同時に多数のエッジ接続を持つので、応答メッセージは任意のエッジを介して送信できます。これが可能なのは、応答バッファがエッジ間で共有されているからです。このシナリオでは、もともとの RTMPT リクエストを受け付けたエッジが最善の選択肢になります。なぜなら、Terracotta を介して応答メッセージをコピーする必要がないからです。このような動作にするために、リクエストを送信した最後のエッジを M RTMP 接続内に記録しておき、応答メッセージをそのエッジに送信するようにしています。この前提条件として、ロードバランサに十分なインテリジェンスが備わっていて、リクエストを処理するエッジを選択するときに、アフィニティ (親和性) を考慮して同一のエッジを選択できるようになっている必要があります。

フェイルオーバー

エッジのフェイルオーバー

エッジのフェイルオーバーは、ロードバランサのフェイルオーバーのサポートをベースに動作します。エッジに障害が発生し、ロードバランサがこれを検出したら、ロードバランサでは別のエッジを見つけて、このエッジにリクエストの転送を試みる必要があります。接続状態はすべてのエッジサーバー上で共有されているので、RTMPT リクエストの受け入れという点では、すべてのエッジが同等の存在です。すべてのオリジンは、エッジがダウンしたときに、エッジの接続障害によって通知を受け取ります。オリジンが、障害を起こしたエッジにメッセージを送信中である場合、そのオリジンは未送信のメッセージをほかのエッジに再送信します。各 RTMPT パケットは、リクエストに結び付けられたシーケンス ID を持っており、この ID はエッジクラスタ上の接続状態に保持されます。この ID は、リクエストが正常に処理されると (リクエストがオリジンに転送された後、応答メッセージがないかどうか送信バッファがチェックされる前に)、更新されます。このしくみによって、リクエストが失われないようになっています。リクエストが 2 回処理されるケースが生じることもあり、この場合、リクエストがべき等でないと問題が発生しますが、この問題はここで取り上げたソリューションでは対処できません。

オリジンのフェイルオーバー

該当するオリジン上のすべての接続は切断され、多重化接続切断通知の結果として、エッジ上の対応する接続は解放されます。クライアントには障害が発生したことがわかり、クライアントが再接続を行います。

セキュリティ

偽装リクエストの防止

各クライアントにはクライアント ID が割り当てられ、このクライアント ID はサーバー上の接続オブジェクトにマップされます。ほかのクライアントが同一のクライアント ID を使ったり、サーバーと通信したりすることについては、これを防ぐ必要があります。

  1. エッジは、接続が作成されたときにクライアントの IP を保存し、その後の接続リクエストではクライアントの IP をチェックする必要があります。
  2. RTMPT パケットのシーケンス ID がチェックされ、不正な RTMPT シーケンス ID を持つパケットは破棄されます。
  3. セキュリティを徹底する必要がある場合には、RTMPS が推奨されます。

認証

クライアントの認証メッセージは "connect" RTMP 呼び出しのパラメータによって送信され、実際の認証プロセスはアプリケーション依存です。

添付ファイル