Azure 上的游戏记分牌

Game scoreboard on Azure

你能给我推荐一个在 Azure 云上存储游戏分数的替代解决方案吗?我正在寻找 托管便宜到 运行易于上下扩展存储选项。

输入数据:{ "player": 1, "score": 10 }(分数可能已经算出来了,不用再求和了)

应该有一个选项可以进行 2 种类型的查询:
1. 获取玩家在记分牌中的位置。
2.获取位置X和Y之间的位置(球员和得分)。

应用程序无法在写入期间告知玩家在记分牌中的位置 - 后端数据库解决方案应提供该信息,或者数据布局应允许快速计算所有玩家之间的位置。 非实时精度是可以接受的。

当前后端技术选择:Azure Web App (.NET Core)、存储(Table、Blob、队列)。
使用 SQL 服务器很明显如何做到这一点,但是 我想避免 将 SQL 服务器引入技术堆栈。保存在内存中也不是一个选项,因为有多个 Web 服务器。

您是否知道如何使用任何其他 Azure 服务存储和检索此类数据? DocumentDB、Data Lake 还是其他?

如果你使用 tables 那么你的第一个查询很简单,你可以使用玩家 ID(玩家的唯一标识符,无论你选择它是什么)作为你的分区键和分数可以是 属性 属于同一实体。当您想读回(您的查询 1)时,您只需使用唯一玩家 ID 进行点查询并获得分数。

您的第二个查询有点棘手。您可能希望避免下载整个 table 并在客户端进行内存排序,这是客户端和 table 服务世界组合中最糟糕的。

因此需要在此处选择不同的分区键来优化第二个查询。您可以做的一件事是创建一个范围桶并为该范围获取唯一的哈希值。 IE。如果你正在编写一个位置为 19 的玩家并且你选择你的桶大小为 50,那么这将进入你的第一个位置在 1 - 50 之间的玩家桶。在将位置为 19 的玩家写入 table 之前,你的门面(或适配器,无论你怎么称呼它)客户端中的代码层将检测到它进入第一个存储桶,为该存储桶生成一个哈希并将播放器写入 table,其中为存储桶 1-50 生成的哈希为分区键和实际玩家唯一 ID 作为其行键和其余属性。

您为存储桶生成的散列可以只是存储桶的下限和上限的散列等。确保使用加密散列,但不是 Object.GethashCode,因为这可能 return 不同的散列完全相同对象的值,不应用于他的目的。

当您想要获得范围 X 和 Y 之间的所有玩家时,您需要查询您知道的那些覆盖该范围而不是整个 table 的分区。而且您知道要使用确切的分区键(表示存储桶的哈希值)查询分区,这样效率会更高。

插入播放器时,您需要为第一个查询创建 2 个实体 1,为第二个查询创建第二个实体,然后将两者都插入。此过程也称为对数据进行反规范化,以针对不同的查询需求进行优化。希望这有助于了解如何使用 Azure tables 对其进行优化。

Azure Table 存储有利于存储纯数据,而且价格低廉且易于扩展。但这对复杂查询没有好处。如果满足您的需要,您可以选择 Table 存储来节省一些钱。价格范围为每 gigabyte/month.

4.5 到 12 美分

Azure DocumentDB 是一个分布式文档数据库。它比 Azure Table 存储更强大,但也更昂贵。价格:每 gigabyte/month.

25 美分

Azure Data Lake Store 是针对大数据分析工作负载的优化存储,我们经常用它来批处理、交互式、流式分析和机器学习数据,例如日志文件、物联网数据、点击流、大型数据集

AzureTable存储如果能满足您的需求,还是选择它比较好。根据您的描述,我建议您使用 Azure Table 存储并通过使用不同的分区键来优化查询。关于如何设计可扩展和高性能的表,link下面供您参考。

Azure Storage Table Design Guide: Designing Scalable and Performant Tables

Get player's position in the scoreboard.

如果我们想得到位置(排名),最好的存储数据的方法是使用分数作为分区键。如果我们只知道当前玩家的id,​​那么在查询位置之前需要先获取当前玩家的分数。要根据玩家 ID 查询玩家,最好通过将玩家 ID 设置为分区键,在 Azure Table 存储中存储另一份游戏数据。

Get positions (player and score) between position X and Y.

使用score作为partition key适合这种场景。

综上所述,我建议您保存两份不同的游戏数据。一种是使用分数作为分区键,另一种是使用玩家 ID 作为分区键。

For both queries I don't know the position

您查询的结果数据是按分区键排序的。所以结果数就是你的玩家的位置(排名)。

例如,将score设置为partition key后,可以使用小于score(ex. 10)的partition key查询一定范围内的entity,entity count为得分为10的玩家位置。

计算 在列表中的位置 的问题是,当您的数据可以分布在多个服务器上时,排名(和一般的 window 操作)很难做到.随着数据的增长,它会成为一项昂贵的操作,并且扩展性不佳。

Data Lake 确实支持 window 个函数,包括 RANK() and DENSE_RANK()。您需要使用一些有代表性的数据对此进行良好测试,以查看性能如何。


另一种选择是在一定的时间间隔内预先计算排名,然后"cache"将完整的记分牌存入Table存储;如果acceptable因为对用户有延迟(例如你可以说排名每小时刷新一次)

那么您可以安排一个 WebJob:

  1. 读取所有分数高于阈值的分数记录,按分数排序
  2. 为玩家排名创建一个新的 table,例如player-scores-170401-124200
  3. 为总体排名创建一个新的 table,例如scoreboard-170401-124200
  4. 将排名记分板批量写入新的tables

这样,您可以在 PartitionKey=player 上查询 player-scores 以获得个人排名,并使用不等式过滤器查询 scoreboard 以获得 window 的位置.

查询(这是您可能应该优化的操作)变得非常便宜和容易,因为它是每条信息的单个事务。

运行 计算也不是太昂贵,交易成本是您需要关注的事情。假设n个得分记录:

  • 您可以批量读取 100 个。事务 ~= n / 100
  • 您可以以 100 个为一组写入同一分区。事务 ~= 2n / 100
  • 您可以选择保留历史排名,或在单笔交易中删除整个 table

所以您的 100 万条记录的总交易量是:

1000000*3/100 = 30,000 per run

每天每小时执行一次(72 万笔交易),每天大约花费 2-3 美分(当然不包括存储成本)。在我的书中,这比 运行 便宜 :-)