This document is about: SERVER 5
SWITCH TO

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

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

コンセプト

ロードバランシングアプリケーションは、Hiveフレームワークと実装機能(ルーム、イベント、プロパティなど)を拡張するもので、複数サーバーでのアプリケーションの実行を可能にする拡張レイヤーが加えられました。

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

ロードバランシングにはロビーサポートとマッチメイキング機能も追加されました。

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

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

  • ゲームサーバー上で現在オープンになっているゲームの履歴を保持します。
  • 接続したゲームサーバーの負荷の履歴を保持し、適切なゲームサーバーにピアを割り当てます。
  • クライアントが利用できるルームのリストを「ロビー」に保持し、アップデートします。
  • クライアント向けにルームを検索し(ランダムまたは名前で)、クライアントにゲームサーバーのアドレスを転送します。

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

  • ゲームルームのホスティング。
  • ゲームサーバーの最新の負荷と、ゲームサーバーのゲームのリストをマスターサーバーに定期的に報告します。

Photon Cloudとの違い

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

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

これらの違いについて、詳細は こちらを参照してください。

基本ワークフロー

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

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

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

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

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

マスターサーバー

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

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

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

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

  • Authenticate
    Authenticate操作にはダミー実装しかありません。
    これは開発者が独自の認証メカニズムを実装する際、出発点として利用するためのものです。

C#

    // 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 操作のオプションのプロパティでフィルタリングされます)が一定の間隔でクライアントに送信されます。
    クライアントは、接続中はアップデートイベントを受信します。

    C#

    // 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はゲームの記録にのみ使用されますーゲーム自体はゲームサーバー上にのみ存在します。

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

マスターサーバーは、どのゲームサーバーが利用可能か、ゲームサーバーがゲームをいくつホスティングしているか、また最新の負荷について常に把握しています。
これを実現するには、各ゲームサーバーは起動時にマスターサーバーに接続する必要があります。
MasterApplicationGameServerCollectionを管理し、このGameServerCollectionにはIncomingGameServerPeersが保存されています。

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

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

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

ゲームサーバー

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

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

クライアントがマスターからゲームサーバーアドレスを受信した時点で、クライアントはHiveで利用可能なゲームサーバー上で、どの操作でも呼び出すことができます(ルームに参加する際に)。
唯一違うのは、ゲームサーバーではJoinGameCreateGame操作に別々の操作コードが使われる点です。

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

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

C#

// OutgoingMasterServerPeer.cs:
protected virtual void HandleRegisterGameServerResponse(OperationResponse operationResponse)
{
    // [...]

    switch (operationResponse.ReturnCode)
    {
    case (short)ErrorCode.Ok:
        {
            log.InfoFormat("Successfully registered at master server: serverId={0}", GameApplication.ServerId);
            this.IsRegistered = true;
            this.UpdateAllGameStates();
            this.StartUpdateLoop();
            break;
        }
    }
}

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

C#

// OutgoingMasterServerPeer.cs:
public virtual void UpdateAllGameStates()
{
    // [...]

    foreach (var gameId in GameCache.Instance.GetRoomNames())
    {
        Room room;
        if (GameCache.Instance.TryGetRoomWithoutReference(gameId, out room))
        {
            room.EnqueueMessage(new RoomMessage((byte)GameMessageCodes.ReinitializeGameStateOnMaster));
        }                
    }
}

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

C#

 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());
        }
}

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

LoadBalancing Implementation

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

コンセプト

ロードバランサーは3つの基本的な要素とともに作動します:サーバー負荷レベル。サーバーウェイト、およびサーバー自体です。

サーバー

ロードバランサーによって操作されるオブジェクトです。
サーバーには負荷レベルとウェイトがあります。
ウェイトはロードバランサーによって提供され、負荷レベルは外部ロジックによってロードバランサーに提供されます。

サーバーの負荷レベル

サーバーの負荷レベルを定義する定数のセット。
サーバはその負荷レベルを測定/計算し、変更があるたびにロードバランサーに通知します。
Photon Server v4の最初のバージョンでは、5つのレベルしかありませんでした。
Photon Server v5には10レベルが含まれています。

サーバーウェイト

サーバーウェイトは、ロードバランサーによって負荷レベルに付加されるいくつかの数値です。
構成された「WorkloadConfigFile」から取得するか、またはファイルが構成されていないか提供されていない場合は、いくつかのデフォルト値を取得する。
これらの数値は、LoadBalancer から返されるサーバーの確率を定義します。
したがって、負荷レベルが高いほど、その確率を下げるために重みを小さくする必要があります。
最高レベルの場合、重みはゼロです。
このような負荷レベルのサーバーはまったく使用されません。
いわゆる、幾何学的確率が使用されます。
各サーバーにセグメントを割り当てます。
このセグメントの長さは、サーバーウェイトで定義されます。
そして、すべてのセグメントを連結して1つの大きなセグメントを作ります。
サーバーは負荷レベルでソートされます。
負荷の低いものから順に並べます。
次に、乱数を取得し(乱数は [0 ...すべてのサーバーウェイトの合計] の範囲から取得します)、どのサーバセグメントにその乱数が含まれているかを調べます。

:
たとえば、3つのサーバー'A'、'B'および'C'があり、それぞれのウェイトが3(|___A|)、 1(|_B|)、 5 (|_____C|)とします。
大きなセグメントは5、3、1 (|_____C|___A|_B|)となります。
そして、0から8 ( 5 + 3 + 1)の間で乱数を設定します。
[0, 4] -> サーバーC、[5, 7] サーバーA, [8] - サーバーB。

: 開閉可能な間隔:

  • [a, b): 左を含むが、右は含まない
  • (a, b): aもbも含まない

サーバーのステート

ゲームサーバーのステートを定義する3つの定数があります:「Normal」、「Offline」、「OutOfRotation」です。
LoadBalancerの基本的な実装では、サーバーのステートに留意していませんでした。
すべてのサーバーの状態は「Normal」とみなされていました。
ステートが'Normal'から'Offline'または'OutOfRotation'のいずれかに変更されると、LoadBalancerの外部ロジックによってサーバーが削除されます。
Photon Server v5では、ロードバランサーはサーバーのステートを認識しています。
このためサーバーのステートが'Normal'でない場合、サーバーがLoadBalancerから返されることはありません。

サーバーのステートはテキストファイルで設定します。
ファイル名は「deploy\LoadBalancing\GameServer\bin\GameServer.xml.config」
の「ServerState」設定で設定します。
パスはデプロイフォルダからの相対パスです。
デフォルトでは「ServerState.txt」となっていますが、デプロイフォルダからの相対パスなので「deploy\ServerState.txt」となります。

優先順位

クラスタへのサーバーの迅速な追加が必要な場合があります。
通常、新しいサーバーを迅速に導入する方法を提供するプロバイダーはコストがかかります。
だからこそ、それらのサーバをできるだけ早く(不要になったらすぐに)ダウンさせた方がいいのです。
ここで、Photon 5のLoadBalancerの優先順位が役立ちます:

サーバーのメインプールの負荷が閾値に達すると、ロードバランサーはプールからさらに高い優先順位のサーバーを取得します。
逆の場合も同様です。
クラスタの負荷が一定の低い値に達すると、優先度の高いゲームサーバーが除外される、といった具合です。

「deploy\LoadBalancer.config」でロードバランサーの2つのプロパティを設定する必要があります。

  • PriorityDownThreshold`: より高い優先度のサーバーが利用可能なサーバープールから除外されるときの負荷の値を定義する。
  • PriorityUpThreshold`: より高い優先度のサーバーが利用可能なサーバープールに含まれるときの負荷値を定義します。

これらの値に留意し、新しいサーバーを追加したときに負荷がPriorityDownThreasholdを下回らないようにし、優先度の高いサーバーを削除したときに残りのサーバーの負荷がPriorityUpThresholdに達しないようにしなければなりません。

GameSever側では、GameServer.xml.configでサーバーの優先順位を設定する必要がある。
GameServerセクションで、LoadBalancerPriority設定を追加します。
0がデフォルト値です。
これは第一階層のサーバー用です。
他にも1、2、3などの優先順位を使用することができます。

最初のうちは、最も低い優先順位のサーバーを使用します。
通常、それは0です。
このようなサーバーがすべて、あらかじめ設定されたレベルに達すると、次の優先度を追加します。
例えば、優先度を「1」とします。
負荷レベルが下がり、configで事前に定義された別の閾値に達すると、優先度「1」のすべてのサーバーをロードバランサーから外し、これらのサーバーは使用されなくなります。

予約

この背景にある考え方は簡潔で、次のようなものです。
クラスタの負荷は、24時間のうちで一定ではありません。
日中は負荷が高く、夜間は低くなります。
どのような場合でも、それをうまく処理できるだけの十分なサーバーを用意する必要があります。
しかし場合によっては、サーバーをあるクラスタから別のクラスタに素早く移動させて、クラスタの負荷を軽減する必要があります。
そのために導入したのがReserveRatioです。
「deploy\LoadBalancer.config」で設定したロードバランサーでは、ReserveRatioを定義できます。
デフォルトでは「0.0」となっており、予約されたサーバーがないことを意味します。
この値を0.2に設定すると、10台のサーバーの場合にはロードバランサーは2台のサーバーを予約します。
9台のサーバーの場合は、1つのサーバーのみが予約されます。
このロジックは、優先順位の動作に似ています。
サーバーの負荷が定義された値に達すると、予備から1つのサーバーだけを取り出します。
それだけでは足りない場合は、さらに1台を確保します。
その後、使用中のサーバーを1台ずつ予備に戻します。

予約は各優先度ごとに独立して動作します。
すべての優先度はそれぞれの予約を持っています。
次の優先度は、予約したサーバーを含む現在の優先度のすべてのサーバーがあらかじめ定義された負荷レベルに達したときにのみ使用されます。

負荷の判定

See the Photon.Common.LoadBalancer.LoadShedding namespace in the "\src-server\LoadBalancing\LoadBalancing.sln" solution for implementation details.
Respective source files are located under "\src-server\Photon.Common\LoadBalancer\LoadShedding".

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

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

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

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

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

実装の詳細

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

C#

namespace Photon.Common.LoadBalancer.LoadShedding
{
    public enum FeedbackName
    {
        CpuUsage,
        PeerCount,
        Bandwidth,
        OutOfRotation
    }
}

C#

namespace Photon.Common.LoadBalancer.LoadShedding
{
    public enum FeedbackLevel
    {
        Lowest = 0,
        Level0 = Lowest,
        Level1 = 1,
        Level2 = 2,
        Level3 = 3,
        Level4 = 4,
        Level5 = 5,
        Level6 = 6,
        Level7 = 7,
        Level8 = 8,
        Level9 = 9,
        Highest = Level9,
        LEVELS_COUNT
    }
}

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

C#

// src-server\Photon.Common\LoadBalancer\LoadShedding\Configuration\DefaultConfiguration.cs:
internal class DefaultConfiguration
{
    internal static List<FeedbackController> GetDefaultControllers()
    {
        var cpuController = new FeedbackController(
        FeedbackName.CpuUsage,
        new SortedDictionary<FeedbackLevel, FeedbackLevelData>
                {
                    { FeedbackLevel.Level0, new FeedbackLevelData(10, 0) },
                    { FeedbackLevel.Level1, new FeedbackLevelData(20, 9 ) },
                    { FeedbackLevel.Level2, new FeedbackLevelData(30, 19) },
                    { FeedbackLevel.Level3, new FeedbackLevelData(40, 29) },
                    { FeedbackLevel.Level4, new FeedbackLevelData(50, 38) },
                    { FeedbackLevel.Level5, new FeedbackLevelData(60, 48) },
                    { FeedbackLevel.Level6, new FeedbackLevelData(70, 57) },
                    { FeedbackLevel.Level7, new FeedbackLevelData(80, 67) },
                    { FeedbackLevel.Level8, new FeedbackLevelData(90, 77) },
                    { FeedbackLevel.Level9, new FeedbackLevelData(int.MaxValue, 77) }
                },
        0,
        FeedbackLevel.Lowest);

    // [...]
}

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

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

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

C#

// src-server\Photon.Common\LoadBalancer\LoadShedding\WorkloadController.cs
private void Update()
{
    if (!this.IsInitialized)
    {
        return;
    }

    FeedbackLevel oldValue = this.feedbackControlSystem.Output;

    if (this.cpuCounter.IsValid)
    {
        var cpuUsage = (int)this.cpuCounter.GetNextAverage();
        Counter.CpuAvg.RawValue = cpuUsage;

        WorkloadPerformanceCounters.WorkloadCPU.RawValue = cpuUsage;
        FeedbackLevel level;
        this.feedbackControlSystem.SetCpuUsage(cpuUsage, out level);
        WorkloadPerformanceCounters.WorkloadLevelCPU.RawValue = (byte)level;
    }

    if (this.bytesInOutCounters.IsValid)
    {
        int bytes = (int)this.bytesInOutCounters.GetNextAverage();
        Counter.BytesInAndOutAvg.RawValue = bytes;

        WorkloadPerformanceCounters.WorkloadBandwidth.RawValue = bytes;
        FeedbackLevel level;
        this.feedbackControlSystem.SetBandwidthUsage(bytes, out level);
        WorkloadPerformanceCounters.WorkloadLevelBandwidth.RawValue = (byte)level;
    }

    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();
    }

    this.UpdateWindowsSpecificCounters();
}

全体のフィードバックレベルに変更があった場合、RaiseFeedbacklevelChanged は新しいサーバーのステートをマスターにレポートします。これはOutgoingMasterServerPeerクラス内のUpdateServerStateのトリガーとなります。

C#

// src-server\LoadBalancing\LoadBalancing\GameServer\OutgoingMasterServerPeer.cs
private void UpdateServerState()
{
    if (this.Connected == false)
    {
        return;
    }

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

workload.configの例

XML

<?xml version="1.0" encoding="utf-8" ?>
<FeedbackControlSystem>
  <FeedbackControllers>
    <!-- CPU -->
    <add Name="CpuUsage" InitialInput="0" InitialLevel="Lowest">
      <FeedbackLevels>
        <add Level="Level0" Value="10"  ValueDown="0"/>
        <add Level="Level1" Value="20"  ValueDown="9"/>
        <add Level="Level2" Value="30"  ValueDown="19"/>
        <add Level="Level3" Value="40"  ValueDown="29"/>
        <add Level="Level4" Value="50"  ValueDown="39"/>
        <add Level="Level5" Value="60"  ValueDown="49"/>
        <add Level="Level6" Value="70"  ValueDown="58"/>
        <add Level="Level7" Value="80"  ValueDown="67"/>
        <add Level="Level8" Value="90"  ValueDown="77"/>
        <add Level="Level9" Value="110" ValueDown="77"/>
     </FeedbackLevels>
    </add>

    <!-- Bandwidth -->
    <!-- 10 / 30 / 40 / 80 / 100 MB -->
    <add Name="Bandwidth" InitialInput="0" InitialLevel="Lowest">
        <FeedbackLevels>        
            <add Level="Level0" Value="10485760"  ValueDown="0" />
            <add Level="Level1" Value="20971520"  ValueDown="10480000" />
            <add Level="Level2" Value="31457280"  ValueDown="20968000" />
            <add Level="Level3" Value="41943040"  ValueDown="31450000" />
            <add Level="Level4" Value="52428800"  ValueDown="41940000" />
            <add Level="Level5" Value="62914560"  ValueDown="52420000" />
            <add Level="Level6" Value="73400320"  ValueDown="62910000" />
            <add Level="Level7" Value="83886080"  ValueDown="73390000" />
            <add Level="Level8" Value="94371840"  ValueDown="83880000" />
            <add Level="Level9" Value="115343360" ValueDown="83880000" /><!-- Level1*11 -->
        </FeedbackLevels>
    </add>
  </FeedbackControllers>

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

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

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

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

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

設定とデプロイメント

This setup is only intended for local development.
デモ用に、SDKにはデプロイディレクトリに1つのマスターサーバーと2つのゲームサーバーのセットアップが含まれています:

  • "/deploy/LoadBalancing/Master"
  • "/deploy/LoadBalancing/GameServer"

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

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

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

ゲームサーバーがマスターサーバーに登録できることを確認する必要があります。

「GameServer.xml.config」にあるMasterIPAddressをマスターのパブリックIPに設定してください。

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

XML

<GameServer>
      <MasterIPAddress>127.0.0.1</MasterIPAddress>      
      <PublicIPAddress>127.0.0.1</PublicIPAddress>
        <!-- use this to auto-detect the PublicIPAddress: -->
        <!-- <PublicIPAddress></PublicIPAddress> -->
      <!-- [...] -->
</GameServer>

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

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

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

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

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

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

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

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

C#

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)。

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

ポートの設定

アプリケーションのポートを変更するには、「PhotonServer.config」で設定されたリスナーノードの「Port」属性値を編集します。
クライアントが新しいマスターサーバーポートに接続するように変更します。
Photonサーバーを再起動する。

マスターサーバーポートの変更

「マスター」アプリケーションのポートを変更した場合、NameServerアプリケーションの設定を更新する必要があります。
ポートが変更されたリスナーのプロトコルにマッチする設定を変更することです。
リスナー名と設定名の対応関係は以下の通りです。

  • 「deploy\bin_Win64\PhotonServer.config」内のリスナー名 -> "deploy\NameServer\bin\NameServer.xml.config"内の設定名
    「deploy\bin_Win64\PhotonServer.config」内のリスナー名 -> 「deploy\NameServer\bin\NameServer.xml.config」内の設定名
  • 「UDPListener」 -> 「MasterServerPortUdp」
  • 「TCPListener」 -> 「MasterServerPortTcp」
  • 「PeerType」および「Secure」(かつ「Url」)値に依存する「HTTPListener」
    • 「WebSocket」->「MasterServerPortWebSocket」/「MasterServerPortSecureWebSocket」(「MasterServerWsPath」はリスナー「URL」にもとづき変更される可能性があります)

例:

「deploy\bin_Win64\PhotonServer.config」でマスターサーバーのUDPポートを
デフォルトの5055から5555に変更した場合:

XML

<UDPListener
    IPAddress="0.0.0.0"
    Port="5555"
    OverrideApplication="Master">
</UDPListener>

UDPListenerに設定した値に合致するよう、「deploy\NameServer\bin\NameServer.xml.config」の「MasterServerPortUdp」の値を編集する必要があります :

XML

<NameServer>
  <MasterServerPortUdp>5555</MasterServerPortUdp>
</NameServer>

「NameServer」アプリケーションに設定されている「MasterServerPortXXX」の値は、「NameServer」アプリケーションが各プロトコルの認証を行う際に、「Master」アドレスと共にクライアントに返すポート番号に対応しています。
プロトコルにポートが設定されていない(欠落している)場合や、プロトコルにポートが正しく設定されていない(不一致)場合、クライアントがマスターサーバーに接続できないことがあります。

ゲームサーバーポートの変更

「ゲーム」アプリケーションのポートを変更した場合、そのアプリケーションの設定を更新する必要があります。
ポートが変更されたリスナーのプロトコルに合った設定を変更することが必要です。
リスナー名と設定名の対応関係は以下の通りです:

  • 「deploy\bin_Win64\PhotonServer.config」内のリスナー名 -> 「deploy\LoadBalancing\GameServer\bin\GameServer.xml.config」内の設定名
  • 「deploy\bin_Win64\PhotonServer.config」のリスナー名->「deploy\LoadBalancing\GameServer\bin\GameServer.xml.config」の設定名
  • 「UDPListener」->「GamingUdpPort」
  • 「TCPListener」->「GamingTcpPort」
  • 「PeerType」および「Secure」(および「Url」) 値に依存する「HTTPListener」:
    • 「WebSocket」->「GamingWebSocketPort」/「GamingSecureWebSocketPort」
      (「GamingWsPath」をリスナー「Url」にもとづいて変更することも可能です)

例:

「deploy\bin_Win64\PhotonServer.config」でゲームサーバーのUDPポートの値をデフォルトの5056から6666に変更した場合:

XML

<UDPListener
    IPAddress="0.0.0.0"
    Port="6666"
    OverrideApplication="Game">
</UDPListener>

UDPListenerに設定した値に合致するよう、「deploy\LoadBalancing\GameServer\bin\GameServer.xml.config」の「GamingUdpPortp」の値を編集する必要があります :

XML

<GameServer>
  <Master>
    <GamingUdpPort>6666</GamingUdpPort>
  </Master>
</GameServer>

「deploy\LoadBalancing\GameServer\GameServer.xml.config」の「Game」アプリケーションに設定されている「GamingXXXPort」設定の値は、各プロトコルのマッチメイキングで「Master」アプリケーションがクライアントに返す「Game」アドレスのポート番号に対応しています。
もし、あるプロトコルにポートが設定されていなかったり(欠落)、正しく設定されていなかったり(不一致)すると、クライアントがそのプロトコルを使ってルームに参加したり、ルームを作成したりする際に問題が発生する可能性があります。
実際には、「Game」サーバーアプリケーションに接続することができません。

ポートの追加

アプリケーションごとに複数のポートを設定することができます。
これは、一部のクライアントが特定のプロトコルを使用してアプリケーションに到達することが困難であり、そのプロトコルをすべてのクライアントに対して変更することができない場合に役立ちます。
クライアントは、特に「Game」サーバーアプリケーションに接続する際に、必要に応じて明示的にこれらのポートに切り替える必要があります。

例:

デフォルトではアプリケーションごとに、代替UDPポートを提供しています。
クライアントはポート5055を使用してマスターサーバーに接続し、代わりに27001にも接続できます。
クライアントはポート5056を使用してゲームサーバーに接続し、代わりに27002にも接続できます。
クライアントはポート5058を使用してネームサーバーに接続し、代わりに27000にも接続できます。

XML

    <UDPListeners>
      <UDPListener
        IPAddress="0.0.0.0"
        Port="5055"
        OverrideApplication="Master">
      </UDPListener>
      <UDPListener
        IPAddress="0.0.0.0"
        Port="27001"
        OverrideApplication="Master">
      </UDPListener>
      <UDPListener
        IPAddress="0.0.0.0"
        Port="5056"
        OverrideApplication="Game">
      </UDPListener>
      <UDPListener
        IPAddress="0.0.0.0"
        Port="27002"
        OverrideApplication="Game">
      </UDPListener>
      <UDPListener
        IPAddress="0.0.0.0"
        Port="5058"
        OverrideApplication="NameServer">
      </UDPListener>
      <UDPListener
        IPAddress="0.0.0.0"
        Port="27000"
        OverrideApplication="NameServer">
      </UDPListener>
    </UDPListeners>
Back to top