Instructivo: Uso Memorystore para Redis como una tabla de clasificación de juegos

En este instructivo, se muestra cómo usar Memorystore para Redis a fin de compilar una aplicación de tabla de clasificación basada en ASP.NET que se ejecuta en Google Kubernetes Engine y, también, cómo publicar y recuperar puntuaciones mediante un juego de muestra basado en JavaScript independiente. Este documento está dirigido a desarrolladores de juegos que deseen operar sus propias tablas de clasificación en la nube.

Introducción

Las tablas de clasificación son una función común de los juegos multijugador. Una tabla de clasificación muestra las clasificaciones de los jugadores en tiempo real, lo que ayuda a alentar a los jugadores a participar más en el juego. Para capturar las acciones del jugador y clasificar la puntuación en tiempo real, una base de datos en la memoria como Redis es una excelente opción.

En el siguiente diagrama, se muestra la infraestructura de la tabla de clasificación:

Diagrama que muestra un clúster de GKE dentro de una VPC y una instancia de Memorystore para Redis independiente.

En Google Cloud, la tabla de clasificación consta de un clúster de GKE dentro de una red de VPC y de una instancia de Memorystore para Redis independiente.

En el siguiente diagrama, se muestra la arquitectura de la aplicación para este instructivo. Los clientes usan la API de tabla de clasificación para interactuar con las puntuaciones que se mantienen en una instancia de Memorystore para Redis que se ejecuta en Google Cloud.

Diagrama en el que se muestra la arquitectura de la aplicación en este instructivo

Métodos de la API de la tabla de clasificación

La API de la aplicación de la tabla de clasificación incluye los siguientes métodos:

  • PostScore(string playerName, double score): Este método publica una puntuación en la tabla de clasificación para el jugador especificado.
  • RetrieveScores(string centerKey, int offset, int numScores): Este método se descarga un conjunto de puntuaciones. Si pasas un ID de jugador como el valor de centerKey, el método muestra las puntuaciones que están por encima y por debajo de las del jugador especificado. Si no pasas un valor para centerKey, el método muestra las puntuaciones N principales, en la que N es el valor que pasas en numScores. Por ejemplo, para obtener las 10 puntuaciones principales, llama a RetrieveScores('', 0, 10). Para obtener 5 puntuaciones por encima y por debajo de la puntuación de un jugador, llama a RetrieveScores('player1', -5, 10).

El repositorio de código para el ejemplo incluye un juego ficticio y una implementación de tabla de clasificación de prueba de concepto. Durante este instructivo, implementarás el juego y la tabla de clasificación, y validarás que la API de la tabla de clasificación funcione de forma correcta y se pueda acceder a ella a través de Internet.

Objetivos

  • Crear una instancia de Memorystore para Redis
  • Crear un servicio sin interfaz gráfica con un extremo que dirija solicitudes a esta instancia
  • Implementar la aplicación de la tabla de clasificación en GKE
  • Verificar la funcionalidad de la tabla de clasificación mediante la aplicación implementada que realiza llamadas a la API

Costos

En este instructivo, se usan los siguientes componentes facturables de Google Cloud:

Para generar una estimación de costos en función del uso previsto, usa la calculadora de precios. Es posible que los usuarios nuevos de Google Cloud sean aptos para obtener una prueba gratuita.

Antes de comenzar

  1. Accede a tu cuenta de Google Cloud. Si eres nuevo en Google Cloud, crea una cuenta para evaluar el rendimiento de nuestros productos en situaciones reales. Los clientes nuevos también obtienen $300 en créditos gratuitos para ejecutar, probar y, además, implementar cargas de trabajo.
  2. En la página del selector de proyectos de Google Cloud Console, selecciona o crea un proyecto de Google Cloud.

    Ir al selector de proyecto

  3. Asegúrate de que la facturación esté habilitada para tu proyecto de Cloud. Descubre cómo confirmar que tienes habilitada la facturación en un proyecto.

  4. Habilita las API de Memorystore for Redis and Google Kubernetes Engine.

    Habilita las API

Prepara tu entorno

En este instructivo, ejecutarás comandos en Cloud Shell. Cloud Shell te brinda acceso a la línea de comandos en Google Cloud y, además, incluye el SDK de Cloud y otras herramientas que necesitas para el desarrollo de Google Cloud. La inicialización de Cloud Shell puede tomar varios minutos.

  1. Abre Cloud Shell:

    Abra Cloud Shell

  2. Establece la zona predeterminada de Compute Engine en la zona en la que crearás tus recursos de Google Cloud. En este instructivo, usarás la zona us-central1-a en la región us-central1.

    export REGION=us-central1
    export ZONE=us-central1-a
    gcloud config set compute/zone $ZONE
    
  3. Clona el repositorio de GitHub que contiene el código de muestra:

    git clone https://github.com/GoogleCloudPlatform/memstore-gaming-leaderboard.git
    
  4. Ve al directorio clonado:

    cd memstore-gaming-leaderboard
    
  5. Mediante un editor de texto, abre el archivo leaderboardapp/k8s/appdeploy.yaml y cambia el marcador de posición [YOUR_PROJECT_ID] por el ID de tu proyecto de Google Cloud.

Crea una instancia de Memorystore para Redis

Para este instructivo, crearás una instancia de nivel básico de Memorystore para Redis, que es adecuada para las pruebas y el alcance de este instructivo. Para una implementación de producción, recomendamos que implementes una instancia de nivel Estándar, que proporciona un ANS del 99.9% con conmutación por error automática para garantizar que tu instancia tenga alta disponibilidad. Para obtener detalles sobre las instancias de nivel Estándar, consulta alta disponibilidad de Memorystore para Redis.

  • En Cloud Shell, crea una instancia de 1 GB de Memorystore para Redis:

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

    Este comando puede tardar unos minutos en completarse.

Compila las imágenes de contenedor de la aplicación

Para este instructivo, implementarás una aplicación de tabla de clasificación simple mediante GKE. Debido a que Unity y C# son populares en los videojuegos, el nivel de la aplicación usa C# y el framework de ASP.NET.

Para implementar la API de la tabla de clasificación en un clúster de GKE, primero debes subirla a un registro como Container Registry.

  1. Abre el archivo README.md en el proyecto de GitHub que clonaste.
  2. Sigue las instrucciones para crear una imagen de Docker y subirla a Container Registry.

Usarás esta imagen para implementar la aplicación de tabla de clasificación después de crear el clúster en la siguiente sección.

Crea o reutiliza un clúster de GKE

Antes de implementar la aplicación de tabla de clasificación, debes crear un clúster de GKE con rangos de IP de alias habilitados. Si ya tienes un clúster de GKE (con rangos de IP de alias), puedes usar ese clúster para este instructivo. Si usas un clúster existente, debes realizar un paso adicional para configurar la herramienta de línea de comandos de kubectl con credenciales para ese clúster.

  1. Si deseas crear un clúster, crea uno llamado leaderboard que tenga dos nodos:

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

    Este comando puede tardar unos minutos en completarse.

  2. Después de esperar a que se inicie el clúster, verifica que se esté ejecutando:

    gcloud container clusters list
    

    El clúster está en ejecución cuando se puede ver una entrada que tiene el nombre leaderboard, cuyo estado es RUNNING.

Configura credenciales para un clúster existente

  1. Si usas un clúster de GKE existente para este instructivo, configura el archivo kubeconfig con las credenciales de ese clúster. En name-of-existing-cluster, usa el nombre de tu clúster.

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

Asigna la instancia de Memorystore para Redis a un servicio de Kubernetes

Cuando la instancia de Memorystore para Redis está lista, debes crear un servicio dentro del clúster de GKE para que el nivel de la aplicación pueda conectarse.

  1. Verifica que la instancia de Memorystore para Redis se esté ejecutando:

    gcloud redis instances list --region=$REGION
    

    Verás un resultado como el siguiente:

    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
    

    La instancia está en ejecución cuando la columna STATUS muestra READY. Si el campo HOST está vacío, la instancia no completó el proceso de inicio. Espera un momento y vuelve a ejecutar el comando redis instances list.

  2. Almacena la dirección IP de la instancia en una variable de entorno:

    export REDIS_IP=$(gcloud redis instances list --filter="name:cm-redis" --format="value(HOST)" \
        --region=$REGION)
    
  3. Crea el servicio de Kubernetes para Redis:

    kubectl apply -f k8s/redis_headless_service.yaml
    

    Este servicio no tiene un selector de pods, ya que tienes que apuntar a una dirección IP que esté fuera del clúster de Kubernetes.

  4. Crea un extremo que defina la dirección IP de Redis que recuperaste antes:

    sed "s|REDIS_IP|${REDIS_IP}|g" k8s/redis_endpoint.yaml | kubectl apply -f -
    
  5. Verifica que el servicio y el extremo se hayan creado de forma correcta:

    kubectl get services/redis endpoints/redis
    

    Si todo funciona bien, verás un resultado como el siguiente, con una entrada para el servicio de Redis y el extremo de 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 obtener más información sobre los servicios y extremos de Kubernetes, consulta Prácticas recomendadas de Kubernetes: Asignación de servicios externos en el blog de Google Cloud.

Implementa la app y el servicio de la tabla de clasificación en Kubernetes

Ahora se puede acceder a la instancia de Redis desde una aplicación implementada en el clúster de GKE. Por lo tanto, estás listo para implementar la app de la tabla de clasificación.

  • Sigue las instrucciones del archivo README.md incluido en la raíz del repositorio de GitHub que clonaste antes.

Valida la implementación

La aplicación de juego ficticio proporcionada, escrita en JavaScript, se puede usar para realizar llamadas a la API de la tabla de clasificación. En el siguiente fragmento de código, se muestra cómo este juego simula las puntuaciones una vez que el jugador termina de jugar:

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

Además, la app realiza una llamada a la API para recuperar las puntuaciones de la tabla de clasificación, ya sea las puntuaciones principales o centradas en el nombre del jugador, como se muestra a continuación:

                    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 acceder a la aplicación de muestra, sigue estos pasos:

  1. Obtén la dirección IP del juego ficticio mediante la ejecución del siguiente comando:

    kubectl get ingress
    

    La salida es similar a esta:

    NAME                      HOSTS   ADDRESS        PORTS   AGE
    memstore-gaming-ingress   *       34.102.192.4   80      43s
    
  2. Navega a la siguiente URL, en la que ip_address_for_gke es la dirección del juego ficticio:

    http://ip_address_for_gke.
    

Este ejemplo es simple, pero es adecuado para demostrar el uso básico de la API. Cuando publicas o recuperas puntuaciones directamente desde una app cliente de juego que se ejecuta en un dispositivo o máquina de usuario, la muestra llama a la API de la tabla de clasificación en la dirección IP pública asignada a su objeto del balanceador de cargas de Kubernetes. Para esta aplicación de muestra, la API de la tabla de clasificación y el cliente de JavaScript están alojados en la misma dirección IP que obtienes cuando ejecutas el comando kubectl get ingress que se mostró antes.

Cómo se implementan los métodos

La aplicación de la tabla de clasificación, escrita en C#, usa la biblioteca StackExExchange.Redis para comunicarse con Redis. Los siguientes fragmentos de código muestran cómo se implementan PostScore y RetrieveScores mediante Redis y la biblioteca StackExchange.Redis.

En el siguiente fragmento de código, se muestra el 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);
        }

En el siguiente fragmento de código, se muestra el 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;
        }

Adiciones al juego de muestra

La API de la tabla de clasificación sigue las convenciones de REST y se proporciona solo como ejemplo. Cuando ejecutas una tabla de clasificación de producción, te recomendamos integrar un flujo de autenticación para que solo se puedan publicar las puntuaciones de los usuarios validados.

Por el momento, Memorystore para Redis no proporciona persistencia para las puntuaciones de los jugadores. Por lo tanto, si la app experimenta un problema, podrías perder la información de la tabla de clasificación. Si tu juego requiere una tabla de clasificación persistente, debes crear una copia de seguridad de las puntuaciones de la tabla de clasificación a menudo en una base de datos persistente.

Realiza una limpieza

Para evitar que se apliquen cargos a tu cuenta de Google Cloud por los recursos que usaste en este instructivo, borra el proyecto.

Borra el proyecto

  1. En Cloud Console, ve a la página Administrar recursos.

    Ir a Administrar recursos

  2. En la lista de proyectos, elige el proyecto que quieres borrar y haz clic en Borrar.
  3. En el diálogo, escribe el ID del proyecto y, luego, haz clic en Cerrar para borrar el proyecto.

¿Qué sigue?