server | v4 switch to v3  

ロードバランシングアプリケーション

本稿では、サーバ-ーサイドのLoadBalancingアプリケーションの実装について説明します。

Contents

コンセプト

Photon 3では、ロードバランシングアプリケーションは(文字通り「Lite application」を拡張するものです。 Lite 機能(ルーム、イベント、プロパティなど)に加えて、複数サーバーでのアプリケーションの実行を可能にする拡張レイヤーが加えられました。ロードバランシングはロビーサポートとマッチメイキングの機能も追加します。

Photon4ではLiteは非推奨となりHiveに変わりました。 このため現在ロードバランシングは、複雑ですがさらに拡張性の高いHiveを拡張しています。

基本設定に変更はなく、非常に簡単です。マスターサーバーは常に1台で、ゲームサーバーは1台からN台まで存在します。

Photon Server Concept: LoadBalancing Setup
Photon Serverのコンセプト: ロードバランシングの設定

マスターサーバーは以下のタスクを処理します:

  • ゲームサーバー上で現在オープンになっているゲームの履歴を保持します。
  • 接続したゲームサーバーの負荷の履歴を保持し、適切なゲームサーバーにピアを割り当てます。

  • クライアントが利用できるルームのリストを「ロビー」に保持し、アップデートします。

  • クライアント向けにルームを検索し(ランダムまたは名前で)、クライアントにゲームサーバーのアドレスを転送します。

ゲームサーバーは以下のタスクを処理します:

  • ゲームルームのホスティング。これを実行するため、ゲームサーバーは若干修正したバージョンの「Lite application」を実行します。
  • ゲームサーバーの最新の負荷と、ゲームサーバーのゲームのリストをマスターサーバーに定期的に報告します。

Back To Top

Photon Cloudとの違い

ロードバランシングアプリケーションは、Photon Realtime Cloudサービスとほぼ同じロジックを提供しています。 Cloudサービスとして作動するために必要でカスタムロジックの特殊なサーバーに適用できない要件は取り除かれました。 これにより、コードが大幅に簡素化されました。

  • 仮想アプリケーションはありません。1つのLBインスタンスに対してゲームロジックが1つのみ実行されます。操作Authenticate内のAppIDパラメータは無視されます。
  • AppVersionパラメータによってプレイヤーは分離されません。これは、仮想アプリの一部です。

Back To Top

基本ワークフロー

クライアント側の観点からのワークフローも非常に簡潔です:

クライアントはマスターサーバーに接続し、ロビーに入ってオープンになっているゲームのリストを取得します。 マスターでCreateGame操作を呼ぶと、ゲームは実際には作成されませんーマスターサーバーはゲームサーバーに対して最小負荷のみを判定し、そのIPをクライアントに返します。

クライアントがマスター上でJoinGameまたはJoinRandomGame操作を呼ぶと、マスターはゲームが実行中のゲームサーバーを調査し、そのIPをクライアントに返します。

クライアントはマスターサーバーから切断し、受信したばかりのIPでゲームサーバーに接続して、再びCreateGameまたはJoinGame操作を呼びます。

これ以降は、「Liteアプリケーション」とすべて同様に動作します。

Photon Server: LoadBalancing Sequence Diagram
Photon Server: ロードバランシング・シーケンスダイアグラム

Back To Top

マスターサーバー

このセクションでは、マスターサーバーの実装について説明しますー「\src-server\Loadbalancing\Loadbalancing.sln」ソリューション内のLoadBalancing.MasterServer ネームスペースを参照してください。

MasterApplicationは、受信接続の発信元がゲームクライアント(「クライアントポート」上)なのか、ゲームサーバー(「ゲームサーバー上」)なのかを判定します。

Back To Top

マスターサーバー:クライアントピアの処理

MasterClientPeerはマスターサーバーへのクライアント接続を表しています。 以下の操作は、MasterClientPeerで利用可能です:

  • Authenticate
    Authenticate操作にはダミー実装しかありません。 これは開発者が独自の認証メカニズムを実装する際、出発点として利用するためのものです。
    // MasterClientPeer.cs:
    private OperationResponse HandleAuthenticate(OperationRequest operationRequest)
    {
        OperationResponse response;

        var request = new AuthenticateRequest(this.Protocol, operationRequest);
        if (!OperationHelper.ValidateOperation(request, log, out response))
        {
            return response;
        }

        this.UserId = request.UserId;

        // publish operation response
        var responseObject = new AuthenticateResponse { QueuePosition = 0 };
        return new OperationResponse(operationRequest.OperationCode, responseObject);
    }
  • JoinLobby
    JoinLobby操作は、AppLobbyにMasterClientPeerを追加するために使用されます。AppLobbyにはGameList、すなわちすべてのゲームサーバーでオープンとなっているすべてのゲームのリストが含まれます。

ピアは初期のGameListEventを取得し、これにはGameList内にあるゲームの最新のリスト(JoinLobby操作のオプションのプロパティでフィルタリングされます)が含まれます。

その後、変更されたゲームのリストを含むGameListUpdateEventJoinLobby操作のオプションのプロパティでフィルタリングされます)が一定の間隔でクライアントに送信されます。 クライアントは、接続中はアップデートイベントを受信します。

    // AppLobby.cs:
    protected virtual OperationResponse HandleJoinLobby(MasterClientPeer peer, OperationRequest operationRequest, SendParameters sendParameters)
    {
        // validate operation
        var operation = new JoinLobbyRequest(peer.Protocol, operationRequest);
        OperationResponse response;
        if (OperationHelper.ValidateOperation(operation, log, out response) == false)
        {
            return response;
        }

        peer.GameChannelSubscription = null;

        var subscription = this.GameList.AddSubscription(peer, operation.GameProperties, operation.GameListCount);
        peer.GameChannelSubscription = subscription;
        peer.SendOperationResponse(new OperationResponse(operationRequest.OperationCode), sendParameters);

        // publish game list to peer after the response has been sent
        var gameList = subscription.GetGameList();
        var e = new GameListEvent { Data = gameList };
        var eventData = new EventData((byte)EventCode.GameList, e);
        peer.SendEvent(eventData, new SendParameters());

        return null;
    }
  • JoinGame / JoinRandomGame
    JoinGame操作は、AppLobbyのGameListにある、一意のGameIdで特定される既存の対戦に、クライアントが参加したい時に呼び出されます。ゲームが存在し、ピアがそれに参加することを許可されている場合、マスターサーバーはゲームが実際に実行されているゲームサーバーのIPをクライアントに返します。

マスターサーバーはGameSate をアップデートし、ピアをサーバー内の「joining peers」リストに加えます。 ゲームサーバー上のゲームに参加すると(または一定のタイムアウト後)、ピアはそこから削除されます。 このようにマスターサーバーはマスターとゲームサーバーの間を移行するピアを記録します。

ゲームがマスターサーバーによってランダムに選択され、クライアントにGameIdが返された場合を除き、JoinRandomGame は同様に動作します。

  • CreateGame
    CreateGame操作は、クライアントが新しいゲームを作成したい場合に呼び出されます。 マスターサーバーは、新たなゲームを作成するゲームサーバーを判定し、そのゲームサーバーのIPをクライアントに返します。 詳細は「ロードバランシングアルゴリズム」セクションを参照してください。

    また、GameStateオブジェクトが作成されてGameListに追加され、ピアは「参加ピア」として保存されます。 このGameStateはゲームの記録にのみ使用されますーゲーム自体はゲームサーバー上にのみ存在します。

Back To Top

ゲームサーバーピアの処理

マスターサーバーは、どのゲームサーバーが利用可能か、ゲームサーバーがゲームをいくつホスティングしているか、また最新の負荷について常に把握しています。

これを実現するには、各ゲームサーバーは起動時にマスターサーバーに接続する必要があります。 MasterApplicationGameServerCollectionを管理し、このGameServerCollectionにはIncomingGameServerPeersが保存されています。

ゲームサーバーが呼び出せる操作は1つのみです:

  • RegisterGameServer
    ゲームサーバーはマスターサーバーに接続後、RegisterGameServer操作を呼び出します。 ゲームサーバーはマスターのGameServerCollectionとロードバランサー (「ロードバランシングアルゴリズム」を参照してください)に追加されます。 ゲームサーバーは、切断時にGameServerCollectionから削除されます。

ゲームと負荷のアップデートをゲームサーバーがマスターサーバーにどのように送信するかについては、「ゲームサーバー」(#game_server)セクションを確認してください。

Back To Top

ゲームサーバー

このセクションでは、ゲームサーバーの実装について説明します。 「\src-server\Loadbalancing\Loadbalancing.sln」ソリューション内の LoadBalancing.GameServerネームスペースを参照してください。

Back To Top

ゲームサーバー:クライアントピアの処理

ゲームサーバーは「Lite application」から派生します。 クライアントがマスターからゲームサーバーアドレスを受信した時点で、クライアントはLiteで利用可能なゲームサーバー上で、どの操作でも呼び出すことができます。 唯一違うのは、ゲームサーバーではJoinGameCreateGame操作に別々の操作コードが使われているのに対し、Liteは両方ともに JoinGame操作で処理をおこないます。

Back To Top

マスターにゲームステートを報告

ゲームサーバーでは、マスターサーバーへの接続はOutgoingMasterServerPeerとして表示されます。 接続が完了した時点で、ゲームサーバーはマスターサーバーでRegister操作を呼び出します。 その後、ゲームサーバーは既存のゲームステートをすべてマスターサーバーにパブリッシュします。

これは、ゲームステートをマスターに送るよう各ゲームにメッセージを送信することで実行されます。

ゲームサーバーはこれをProcessMessageメソッドで処理し、UpdateGameStateOnMasterメソッドを呼び出して、マスターにUpdateGameEventを送信します。:

 protected virtual void UpdateGameStateOnMaster(
            byte? newMaxPlayer = null,
            bool? newIsOpen = null,
            bool? newIsVisble = null,
            object[] lobbyPropertyFilter = null,
            Hashtable gameProperties = null,
            string newPeerId = null,
            string removedPeerId = null,
            bool reinitialize = false)
        {            
       // [...]

            var e = this.CreateUpdateGameEvent();
        e.Reinitialize = reinitialize;
            e.MaxPlayers = newMaxPlayer;            
        // [ ... more event data is set here ... ]

            var eventData = new EventData((byte)ServerEventCode.UpdateGameState, e);
            GameApplication.Instance.MasterPeer.SendEvent(eventData, new SendParameters());
        }
}

ゲームが作成された際、クライアントがゲームに参加または退出した際、またゲームのプロパティが変更された際には常に、ゲームステートはマスター側でアップデートされます。

Back To Top

ロードバランシングの実装

次のセクションでは、最新の負荷に関する情報をゲームサーバーがサーバーにレポートする方法、マスターサーバーが新しいCreateGameリクエストに最適なゲームサーバーを判定する方法、実際のロードバランシングアルゴリズムについて説明します。

Back To Top

負荷の判定

実装の詳細については、「\src-server\Loadbalancing\Loadbalancing.sln」ソリューション内のLoadBalancing.LoadSheddingネームスペースを参照してください。

ゲームサーバーは、最新の負荷をマスターサーバーに定期的に報告しています。 負荷には、たとえば以下が含まれます:

  • CPU使用率
  • トラフィック
  • ENetおよびBusiness Queue Length、各リクエストにサーバーが要する平均時間など、Photon固有の値。
  • レイテンシー(自身にリクエストを送信している場合)

もっとも重要な(そして一番分かりやすい)要因は CPU負荷なので、ここでは CPU負荷に焦点を当てて説明します。

これらの要因はすべて、1つの値-ゲームサーバーの「ロードレベル」に集約され、マスターサーバーに報告されます。

ロードレベルが低ければ低いほど、ゲームサーバーは新しいゲームをホスティングしやすくなります。

Back To Top

実装の詳細

ゲームサーバーは、上記の要因について「フィードバック」を収集します。 各要因には、1つのFeedbackControllerオブジェクトがあり、これはFeedbackNameとFeedbackLevelから成ります。

internal enum FeedbackName
{
    CpuUsage,     
    Bandwidth,
    TimeSpentInServer
}

public enum FeedbackLevel
{
    Highest = 4,
    High = 3,
    Normal = 2,
    Low = 1,
    Lowest = 0
}

DefaultConfigurationクラスはそれぞれの値の閾値を定義していますーたとえばCPU使用率が20%までならばサーバーのFeedbackLevelは「lowest」と判定され、90%ならばFeedbackLevel は「highest」と判定される、などです。

// DefaultConfiguration.cs:

internal static List<FeedbackController> GetDefaultControllers()
{
    internal static List<FeedbackController> GetDefaultControllers()
    {
        var cpuController = new FeedbackController(
        FeedbackName.CpuUsage,
        new Dictionary<FeedbackLevel, int>
                {
                    { FeedbackLevel.Lowest, 20 },
                    { FeedbackLevel.Low, 35 },
                    { FeedbackLevel.Normal, 50 },
                    { FeedbackLevel.High, 70 },
                    { FeedbackLevel.Highest, 90 }
                },
        0,
        FeedbackLevel.Lowest);

    // [...]
}

これらの値は 「workload.config」ファイルでも設定することができます。 「workload.configの例」を参照してください。 LoadBalancing.LoadShedding.Configurationネームスペースによって設定ファイルから値を読み込むか、または設定ファイルが存在しない場合は、 DefaultConfigurationを適用します。

ゲームサーバーは一部のWindows Performance Countersを一定の間隔で確認し、全てのFeedbackControllersに最新の値を設定して、新しい「全体フィードバック」を計算します。

これは、WorkloadControllerクラスで実行されます:

private void Update()
{
    FeedbackLevel oldValue = this.feedbackControlSystem.Output;

    if (this.cpuCounter.InstanceExists)
    {
        var cpuUsage = (int)this.cpuCounter.GetNextAverage();
        Counter.CpuAvg.RawValue = cpuUsage;
        this.feedbackControlSystem.SetCpuUsage(cpuUsage);
    }

    // [...]

    if (this.timeSpentInServerInCounter.InstanceExists && this.timeSpentInServerOutCounter.InstanceExists)
    {
        var timeSpentInServer = (int)this.timeSpentInServerInCounter.GetNextAverage() + (int)this.timeSpentInServerOutCounter.GetNextAverage();
        Counter.TimeInServerInAndOutAvg.RawValue = timeSpentInServer;
        this.feedbackControlSystem.SetTimeSpentInServer(timeSpentInServer);
    }

    this.FeedbackLevel = this.feedbackControlSystem.Output;
    Counter.LoadLevel.RawValue = (byte)this.FeedbackLevel;

    if (oldValue != this.FeedbackLevel)
    {
        if (log.IsInfoEnabled)
        {
            log.InfoFormat("FeedbackLevel changed: old={0}, new={1}", oldValue, this.FeedbackLevel);
        }

        this.RaiseFeedbacklevelChanged();
    }
}

全体のフィードバックレベルに変更があった場合、OutgoingMasterServerPeerは新しいサーバーのステートをマスターにレポートします。

 public void UpdateServerState()
 {
    // [...]
    this.UpdateServerState(
        GameApplication.Instance.WorkloadController.FeedbackLevel,
        GameApplication.Instance.PeerCount,
        GameApplication.Instance.WorkloadController.ServerState);
}

Back To Top

Workload.config Exampleの例

トラフィックへの負荷を設定するには、最大トラフィックの90%を「最高」、70%を「高」、50%を「通常」、35%を「低」、20%を「最低」としてください。 ただし、これらの%値を使用したくない場合には、任意の値で最低/低/通常/高/最高を定義することもできます。

サーバーの帯域幅が20Mbps (20000000 B/秒)の場合、以下の値を設定可能です:

  • 最低: 4000000
  • 低: 7000000
  • 通常: 10000000
  • 高: 14000000
  • 最高: 18000000
<?xml version="1.0" encoding="utf-8" ?>
<FeedbackControlSystem>
  <FeedbackControllers>
    <add Name="Bandwidth" InitialInput="0" InitialLevel="Lowest">
      <FeedbackLevels>
        <add Level="Lowest" Value="4000000"/>
        <add Level="Low" Value="7000000"/>
        <add Level="Normal" Value="10000000"/>
        <add Level="High" Value="14000000"/>
        <add Level="Highest" Value="18000000"/>
      </FeedbackLevels>
    </add>
  </FeedbackControllers>
</FeedbackControlSystem>

「Highest」の場合のみ結果が生じ、値が超過する場合にはこのゲームサーバーにはもうゲームが作成されません。 さらに高い上限を使用したい場合には、そのアプリケーションがそのメッセージ数を処理できるかテストする必要があります。

Back To Top

ロードバランシングのアルゴリズム

実装の詳細については、「\src-server\Loadbalancing\Loadbalancing.sln」ソリューション内のLoadBalancing.LoadBalancerクラスを参照してください。

マスターサーバーは各ゲームサーバーの LoadLevel を、LoadBalancerクラスに格納しています。 またマスターサーバーには、現状のロードレベルが最低であるすべてのサーバーの追加リストも保持しています。

クライアントがCreateGame操作を呼び出す度に、マスターサーバーはLoadBalancerから、ロードレベルが最低のサーバーのアドレスをフェッチし、それをクライアントに返します;クライアントは、そのアドレスでゲームサーバーに接続します。

Back To Top

設定とデプロイメント

デモ用に、SDKにはデプロイディレクトリに1つのマスターサーバーと2つのゲームサーバーのセットアップが含まれています: - "/deploy/LoadBalancing/Master" - "/deploy/LoadBalancing/GameServer"

このセットアップはローカル開発のみを目的としています。

Back To Top

ゲームサーバーのデプロイ

LoadBalancingプロジェクトを本番サーバーにデプロイする時は、1つのサーバーに2つのゲームサーバーアプリケーションをホスティングしてはいけません。

ゲームサーバーがマスターサーバーに登録できることを確認する必要があります。 Photon.LoadBalancing.dll.config にある MasterIPAddressをマスターのパブリックIPに設定してください。

また、ゲームクライアントがゲームサーバーに到達できることを確認してください。 それぞれのゲームサーバーで、ゲームサーバーにパブリックIPアドレスを設定してください。 値を空のままにすると、パブリックIPアドレスは自動的に検出されます。

パブリックIPの設定にPhoton Controlを使用することもできます。

Back To Top

マスターサーバーのデプロイ

マスタサーバが1つであることを確認してください:ゲームサーバーのPhotonServer.configから"Master"アプリケーションのすべての設定を取り除くか、またはゲームサーバーと全てのクライアントが同じIPで1つのマスタサーバーに接続していることを確認してください。

それ以外は、マスタサーバー上で特別に必要な設定はありません。

Back To Top

ゲームサーバーをローテーションから排除

ロードバランシングの実装」セクションで説明したとおり、マスタサーバーはゲームサーバーの状態を「ServerState」 の列挙から認識します:

  • "online" (これがデフォルトです)
  • "out of rotation" (オープンなゲームはまだロビーにリストされ、プレイヤーはそのサーバー上の既存のゲームに参加できます。ただし、新しいゲームは作成されません。)
  • "offline" (そのサーバー上の既存のゲームには参加できず、またそのゲームサーバーには新しいゲームを作成できません)

ゲームサーバーは、自身の状態をマスターサーバーに定期的に送信しますーOutgoingMasterServerPeerクラスを参照してください:

public void UpdateServerState()
{
  if (this.Connected == false)
  {
    return;
  }

  this.UpdateServerState(
                this.application.WorkloadController.FeedbackLevel,
                this.application.PeerCount,
                this.application.WorkloadController.ServerState);
}

サーバーの状態をプログラミングで設定するには、以下をおこなう必要があります:

  • WorkloadControllerクラスを修正して、現在のサーバーの状態を判定させます。
  • たとえば、「file watcher」を追加してテキストファイルからサーバー状態を読み込みます(0 / 1 / 2)。

クライアントから呼び出されるオペレーションを構築することもできます。またはデータベースから読み込むなど、任意の設定をおこなえます。

ドキュメントのトップへ戻る