0

問題

Firebase Realtime Database (Unity 用) を使用してターン ベースのゲームのサーバー側を管理していますが、マッチメイキングに問題があります... ダウンロードの使用率が高いです。

すべてのオンライン ゲームには、2 人以上のプレイヤーがゲームに参加することを避けるための 2 つの基本状態があります:作成済みと参加済み

  • Created : プレイヤーがゲームに参加しようとしましたが、見つからない場合は新しいゲームが作成されます
  • Joined : プレーヤーがゲームに参加しようとして、状態が Created から Joined に変わった場合

RunTransaction2 人以上のプレイヤーがゲームに参加できないようにするために使用していますが、ローカル キャッシュが原因でデータベースから最新のデータがフェッチされていないことを確認しました。子ノードを追加keepSyncedするmatches-{lang}と常に最新のデータが保持されますが、当然、ダウンロードの使用率が高い。

private DatabaseReference DatabaseReference()
{
    return FirebaseDatabase.DefaultInstance.RootReference.Child(MatchesLocation(LanguageManager.Manager.GetPlayerLanguageCode()));
}

private DatabaseReference DatabaseReferenceLangMatch(Language language)
{
    return FirebaseDatabase.DefaultInstance.RootReference.Child(MatchesLocation(LanguageManager.Manager.GetLanguageCode(language)));
}

public void ManageKeepSyncedMatches(Language lang)
{
    DatabaseReferenceLangMatch(Language.English).KeepSynced(lang == Language.English);
}

public void JoinMatchTransaction(GameMatchOnline gameMatchOnline, UnityAction<string, bool> callback)
{
    JoinTransactionAbort joinResult = JoinTransactionAbort.None;

    DatabaseReference matchesListRef = DatabaseReference();
    Dictionary<string, object> joinerDict = gameMatchOnline.ToJoinDictionary();

    matchesListRef.Child(gameMatchOnline.matchId).RunTransaction(matchData =>
    {
        Dictionary<string, object> matchDict = matchData.Value as Dictionary<string, object>;
        if (matchDict == null)
        {
            joinResult = JoinTransactionAbort.Null;
            return TransactionResult.Success(null);
        }

        if (!matchDict.ContainsKey("state"))
        {
            joinResult = JoinTransactionAbort.Error;
            return TransactionResult.Abort();
        }
        GameMatchOnline.State state = (GameMatchOnline.State)System.Convert.ToInt32(matchDict["state"]);

        if (state != GameMatchOnline.State.Created)
        {
            joinResult = JoinTransactionAbort.Error;
            return TransactionResult.Abort();
        }

        joinResult = JoinTransactionAbort.None;

        matchDict.Add("joinerInfo", joinerDict["joinerInfo"]);
        matchDict["state"] = joinerDict["state"];
        matchData.Value = matchDict;

        return TransactionResult.Success(matchData);

    }).ContinueWith(task =>
    {
        // Fail
        if (task.IsFaulted || task.IsCanceled)
        {
            UnityThread.executeInUpdate(() =>
            {
                if (joinResult == JoinTransactionAbort.Error)
                {
                    callback(null, false);
                }
            });
        }
        // Can Join match
        else if (task.IsCompleted)
        {
            UnityThread.executeInUpdate(() =>
            {
                if (joinResult == JoinTransactionAbort.None)
                {
                    AddListenerResultsValueChanged(gameMatchOnline.matchId, gameMatchOnline.joinerInfo.userId, gameMatchOnline.isPrivate, gameMatchOnline.language);
                    callback(gameMatchOnline.matchId, true);
                }
                else
                {
                    callback(null, false);
                }
            });
        }
    });
}

質問

  • プレイヤーを削除keepSyncedすると、ローカルにキャッシュされた の情報が保持されますmatches-{lang}。これを行うことで、ゲームごとに 2 人以下のプレイヤーが存在することを信頼できますか? *トランザクションは、このような問題を回避するためのものです。
  • リクエストのローカル キャッシュを回避して、常に更新されたデータを取得する方法はありますか?
  • ゲームを別のノードに移動してノードのサイズを縮小することが最善の解決策matches-{lang}でしょうか?

ありがとう!

4

1 に答える 1

1

「keepSynced」プレイヤーを削除すると、「試合」の情報がローカルにキャッシュされます。これを行うことで、ゲームごとに 2 人以下のプレイヤーしか存在しないと信じていいですか? *トランザクションは、この種の問題を回避するためのものです。

KeepSynced がオフの場合でも、トランザクションはローカル キャッシュにヒットしてからインターネットにヒットします。これは遅延アクセスであるため (「すべての一致を取得する」などのことを行わないことを前提としています)、おそらく帯域幅を節約し、必要な保証を行うことができます。KeepSynced を使用するかどうかに関係なく、トランザクションを複数回実行できるように準備する必要があります (ローカル キャッシュが空の場合は null データに対して)。

リクエストのローカル キャッシュを回避して、常に更新されたデータを取得する方法はありますか?

修正

これは少し逆向きになったようです。詳細については、この回答を参照してください。キャッシュされた値を返し、更新された値を要求します。後続の呼び出しは、利用可能になったときに新しい値を取得します。可能な場合は常に使用するようにしてくださいValueChanged

古い答え:

You _can_ just say `GetValueAsync`, which has to bypass the cache since it will only fire once. You really should use ValueChanged listeners to listen for changes and Transactions to change data if you can to keep everything up to date and to avoid data races.

最善の解決策は、ゲームを別のノードに移動して「マッチ」ノードのサイズを縮小することでしょうか?

一般に、共有リソースにアクセスする人が少ないほど、パフォーマンスが向上します。まだお持ちでない場合は、Loteria の投稿をチェックして、チームが Realtime Database で Google Doodle になるのに十分な回復力を持つリアルタイム ゲームをどのように作成したかを確認してください。

TLDR は、プレイヤーがゲームの作成または検索を担当するのではなく、ゲームを探しているプレイヤーがキューに書き込まれるというものです。プレーヤーがマッチメイキング キューに追加されると、Cloud Function トリガーが起動され、ユーザーを接続する作業が行われます。クライアントは、ValueChanged リスナーをデータベースのプレーヤー エントリに配置し、ゲームが書き込まれるのを待つことで、自分がゲームに参加していることを認識します。

ゲームはさらに、いくつかの手動シャーディング ロジックによって低レイテンシーに保たれています。彼らはプロファイリングを実行して、単一のデータベースが処理できるトラフィックの量を確認し、いくつかの迅速な (手動 - 1 日で済むことだったので) スケーリング ロジックを記述して、多数のデータベースの 1 つにプレーヤーを分散させました。

すべてが役立つことを願っています!

-- パトリック

于 2020-04-21T15:57:03.280 に答える