チュートリアル: ゲームのリーダーボードとして Memorystore for Redis を使用する


このチュートリアルでは、Memorystore for Redis を使用して Google Kubernetes Engine(GKE)で実行される ASP.NET ベースのリーダーボード アプリケーションを作成し、JavaScript ベースの別のサンプルゲームを使用してスコアを投稿および取得する方法について説明します。このドキュメントは、独自のリーダーボードをクラウドで運用することを必要とするゲーム デベロッパーを対象としています。

はじめに

リーダーボードは、マルチプレーヤー ゲームで一般的な機能です。リーダーボードは、プレーヤーのランキングをリアルタイムで表示します。これにより、プレーヤーのゲームへの関与を促進できます。プレーヤーのアクションをキャプチャし、スコアをリアルタイムでランク付けするには、Redis などのインメモリ データベースが最適です。

次の図は、リーダーボードのインフラストラクチャを示しています。

VPC 内の GKE クラスタと個別の Memorystore for Redis インスタンスを示す図。

Google Cloud では、リーダーボードは VPC ネットワーク内の GKE クラスタと個別の Memorystore for Redis インスタンスで構成されます。

次の図は、このチュートリアルのアプリケーション アーキテクチャを示しています。クライアントはリーダーボード API を使用して、Google Cloud で実行されている Memorystore for Redis インスタンスに保持されているスコアを操作します。

このチュートリアルで使用するアプリケーションのアーキテクチャを示す図

リーダーボード API のメソッド

リーダーボード アプリケーションの API には、次のメソッドが含まれています。

  • PostScore(string playerName, double score): このメソッドは、指定されたプレーヤーのスコアをリーダーボードに投稿します。
  • RetrieveScores(string centerKey, int offset, int numScores): このメソッドは一連のスコアをダウンロードします。centerKey の値としてプレーヤー ID を渡すと、このメソッドは、指定されたプレーヤーのスコアの上下にあるスコアを返します。centerKey の値を渡さないと、メソッドは上位 N 個の絶対スコアを返します。N は numScores に渡す値です。たとえば、上位 10 個のスコアを取得するには、RetrieveScores('', 0, 10) を呼び出します。プレーヤーのスコアの上下 5 つのスコアを取得するには、RetrieveScores('player1', -5, 10) を呼び出します。

このサンプルのコード リポジトリには、疑似ゲームと概念実証のリーダーボードの実装が含まれています。このチュートリアルでは、ゲームとリーダーボードをデプロイして、リーダーボード API が正しく機能し、インターネット経由でアクセスできることを検証します。

目標

  • Memorystore for Redis インスタンスを作成する。
  • このインスタンスにリクエストを送信するエンドポイントでヘッドレス サービスを作成する。
  • リーダーボード アプリケーションを GKE にデプロイする。
  • API 呼び出しを行うデプロイ済みのアプリケーションを使用して、リーダーボード機能を検証する。

費用

このドキュメントでは、Google Cloud の次の課金対象のコンポーネントを使用します。

料金計算ツールを使うと、予想使用量に基づいて費用の見積もりを生成できます。 新しい Google Cloud ユーザーは無料トライアルをご利用いただける場合があります。

始める前に

  1. Google Cloud アカウントにログインします。Google Cloud を初めて使用する場合は、アカウントを作成して、実際のシナリオでの Google プロダクトのパフォーマンスを評価してください。新規のお客様には、ワークロードの実行、テスト、デプロイができる無料クレジット $300 分を差し上げます。
  2. Google Cloud Console の [プロジェクト セレクタ] ページで、Google Cloud プロジェクトを選択または作成します。

    プロジェクト セレクタに移動

  3. Google Cloud プロジェクトで課金が有効になっていることを確認します

  4. Memorystore for Redis and Google Kubernetes Engine API を有効にします。

    API を有効にする

  5. Google Cloud Console の [プロジェクト セレクタ] ページで、Google Cloud プロジェクトを選択または作成します。

    プロジェクト セレクタに移動

  6. Google Cloud プロジェクトで課金が有効になっていることを確認します

  7. Memorystore for Redis and Google Kubernetes Engine API を有効にします。

    API を有効にする

環境の準備

このチュートリアルでは、Cloud Shell でコマンドを実行します。Cloud Shell では Google Cloud のコマンドラインにアクセスできるほか、Google Cloud で開発を行うために必要な Google Cloud CLI などのツールも利用できます。Cloud Shell の初期化には数分かかることがあります。

  1. Cloud Shell を開きます。

    Cloud Shell を開く

  2. デフォルトの Compute Engine ゾーンを Google Cloud リソースを作成するゾーンに設定します。このチュートリアルでは、us-central1 リージョンの us-central1-a ゾーンを使用します。

    export REGION=us-central1
    export ZONE=us-central1-a
    gcloud config set compute/zone $ZONE
    
  3. サンプルコードを含む GitHub リポジトリのクローンを作成します。

    git clone https://github.com/GoogleCloudPlatform/memstore-gaming-leaderboard.git
    
  4. 作成されたディレクトリのクローンに移動します。

    cd memstore-gaming-leaderboard
    
  5. テキスト エディタを使用して、leaderboardapp/k8s/appdeploy.yaml ファイルを開き、[YOUR_PROJECT_ID] プレースホルダを Google Cloud プロジェクト ID に変更します。

Memorystore for Redis インスタンスの作成

このチュートリアル用に、テストとこのチュートリアルの対象範囲に適した Memorystore for Redis のベーシック ティア インスタンスを作成します。本番環境にデプロイする場合は、インスタンスの高可用性を確保するために、自動フェイルオーバーを備えた 99.9% SLA を提供するスタンダード ティア インスタンスをデプロイすることをおすすめします。スタンダード ティア インスタンスの詳細については、Memorystore for Redis の高可用性をご覧ください。

  • Cloud Shell で 1 GB の Memorystore for Redis インスタンスを作成します。

    gcloud redis instances create cm-redis --size=1 \
      --tier=basic \
      --region=$REGION \
      --zone=$ZONE
    

    このコマンドが完了するまでに数分かかる場合があります。

アプリケーション コンテナ イメージの構築

このチュートリアルでは、GKE を使用して簡単なリーダーボード アプリケーションをデプロイします。Unity と C# はゲームで人気があるため、アプリケーション層では C# と ASP.NET フレームワークを使用します。

リーダーボード API を GKE クラスタにデプロイするには、まず Container Registry などのレジストリにアップロードする必要があります。

  1. クローンを作成した GitHub プロジェクトで README.md ファイルを開きます。
  2. 手順に従って Docker イメージを作成し、Container Registry にアップロードします。

次のセクションでクラスタを作成した後、このイメージを使用してリーダーボード アプリケーションをデプロイします。

GKE クラスタの作成または再利用

リーダーボード アプリケーションをデプロイする前に、有効なエイリアス IP 範囲を使用した GKE クラスタを作成する必要があります。エイリアス IP 範囲を使用した GKE クラスタがすでにある場合は、そのクラスタをこのチュートリアルで使用できます。既存のクラスタを使用する場合は、そのクラスタの認証情報を使用して kubectl コマンドライン ツールを構成する手順も実施する必要があります。

  1. クラスタを作成する場合は、2 つのノードを持つ leaderboard という名前のクラスタを作成します。

    gcloud container clusters create leaderboard --num-nodes=2 --enable-ip-alias
    

    このコマンドが完了するまでには数分かかる場合があります。

  2. クラスタが起動したら、稼働状態にあることを確認します。

    gcloud container clusters list
    

    leaderboard という名前のエントリでステータスが RUNNING になっていれば、クラスタが実行されています。

既存のクラスタの認証情報を構成する

  1. このチュートリアルで既存の GKE クラスタを使用している場合は、そのクラスタの認証情報を使用して kubeconfig ファイルを構成します。name-of-existing-cluster にクラスタの名前を設定します。

    gcloud container clusters get-credentials name-of-existing-cluster
    

Memorystore for Redis インスタンスと Kubernetes Service のマッピング

Memorystore for Redis インスタンスの準備ができたら、アプリケーション階層が接続できるように GKE クラスタ内に Service を作成します。

  1. Memorystore for Redis インスタンスが実行されていることを確認します。

    gcloud redis instances list --region=$REGION
    

    次のような出力が表示されます。

    INSTANCE_NAME  VERSION    REGION           TIER       SIZE_GB    HOST       PORT  NETWORK  RESERVED_IP  STATUS    CREATE_TIME
    cm-redis       REDIS_4_0  us-central1      STANDARD  1            10.0.0.3  6379  default  10.0.0.0/29  READY     2019-05-10T04:37:45
    

    STATUS 列に READY と表示されている場合、インスタンスは実行中です。HOST フィールドが空の場合、インスタンスは起動プロセスを完了していません。少し待ってから、redis instances list コマンドを再度実行します。

  2. インスタンスの IP アドレスを環境変数に保存します。

    export REDIS_IP=$(gcloud redis instances list --filter="name:cm-redis" --format="value(HOST)" \
        --region=$REGION)
    
  3. Redis 用の Kubernetes Service を作成します。

    kubectl apply -f k8s/redis_headless_service.yaml
    

    このサービスにはポッドセレクタがありません。これは、Kubernetes クラスタの外部にある IP アドレスを参照するためです。

  4. 前に取得した Redis IP アドレスを定義するエンドポイントを作成します。

    sed "s|REDIS_IP|${REDIS_IP}|g" k8s/redis_endpoint.yaml | kubectl apply -f -
    
  5. サービスとエンドポイントが正しく作成されたことを確認します。

    kubectl get services/redis endpoints/redis
    

    すべてが正常に機能している場合、次のような出力が表示され、Redis サービスと Redis エンドポイントのエントリが表示されます。

    NAME               TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
    service/redis      ClusterIP   10.59.241.103   none        6379/TCP   5m
    
    NAME               ENDPOINTS       AGE
    endpoints/redis    10.0.0.3:6379   4m
    

Kubernetes Services とエンドポイントの詳細については、Google Cloud ブログの Kubernetes のベスト プラクティス: 外部サービスのマッピングをご覧ください。

リーダーボード アプリケーションとサービスを Kubernetes にデプロイする

Redis インスタンスは、GKE クラスタにデプロイされたアプリケーションから到達可能になっています。リーダーボード アプリケーションをデプロイする準備が整いました。

  • 先ほどクローンを作成した GitHub リポジトリのルートに格納されている README.md ファイルに記された手順に沿って進めます。

デプロイの検証

JavaScript で記述された疑似ゲーム アプリケーションを使用して、リーダーボード API を呼び出します。次のコード スニペットでは、プレーヤーがプレイを終了した後、疑似ゲームがスコアを投稿しています。

                    var scoreInfo = {
                        playerName: this.username,
                        score: this.calculateScore().toFixed(2)
                    };

                    var pThis = this;

                    var postUrl = "/api/score";
                    (async () => {
                        try {
                            await axios.post(postUrl, scoreInfo)
                        } catch (error) {
                            console.error(error);
                        }

                        var lbPromise = pThis.fetchLeaderboard();
                        var qPromise = pThis.fetchQuestions();

                        pThis.questions = await qPromise;
                        await lbPromise;
                    })();

さらに、アプリが API 呼び出しを行い、リーダーボード スコアを取得します。次のように、トップスコアまたはプレーヤー名の前後のスコアを取得します。

                    var pThis = this;
                    var getUrl = "/api/score/retrievescores";

                    (async () => {
                        try {
                            var params = {
                                centerKey: '',
                                offset: 0,
                                numScores: 11
                            };

                            if (pThis.centered) {
                                params.centerKey = pThis.username;
                                params.offset = -5;
                                params.numScores = 11;
                            }

                            const response = await axios.get(getUrl, { params: params });
                            pThis.leaderboard = response.data;
                        } catch (error) {
                            console.error(error);
                            return []
                        }
                    })();

サンプル アプリケーションにアクセスするには:

  1. 次のコマンドを実行して、疑似ゲームの IP アドレスを取得します。

    kubectl get ingress
    

    出力は次のようになります。

    NAME                      HOSTS   ADDRESS        PORTS   AGE
    memstore-gaming-ingress   *       34.102.192.4   80      43s
    
  2. ブラウザで次の URL にアクセスします。ip_address_for_gke は疑似ゲームのアドレスです。

    http://ip_address_for_gke.
    

このサンプルは単純ですが、基本的な API の使用方法を説明するには十分です。ユーザーのデバイスまたはマシンで実行されているゲーム クライアント アプリケーションからスコアを直接投稿または取得すると、このサンプルは Kubernetes Load Balancer オブジェクトに割り当てられたパブリック IP アドレスでリーダーボード API を呼び出します。このサンプル アプリケーションでは、リーダーボード API と JavaScript クライアントの両方が、前述の kubectl get ingress コマンドを使用して取得した同一の IP アドレスにホストされています。

メソッドの実装方法

C# で記述されたリーダーボード アプリケーションは、Redis との通信に StackExchange.Redis ライブラリを使用します。次のコード スニペットは、Redis と StackExchange.Redis ライブラリを使用して PostScoreRetrieveScores を実装する方法を示しています。

次のコード スニペットは、PostScore メソッドを示しています。

        public async Task<bool> PostScoreAsync(ScoreModel score)
        {
            IDatabase db = _redis.GetDatabase();

            // SortedSetAddAsync corresponds to ZADD
            return await db.SortedSetAddAsync(LEADERBOARD_KEY, score.PlayerName, score.Score);
        }

次のコード スニペットは、RetrieveScores メソッドを示しています。

        public async Task<IList<LeaderboardItemModel>> RetrieveScoresAsync(RetrieveScoresDetails retrievalDetails)
        {
            IDatabase db = _redis.GetDatabase();
            List<LeaderboardItemModel> leaderboard = new List<LeaderboardItemModel>();

            long offset = retrievalDetails.Offset;
            long numScores = retrievalDetails.NumScores;

            // If centered, get rank of specified user first
            if (!string.IsNullOrWhiteSpace(retrievalDetails.CenterKey))
            {
                // SortedSetRankAsync corresponds to ZREVRANK
                var rank = await db.SortedSetRankAsync(LEADERBOARD_KEY, retrievalDetails.CenterKey, Order.Descending);

                // If specified user is not present, return empty leaderboard
                if (!rank.HasValue)
                {
                    return leaderboard;
                }

                // Use rank to calculate offset
                offset = Math.Max(0, rank.Value + retrievalDetails.Offset);

                // Account for number of scores when we're attempting to center
                // at element in rank [0, abs(offset))
                if(offset <= 0)
                {
                    numScores = rank.Value + Math.Abs((long)retrievalDetails.Offset) + 1;
                }
            }

            // SortedSetRangeByScoreWithScoresAsync corresponds to ZREVRANGEBYSCORE [WITHSCORES]
            var scores = await db.SortedSetRangeByScoreWithScoresAsync(LEADERBOARD_KEY,
                skip: offset,
                take: numScores,
                order: Order.Descending);

            var startingRank = offset;
            for (int i = 0; i < scores.Length; i++)
            {
                var lbItem = new LeaderboardItemModel
                {
                    Rank = startingRank++,
                    PlayerName = scores[i].Element.ToString(),
                    Score = scores[i].Score
                };
                leaderboard.Add(lbItem);
            }

            return leaderboard;
        }

サンプルゲームへの追加

リーダーボード API は REST 規則に従っており、サンプルとしてのみ提供されています。本番環境のゲームのリーダーボードを実行する場合は、検証済みのユーザーから取得したスコアのみを投稿できるように、認証フローを統合することをおすすめします。

現在、Memorystore for Redis ではプレーヤーのスコアを保持できません。したがって、アプリケーションで問題が発生すると、リーダーボードの情報が失われる可能性があります。ゲームに永続的なリーダーボードが必要な場合は、永続的なデータベースにリーダーボードのスコアを定期的にバックアップする必要があります。

クリーンアップ

このチュートリアルで使用したリソースに対して Google Cloud アカウントに課金されないようにするには、プロジェクトを削除します。

プロジェクトの削除

  1. Google Cloud コンソールで、[リソースの管理] ページに移動します。

    [リソースの管理] に移動

  2. プロジェクト リストで、削除するプロジェクトを選択し、[削除] をクリックします。
  3. ダイアログでプロジェクト ID を入力し、[シャットダウン] をクリックしてプロジェクトを削除します。

次のステップ