Tutorial: como usar o Memorystore para Redis como uma tabela de classificação em jogos

Este tutorial mostra como usar o Memorystore para Redis para criar um aplicativo de tabela de classificação baseado em ASP.NET em execução no Google Kubernetes Engine (GKE) e, em seguida, postar e recuperar pontuações usando um jogo de amostra baseado em JavaScript separado. Este documento é destinado a desenvolvedores de jogos que desejam operar suas próprias tabelas de classificação na nuvem.

Introdução

As tabelas de classificação são um recurso comum em jogos multijogador. Uma tabela de classificação mostra a classificação dos jogadores em tempo real, o que ajuda a incentivar os jogadores a se envolverem mais no jogo. Para capturar as ações dos jogadores e classificar a pontuação em tempo real, um banco de dados na memória como o Redis é uma ótima opção.

O diagrama a seguir mostra a infraestrutura da tabela de classificação:

Diagrama mostrando o cluster do GKE dentro de uma VPC e uma instância separada do Memorystore para Redis.

No Google Cloud, o placar consiste em um cluster do GKE dentro de uma rede VPC e em uma instância separada do Memorystore para Redis.

O diagrama a seguir mostra a arquitetura do aplicativo para este tutorial. Os clientes usam a API de tabela de classificação para interagir com as pontuações mantidas em uma instância do Memorystore para Redis em execução no Google Cloud.

Diagrama mostrando a arquitetura do aplicativo neste tutorial

Métodos da API Leaderboard

A API para o aplicativo de tabela de classificação inclui os seguintes métodos:

  • PostScore(string playerName, double score). Este método publica uma pontuação na tabela de classificação para o jogador especificado.
  • RetrieveScores(string centerKey, int offset, int numScores). Esse método faz o download de um conjunto de pontuações. Se você passar um ID de jogador como o valor para centerKey, o método retornará as pontuações acima e abaixo das do jogador especificado. Se você não passar um valor para centerKey, o método retornará as N maiores pontuações absolutas, em que N é o valor que você passa em numScores. Por exemplo, para receber as 10 melhores pontuações, chame RetrieveScores('', 0, 10). Para receber cinco pontos acima e abaixo da pontuação de um jogador, chame RetrieveScores('player1', -5, 10).

O repositório de códigos do exemplo inclui um jogo simulado e uma implementação de tabela de classificação de prova de conceito. Durante este tutorial, você implementa o jogo e a tabela de classificação e valida se a API Leaderboard funciona corretamente e pode ser acessada pela Internet.

Objetivos

  • Crie uma instância do Memorystore para Redis.
  • Crie um serviço sem comando com um endpoint que direciona solicitações para esta instância.
  • Implantar o aplicativo de tabela de classificação no GKE.
  • Verificar a funcionalidade da tabela de classificação usando o aplicativo implantado que faz chamadas de API.

Custos

Neste tutorial, usamos os seguintes componentes faturáveis do Google Cloud:

Para gerar uma estimativa de custo baseada na projeção de uso deste tutorial, use a calculadora de preços. Novos usuários do Google Cloud podem ser qualificados para uma avaliação gratuita.

Antes de começar

  1. Faça login na sua conta do Google Cloud. Se você começou a usar o Google Cloud agora, crie uma conta para avaliar o desempenho de nossos produtos em situações reais. Clientes novos também recebem US$ 300 em créditos para executar, testar e implantar cargas de trabalho.
  2. No Console do Google Cloud, na página do seletor de projetos, selecione ou crie um projeto do Google Cloud.

    Acessar o seletor de projetos

  3. Verifique se o faturamento está ativado para seu projeto na nuvem. Saiba como confirmar se o faturamento está ativado para o projeto.

  4. Ative as APIs Memorystore for Redis and Google Kubernetes Engine.

    Ative as APIs

Como preparar o ambiente

Neste tutorial, você executa comandos no Cloud Shell. O Cloud Shell fornece acesso à linha de comando no Google Cloud e inclui o SDK do Cloud e outras ferramentas necessárias para o desenvolvimento do Google Cloud. O Cloud Shell pode levar vários minutos para ser inicializado.

  1. Abra o Cloud Shell:

    Abra o Cloud Shell

  2. Defina a zona padrão do Compute Engine como a zona em que você criará os recursos do Google Cloud. Neste tutorial, você usará a zona us-central1-a na região us-central1.

    export REGION=us-central1
    export ZONE=us-central1-a
    gcloud config set compute/zone $ZONE
    
  3. Clone o repositório GitHub que contém o código de amostra:

    git clone https://github.com/GoogleCloudPlatform/memstore-gaming-leaderboard.git
    
  4. Acesse o diretório clonado:

    cd memstore-gaming-leaderboard
    
  5. Usando um editor de texto, abra o arquivo leaderboardapp/k8s/appdeploy.yaml e altere o marcador [YOUR_PROJECT_ID] para o ID do projeto do Google Cloud.

Como criar uma instância do Memorystore para Redis

Para este tutorial, crie uma instância de nível Básico do Memorystore para Redis, que é apropriada para testes e o escopo deste tutorial. Para uma implantação de produção, recomendamos que você implante uma instância de nível Padrão, que fornece um SLA de 99,9% com failover automático para garantir que sua instância esteja altamente disponível. Para detalhes sobre instâncias de nível Padrão, consulte alta disponibilidade para o Memorystore para Redis.

  • No Cloud Shell, crie uma instância de 1 GB do Memorystore para Redis:

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

    Isso pode demorar alguns minutos.

Como criar as imagens de contêiner do aplicativo

Neste tutorial, você implantará um aplicativo simples de tabela de classificação usando o GKE. Como o Unity e o C# são famosos nos jogos, o nível do aplicativo usa o C# e o framework do ASP.NET.

Para implantar a API Leaderboard em um cluster do GKE, primeiro você precisa fazer upload dela para um registro como o Container Registry.

  1. Abra o arquivo README.md no projeto do GitHub que você clonou.
  2. Siga as instruções para criar uma imagem do Docker e fazer upload dela para o Container Registry.

Você usará essa imagem para implantar o aplicativo de tabela de classificação depois de criar o cluster na próxima seção.

Como criar ou reutilizar um cluster de GKE

Antes de implantar o aplicativo de tabela de classificação, você precisa criar um cluster do GKE com intervalos de IP do alias ativados. Se você já tiver um cluster do GKE (usando intervalos de IP do alias), será possível usá-lo para este tutorial. Se você usar um cluster existente, precisará executar uma etapa adicional para configurar a ferramenta de linha de comando kubectl com credenciais para esse cluster.

  1. Se você quiser criar um cluster, crie um chamado leaderboard que tenha dois nós:

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

    Esse comando pode levar alguns minutos para ser concluído.

  2. Após aguardar a inicialização do cluster, verifique se ele está em execução:

    gcloud container clusters list
    

    O cluster está em execução quando você vê uma entrada com o nome leaderboard com status RUNNING.

Configurar credenciais para um cluster atual

  1. Se você estiver usando um cluster do GKE existente para este tutorial, configure o arquivo kubeconfig com as credenciais desse cluster. Em name-of-existing-cluster, use o nome do cluster.

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

Como mapear a instância do Memorystore para Redis para um serviço do Kubernetes

Quando a instância do Memorystore para Redis estiver pronta, você criará um serviço dentro do cluster do GKE para que o nível do aplicativo se conecte.

  1. Verifique se a instância do Memorystore para Redis está em execução:

    gcloud redis instances list --region=$REGION
    

    A saída será exibida assim:

    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
    

    A instância está em execução quando a coluna STATUS mostra READY. Se o campo HOST estiver vazio, a instância não concluiu o processo de inicialização. Aguarde um momento e execute o comando redis instances list novamente.

  2. Armazene o endereço IP da instância em uma variável de ambiente:

    export REDIS_IP=$(gcloud redis instances list --filter="name:cm-redis" --format="value(HOST)" \
        --region=$REGION)
    
  3. Crie o serviço do Kubernetes para Redis:

    kubectl apply -f k8s/redis_headless_service.yaml
    

    Este serviço não tem um seletor de Pod, porque o ideal é que ele aponte para um endereço IP que esteja fora do cluster do Kubernetes.

  4. Crie um endpoint que defina o endereço IP Redis que você recuperou anteriormente:

    sed "s|REDIS_IP|${REDIS_IP}|g" k8s/redis_endpoint.yaml | kubectl apply -f -
    
  5. Verifique se o serviço e o endpoint foram criados corretamente:

    kubectl get services/redis endpoints/redis
    

    Se tudo estiver funcionando corretamente, você verá um resultado como o seguinte, com uma entrada para o serviço Redis e o endpoint 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
    

Para mais informações sobre os serviços e endpoints do Kubernetes, consulte Práticas recomendadas do Kubernetes: mapeamento de serviços externos no blog do Google Cloud.

Como implantar o serviço e o aplicativo da tabela de classificação no Kubernetes

A instância do Redis agora está acessível a partir de um aplicativo implantado no cluster do GKE. Portanto, você está pronto para implantar o aplicativo de tabela de classificação.

  • Siga as instruções no arquivo README.md incluído na raiz do repositório do GitHub que você clonou anteriormente.

Como validar a implantação

O aplicativo de jogo simulado fornecido, escrito em JavaScript, pode ser usado para fazer chamadas para a API Leaderboard. O seguinte snippet de código mostra como o jogo simulado registra as pontuações após o jogador terminar de jogar:

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

Além disso, o aplicativo faz uma chamada de API para recuperar as pontuações da tabela de classificação, sejam as pontuações mais altas ou as pontuações de um jogador, assim:

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

Para acessar o aplicativo de amostra, siga estas etapas:

  1. Consiga o endereço IP do jogo simulado executando o seguinte comando:

    kubectl get ingress
    

    A resposta será semelhante a:

    NAME                      HOSTS   ADDRESS        PORTS   AGE
    memstore-gaming-ingress   *       34.102.192.4   80      43s
    
  2. Navegue até o URL a seguir, em que ip_address_for_gke é o endereço do jogo simulado:

    http://ip_address_for_gke.
    

Esta amostra é simples, mas é adequada para demonstrar o uso básico da API. Quando você publica ou recupera pontuações diretamente de um aplicativo cliente de jogo em execução em um dispositivo ou máquina de usuário, a amostra chama a API Leaderboard no endereço IP público atribuído ao respectivo objeto de balanceador de carga do Kubernetes. Para esse aplicativo de amostra, a API de cabeçalho e o cliente JavaScript são hospedados no mesmo endereço IP que você recebe usando o comando kubectl get ingress mostrado anteriormente.

Como os métodos são implementados

O aplicativo de tabela de classificação, escrito em C#, usa a biblioteca StackExchange.Redis para se comunicar com o Redis. Os snippets de código a seguir mostram como PostScore e RetrieveScores são implementados usando o Redis e a biblioteca StackExchange.Redis.

O snippet de código a seguir mostra o método 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);
        }

O snippet de código a seguir mostra o método 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;
        }

Adições ao jogo de amostra

A API Leaderboard segue as convenções REST e é fornecida apenas como exemplo. Quando você executa uma tabela de classificação de jogos de produção, recomendamos a integração de um fluxo de autenticação para que apenas pontuações provenientes de usuários validados sejam publicadas.

Atualmente, o Memorystore para Redis não fornece persistência para pontuações de jogadores. Portanto, se o aplicativo tiver um problema, é possível perder as informações da tabela de classificação. Se seu jogo exigir uma tabela de classificação permanente, você precisa fazer backup das pontuações da tabela de classificação periodicamente em um banco de dados permanente.

Como fazer a limpeza

Para evitar cobranças na sua conta do Google Cloud pelos recursos usados neste tutorial, exclua o projeto.

Excluir o projeto

  1. No Console do Cloud, acesse a página Gerenciar recursos:

    Acessar "Gerenciar recursos"

  2. Na lista de projetos, selecione o projeto que você quer excluir e clique em Excluir .
  3. Na caixa de diálogo, digite o ID do projeto e clique em Encerrar para excluí-lo.

A seguir