本教程将向您展示如何利用 Memorystore for Redis,构建起运行在 Google Kubernetes Engine(GKE)之上的 ASP.NET 排行榜应用程序。随后,我们还将通过一款 JavaScript 示例游戏演示如何发布并检索分数。本教程适用于希望在云端运行游戏排行榜的开发人员群体。


内容简介


排行榜属于多人游戏中的一项常见功能。排行榜能够实时显示玩家排名,鼓励玩家们积极参与游戏。对于这样一项捕捉玩家行为并实时进行得分排名的功能,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)
复制代码


此方法负责下载一组得分。如果您将玩家 ID 作为


centerKey
复制代码


的值进行传递,则此方法将返回当前玩家排名之前及之后几个位置的对应得分。如果您没有传递


centerKey
复制代码


的值,则此方法仅返回前 N 名玩家的得分,其中 N 值为


numScores
复制代码


的取值。例如,如果希望获取前10名玩家的得分,则调用


RetrieveScores('', 0, 10)
复制代码


如果需要获取玩家当前排名之前及之后五名的对应得分,则调用


RetrieveScores('player1', -5, 10)
复制代码


本示例中的代码库包含一款模拟游戏,外加一套排行榜概念验证实现。在本教程中,我们可以部署游戏与排行榜,并验证排行榜API能否正常工作并通过互联网进行访问。


目标


  • 创建一个 Memorystore for Redis 实例。

  • 创建一项 headless 服务,其中包含一个负责将请求定向至该实例的端点。

  • 将排行榜应用部署至 GKE。

  • 使用部署完成的应用发出API调用,借此验证排行榜功能是否正常。


成本


本教程使用以下 Google Cloud 计费组件:


  • Google Kubernetes Engine

  • Memorystore for Redis


您可以使用我们提供的费用计算器根据预期使用量进行成本估算。我们还为 Google Cloud 新用户提供免费试用选项,请点击此处了解。


准备工作


1.在 GCP Console 的项目选择器页面上,选择或创建 GCP 项目。


注意:如果您不打算保留在此过程中创建的资源,请创建新的 GCP 项目,而不要选择现有的 GCP 项目。完成本教程介绍的步骤后,您可以删除所创建的项目,并移除与该项目关联的所有资源。


转到项目选择器页面


2.确保您的 Google Cloud Platform 项目已启用结算功能。 了解如何确认您的项目已启用结算功能。


3.启用 Memorystore for Redis 与 Google Kubernetes Engine API。


启用 API


环境筹备


在本教程中,所有命令都将运行在 Cloud Shell 当中。通过 Cloud Shell,您可以访问 Google Cloud 中的命令行,包括 Google Cloud 开发工作中常用的 Cloud SDK 与其他工具。Cloud Shell 的初始化操作可能需要几分钟时间。


1.打开 Cloud Shell:


打开 Cloud Shell


2.将 Compute Engine 的默认可用区设置为您希望创建 Google Cloud 资源的可用区。在本教程中,我们选择


us-central1
复制代码


区域中的


us-central1-a
复制代码


可用区。


3.export REGION=us-central14.export ZONE=us-central1-a5.gcloud config set compute/zone $ZONE
复制代码


6.克隆包含示例代码的GitHub库:


7.git clone https://github.com/GoogleCloudPlatform/memstore-gaming-leaderboard.git
复制代码


8.转到克隆完毕的目录:


9.cd memstore-gaming-leaderboard
复制代码


10.使用文本编辑器,打开


leaderboardapp/k8s/appdeploy.yaml
复制代码


文件并将


[YOUR_PROJECT_ID]
复制代码


占位符替换为您的 Google Cloud 项目 ID。


创建一个 Memorystore for Redis 实例


在本教程中,我们将创建一个 Memorystore for Redis 的 Basic Tier 实例,此实例已经能够满足我们的演示与测试需求。对于实际生产部署,我们建议您选择部署Standard Tier 实例,其提供99.9%服务水平协议(SLA)与自动故障转移机制,可确保实例具备更高可用性。关于 Standard Tier 实例的细节信息,请参阅 Memorystore for Redis 的高可用性说明。


-在 Cloud Shell 中,创建一个1 GB Memorystore for Redis 实例:


gcloud redis instances create cm-redis --size=1 --tier=standard --region=$REGION --zone=$ZONE
复制代码


此命令可能需要运行几分钟时间。


构建应用容器镜像


在本教程中,我们将使用 GKE 部署一款简单的排行榜应用。考虑到 Unity 与 C#在游戏开发领域的高流行度,我们会在应用层中使用 C#与 ASP.NET 框架。


要将排行榜 API 部署至 GKE 集群内,我们首先需要将其上传至 Container Registry 等注册表当中。


1.打开克隆完毕的 GitHub 项目中的


README.md
复制代码


文件。


2.按照说明创建 Docker 镜像并将其上传至 Container Registry。


在完成下一节中介绍的集群创建工作后,我们即可利用这套镜像部署自己的排行榜。


创建或重用一套 GKE 集群


在部署排行榜应用之前,我们首先需要创建一套启用 alias IP 范围的 GKE 集群。如果您已经拥有一套 GKE 集群(而且启用了alias IP范围),那么直接在本示例中使用即可。要使用现有集群,您必须通过额外操作步骤利用集群凭证配置


kubectl
复制代码


命令行工具。


1.如果您希望创建一套新集群,推荐使用


leaderboard
复制代码


名称并在其中包含两个节点:


2.gcloud container clusters create leaderboard --num-nodes=2 --enable-ip-alias
复制代码


此命令可能需要运行几分钟时间。


3.在集群启动完毕之后,验证其是否正常运行:


4.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 实例映射至 Kuernetes 服务


当 Memorystore for Redis 实例准备就绪后,我们即可在 GKE 集群中选定该应用层要接入的服务。


1.验证该 Memorystore for Redis 实例是否正在运行:


2.gcloud redis instances list --region=$REGION
复制代码


您将看到如下输出结果:


INSTANCE_NAME  VERSION    REGION           TIER       SIZE_GB    HOST       PORT  NETWORK  RESERVED_IP  STATUS    CREATE_TIMEcm-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
复制代码


命令。


3.将该实例的 IP 地址保存为一项环境变量:


4.export REDIS_IP=$(gcloud redis instances list --filter="name:cm-redis" --format="value(HOST)" 5.    --region=$REGION)
复制代码


6.为 Redis 创建 Kubernetes 服务:


7.kubectl apply -f k8s/redis_headless_service.yaml
复制代码


此服务并未包含 Pod 选择器,因为我们希望将其指向 Kubernetes 集群之外的 IP 地址。


8.创建一个端点,用于定义先前检索的 Redis IP 地址:


9.sed "s|REDIS_IP|${REDIS_IP}|g" k8s/redis_endpoint.yaml | kubectl apply -f -
复制代码


10.验证该服务与端点是否正确创建完成:


11.kubectl get services/redis endpoints/redis
复制代码


如果一切运作正常,您将看到如下输出,包括 Redis 服务与 Redis 端点的入口:


NAME               TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGEservice/redis      ClusterIP   10.59.241.103   none        6379/TCP   5mNAME               ENDPOINTS       AGEendpoints/redis    10.0.0.3:6379   4m
复制代码


若需了解更多与 Kubernetes 服务与端点相关的信息,请参阅 Google Cloud 博客上发布的《Kubernetes 最佳实践:映射至外部服务》。


在 Kubernetes 上部署排行榜应用与服务


现在,我们已经可以通过部署在 GKE 集群上的应用访问 Redis 实例。做好这项准备,接下来就是着手部署排行榜应用。


  • 请遵循克隆完成的 GitHub repo 根目录中 README.md 文件的具体说明。


验证部署成果


这里我们使用由 JavaScript 编写的模拟游戏调用排行榜 API。以下代码段,展示了模拟游戏在玩家完成游戏之后发布得分的具体方式:


leaderboardapp/Views/Home/Index.cshtmlView on GitHubvar 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 调用以检索排行榜上的原有得分,包括前N位最高得分以及当前玩家之前与之后几名的对应得分,具体如下:


leaderboardapp/Views/Home/Index.cshtmlView on GitHubvar 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 地址:


2.kubectl get ingress
复制代码


输出示例如下:


NAME                      HOSTS   ADDRESS        PORTS   AGEmemstore-gaming-ingress   *       34.102.192.4   80      43s
复制代码


3.浏览以下 URL,其中 ip_address_for_gke 部分为模拟游戏的地址:


4.http://ip_address_for_gke.
复制代码


这里的示例虽然简单,但足以体现 API 的基本使用情况。当大家直接通过用户设备或者计算机上运行的客户端应用发布或检索分数时,本示例将在对应分配的Kubernetes Load Balancer 对象公网 IP 地址之上、对排行榜 API 执行调用。在此示例应用当中,排行榜 API 与 JavaScript 客户端都托管在前文所示、使用


kubectl get ingress
复制代码


命令获取的同一 IP 地址上。


各方法的具体实现方式


我们这款使用 C#编写的排行榜应用,利用 StackExchange.Redis 库实现与 Redis 之间的通信。以下代码片段展示了如何利用 Redis 与


StackExchange.Redis
复制代码


库实现


PostScore
复制代码



RetrieveScores
复制代码


方法。


以下代码片段用于显示


PostScore
复制代码


方法:


leaderboardapp/RedisLeaderboardRepo.csView on GitHubpublic async Task<bool> PostScoreAsync(ScoreModel score){IDatabase db = _redis.GetDatabase();// SortedSetAddAsync对应ZADDreturn await db.SortedSetAddAsync(LEADERBOARD_KEY, score.PlayerName, score.Score);}
复制代码


以下代码片段用于显示


RetrieveScores
复制代码


方法:


leaderboardapp/RedisLeaderboardRepo.csView on GitHubpublic 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 (!string.IsNullOrWhiteSpace(retrievalDetails.CenterKey)){// SortedSetRankAsync 对应ZREVRANKvar rank = await db.SortedSetRankAsync(LEADERBOARD_KEY, retrievalDetails.CenterKey, Order.Descending);// 如果不存在特定用户,则返回空排行榜 if (!rank.HasValue){return leaderboard;}// 利用排名以计算偏移 offset = Math.Max(0, rank.Value + retrievalDetails.Offset);// 在尝试居中显示时,通过// 排名[0, abs(offset))中的元素计入分数 if(offset <= 0){numScores = rank.Value + Math.Abs((long)retrievalDetails.Offset) + 1;}}// SortedSetRangeByScoreWithScoresAsync对应 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 账户中删除此项目。


删除此项目


警告: 删除项目可能造成以下结果:


  • 项目中的所有内容均被删除。如果您在本教程中使用原有项目,则在删除此项目的同时,项目中其他已经完成的工作成果也将被一并删除。

  • 自定义项目 ID 丢失。创建此项目时,您可能创建了希望未来继续使用的自定义项目 ID。为了避免 URL(例如 appspot.com URL)使用该项目 ID,请仅删除项目内部的特定资源,而非删除整个项目。


如果您后续还希望体验其他教程与入门指南内容,我们建议大家重复使用当前项目以控制云资源使用带来的成本。


2.在 Cloud Console 中,前往资源管理页面。


前往资源管理页面


3.在项目列表中,选定您希望删除的项目,并点击Delete 。


4.在对话框内输入项目 ID,而后点击Shut down以删除该项目。


后续步骤


  • 了解 Memorystore for Redis 各关键概念。

  • 了解如何如何通过 OpenCensus 对 Memorystore for Redis 工作负载进行客户端跟踪。

  • 关于排行榜概念的更多细节信息,请参阅 Google Play Games Services 说明文档。



相关推荐