튜토리얼: Memorystore for Redis를 게임 리더보드로 사용


이 튜토리얼에서는 Memorystore for Redis를 사용하여 Google Kubernetes Engine(GKE)에서 실행되는 ASP.NET 기반 리더보드 애플리케이션을 빌드한 후 별도의 자바스크립트 기반 샘플 게임을 사용하여 점수를 게시하고 검색하는 방법을 설명합니다. 이 문서는 클라우드에서 자체 리더보드를 운영하려는 게임 개발자를 대상으로 합니다.

소개

리더보드는 멀티플레이어 게임에서 일반적인 기능입니다. 리더보드는 플레이어 순위를 실시간으로 보여주므로 플레이어가 게임에 더 몰입할 수 있게 해줍니다. 플레이어의 동작을 포착하여 실시간으로 점수를 매기려면 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. API Memorystore for Redis and Google Kubernetes Engine 사용 설정

    API 사용 설정

  5. Google Cloud Console의 프로젝트 선택기 페이지에서 Google Cloud 프로젝트를 선택하거나 만듭니다.

    프로젝트 선택기로 이동

  6. Google Cloud 프로젝트에 결제가 사용 설정되어 있는지 확인합니다.

  7. API Memorystore for Redis and Google Kubernetes Engine 사용 설정

    API 사용 설정

환경 준비

이 가이드에서는 Cloud Shell에서 명령어를 실행합니다. Cloud Shell은 Google Cloud의 명령줄에 대한 액세스 권한을 제공하고 Google Cloud CLI 및 Google Cloud 개발에 필요한 기타 도구를 포함합니다. 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에서 1GB 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 클러스터를 만들어야 합니다. GKE 클러스터(별칭 IP 범위 사용)가 이미 있다면 클러스터를 이 가이드에서 사용할 수 있습니다. 기존 클러스터를 사용할 때는 클러스터의 사용자 인증 정보로 kubectl 명령줄 도구를 구성하는 추가 단계를 수행해야 합니다.

  1. 클러스터를 만들려면 노드가 두 개 있는 leaderboard라는 클러스터를 만듭니다.

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

    이 명령어를 완료하는 데 몇 분이 걸릴 수 있습니다.

  2. 클러스터가 시작될 때까지 기다린 후 실행 중인지 확인합니다.

    gcloud container clusters list
    

    상태가 RUNNINGleaderboard라는 항목이 표시되면 클러스터가 실행 중인 것입니다.

기존 클러스터의 사용자 인증 정보 구성

  1. 이 튜토리얼에 기존 GKE 클러스터를 사용하려면 클러스터의 사용자 인증 정보로 kubeconfig 파일을 구성합니다. name-of-existing-cluster에는 클러스터 이름을 사용합니다.

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

Memorystore for Redis 인스턴스를 Kubernetes 서비스에 매핑

Memorystore for Redis 인스턴스가 준비되었으면 애플리케이션 계층에서 연결할 수 있도록 GKE 클러스터 내에 서비스를 만듭니다.

  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 서비스를 만듭니다.

    kubectl apply -f k8s/redis_headless_service.yaml
    

    이 서비스에는 Pod 선택기가 없습니다. 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 서비스 및 엔드포인트에 대한 자세한 내용은 Google Cloud 블로그의 Kubernetes 권장사항: 외부 서비스 매핑을 참조하세요.

Kubernetes에 리더보드 앱 및 서비스 배포

이제 GKE 클러스터에 배포된 애플리케이션에서 Redis 인스턴스에 연결할 수 있습니다. 따라서 리더보드 앱을 배포할 수 있습니다.

  • 앞에서 클론한 GitHub 저장소의 루트에 있는 README.md 파일의 안내를 따릅니다.

배포 검증

자바스크립트로 작성된 모의 게임 애플리케이션을 사용하여 리더보드 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와 자바스크립트 클라이언트 모두 이전에 설명한 kubectl get ingress 명령어를 사용하여 가져온 IP 주소와 동일한 IP 주소로 호스팅됩니다.

메서드 구현 방법

C#으로 작성된 리더보드 애플리케이션은 StackExchange.Redis 라이브러리를 사용하여 Redis와 통신합니다. 다음 코드 스니펫에서는 PostScoreRetrieveScores를 Redis와 StackExchange.Redis 라이브러리를 사용하여 구현하는 방법을 보여줍니다.

다음 코드 스니펫은 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를 입력한 후 종료를 클릭하여 프로젝트를 삭제합니다.

다음 단계