Google App Engine中的全球排行榜

时间:2014-02-24 04:43:32

标签: python google-app-engine cron leaderboard

我想为移动游戏构建一个后端,其中包括使用Google App Engine(Python)为所有玩家提供“实时”全球排行榜,以及持续一定天数的活动。

典型用法如下: - 用户启动并完成战斗,获得积分(战斗2-5分钟) - 在活动期间,积分将累积在玩家的账户中。 - 玩家可以随时查看排行榜。 - 排行榜将返回前10名玩家,以及5名玩家在玩家得分之上和之下。

现在,实时方面没有真正的限制,电路板可以每30秒更新一次。我希望它尽可能“快速”,而不会花费太多。

由于我对GAE不太熟悉,这是我想到的解决方案:

  • 每个玩家实体都有一个event_points属性
  • 定期使用Cron作业,对分数不为零的所有玩家进行数据存储查询。查询是 排序。
  • 然后,cron作业遍历查询结果,在每个Player实体中写回等级。

当我想到这个解决方案时,感觉非常“蛮力”。

此解决方案的问题在于所有实体的读写成本。 如果我们最终得到50K活跃用户,这将意味着排序查询50K + 1次读取,并且定期写入50k + 1次,这可能非常昂贵(取决于间隔)

我知道memcache可以防止一些读取和一些写入,但如果某些实体不在memcache中,那么查询它是否有意义? 另外,我已经读过memcache可以随时刷新,所以除非有办法以便宜的方式“备份”,否则这似乎是一个危险的用途,因为数据相对重要。

有没有更简单的方法来解决这个问题?

3 个答案:

答案 0 :(得分:2)

您不需要50,000次读取或50,000次写入。解决方案是在您的points属性上设置排序顺序。每次更新时,数据存储区都会自动更新其订单,这意味着除了points属性之外,您不需要rank属性。而且你不需要相应的cron工作。

然后,当您需要检索排行榜时,您会运行两个查询:一个用于6个与您的用户具有更多或相同点数的实体;第二 - 对于具有较少或相同点数的6个实体。合并结果,这是您要向用户显示的内容。

对于您的前10个查询,您可能希望将其结果放在Memcache中,其过期时间为5分钟。当您需要它时,首先检查Memcache。如果未找到,请运行查询并更新Memcache。

编辑:

澄清查询部分。您需要设置排序顺序和不等式过滤器的正确组合,以获得所需的结果。根据App Engine文档,查询按以下顺序执行:

  
      
  1. 标识与查询的种类,过滤器对应的索引   属性,过滤运算符和排序顺序。
  2.   
  3. 从中扫描   索引的开头到满足所有的第一个实体   查询的过滤条件。
  4.   
  5. 继续扫描索引,返回   每个实体依次,直到遇到不符合的实体   过滤条件,或到达索引的末尾,或具有   收集了查询请求的最大结果数。
  6.   

因此,您需要将ASCENDING命令与GREATER_THAN_OR_EQUAL过滤器组合用于一个查询,并将DESCENDING命令与LESS_THAN_OR_EQUAL过滤器组合用于另一个查询。在这两种情况下,您都要在6处检索结果的限制。

还有一个注意事项:您为6个实体设置了限制,因为两个查询都将返回用户自己。你可以添加另一个过滤器(userId NOT_EQUAL到你的用户的id),但我不推荐它 - 成本不值得节省。显然,您不能对点使用GREATER_THAN / LESS_THAN过滤器,因为许多用户可能具有相同的点数。

答案 1 :(得分:1)

以下是Google开发人员article使用Google代码JAM ranking library解释类似问题和解决方案。可以在Google网上论坛forum中讨论对此库的进一步帮助和扩展。

该库基本上创建了一个N-ary树,每个节点包含特定范围内的分数计数。得分范围进一步向下划分直至叶节点,其中单个得分。树遍历(O log(n))可用于查找得分高于特定分数的玩家的数量。这是玩家的等级。它还建议在pull taskqueue中聚合得分提交请求,然后在后端的后台线程中批量处理它们。

答案 2 :(得分:0)

这是否更简单是值得商榷的。

我认为排名不仅仅是排序点积累的问题,在这种情况下,这只是一个简单的查询。我排名涉及其他因素而不仅仅是当前得分。

我会考虑为用户(实际上是队列)的每次点数更新写出事件记录。任务运行收集所有当前事件记录,此外,您还维护一组表示排行榜顶部的记录。根据传入的事件记录调整此记录集。处理后丢弃事件记录。这将限制您在小时间窗口中只读取和写入活动事件。排行榜可能是单个实体,并由密钥和缓存提取。

我假设您可能有不同的排名方案,如当前活跃排名(当前7天),与所有时间排名。 (即不玩一段时间的玩家将不会有良好的当前排名)。

当玩家查看他们的排名时,你可以通过两个简单的查询Players.query(Players.score > somescore).fetch(5)Players.query(Players.score < somescore).fetch(5)来做到这一点,这不应该花费太多而且你可以缓存它们。