【问题标题】:Best way to store replay data存储回放数据的最佳方式
【发布时间】:2015-09-05 19:01:24
【问题描述】:

我目前正在分析一个游戏回放文件,并且想知道存储数据的最佳方式,以便我可以快速创建复杂的查询。例如,分析器每 50 毫秒返回一个数据结构,您可以在其中访问当前回合详细信息和当前玩家状态的快照,例如他持有什么武器、他有多少生命值、他目前射杀了哪些球员等从 10000 毫秒到 20000 毫秒,玩家“Kyle”对其他玩家造成了多大的伤害。我希望能够存储所有正在分析的数据并使用 API 在前端重放它,以便您可以直观地重放它。

我可以将有关回放的元数据存储到数据库中,例如:(Round 1, StartTime: 10000, EndTime: 30000), (Round 2, StartTime: 31000, EndTime: 37000)。我还可以存储有关玩家何时被杀(Kyle,DeathTime:31000,KilledBy:Micheal)或玩家受伤(Kyle,HurtBy:Micheal,伤害:10,武器:x)的元数据。

要完成我想要为不同情况创建复杂查询的目标,我需要将两者结合起来吗?例如将毫秒级的数据存储在 NoSql 数据库/文档中,然后解析整个文件并将第二段中提到的元数据存储到另一个数据库中。只存储毫秒级的数据然后能够创建快速查询来解析我想要的数据是不可行的吗?

【问题讨论】:

    标签: azure database-design azure-cosmosdb


    【解决方案1】:

    听起来你正在开发一款很酷的游戏。 Microsoft Azure Table Storage(一个 NoSQL 数据库)非常快,非常适合您的游戏。您可以使用 Slazure(我编写了 Slazure BTW),它包含一个查询语言(一个自定义 LINQ 提供程序),它还将数据存储在 Azure 表存储中。如果我为您的游戏使用 Slazure 和 Microsoft Azure Table Storage NoSQL 数据库,我会这样做。我建议您为每个事件在 PlayersTable 表中存储一行,其中 PartitionKey 是玩家姓名,RowKey 是进入游戏回合的毫秒数 - 因为每个表都在PartitionKey 列并按 RowKey 列排序,所有使用这些列的查询确实非常快。还为使用的武器、屏幕上的位置、生命值、回合数以及被谁杀死的玩家创建了列。您可以使用以下相同的建议方法创建一个表来保存损坏数据:

    using SysSurge.Slazure;
    using SysSurge.Slazure.Linq;
    using SysSurge.Slazure.Linq.QueryParser;
    
    namespace TableOperations
    {
        public class PlayerInfo
        {
            // List of weapons
            public enum WeaponList {
                Axe, Arrow, Knife, Sling
            };
    
            // Update a player with some new data
            public void UpdatePlayerData(dynamic playersTable, DateTime gameStartedTime, int round, string playerName, WeaponList weapon, int healthPoints, int xPos, int yPos)
            {
                // Create an entity in the Players table using the player name as the PartitionKey, the entity is created if it doesn't already exist
                var player = playersTable.Entity(playerName);
    
                // Store the time the event was recorded for later as milliseconds since the game started. 
                // This means there is one row for each stored player event
                player.Rowkey = ((DateTime.UtcNow.Ticks - gameStartedTime.Ticks)/10000).ToString("d19");
    
                player.Round = round; // Round number
                player.Weapon = (int)weapon; // Weapon carried by player. Example Axe
    
                // Store player X and Y position coordinates on the screen
                player.X = xPos;
                player.Y = yPos;
    
                // Number of health points, zero means player is dead
                player.HealthPoints = healthPoints; 
    
                // Save the entity to the Azure Table Service storage
                player.Save()
           }
    
            // Update a player with some new data
            public void PlayerKilled(dynamic playersTable, DateTime gameStartedTime, int round, string playerName, string killedByPlayerName)
            {
                // Create an entity in the Players table using the player name as the PartitionKey, the entity is created if it doesn't already exist
                var player = playersTable.Entity(playerName);
    
                // Store the time the event was recorded for later as milliseconds since the game started. 
                // This means there is one row for each stored player event
                player.Rowkey = ((DateTime.UtcNow.Ticks - gameStartedTime.Ticks)/10000).ToString("d19");
    
                player.Round = round; // Round number
    
                // Number of health points, zero means player is dead
                player.HealthPoints = 0;    
    
                player.KilledByPlayerName = killedByPlayerName; // Killed by this player, example "Kyle"
    
                // Save the entity to the Azure Table Service storage
                player.Save()
           }
    
            // Get all the player positions between two time intervals
            public System.Linq.IQueriable GetPlayerPositions(dynamic playersTable, string playerName, int fromMilliseconds, int toMilliseconds, int round)
            {
                return playersTable.Where("PrimaryKey == @0 && RowKey >= @1 && RowKey <= @2 && Round == @3", 
                    playerName, fromMilliseconds.ToString("d19"), toMilliseconds.ToString("d19"), round).Select("new(X, Y)");
            }      
    
        }
    }
    

    首先你需要记录游戏开始的时间和回合数:

    var gameStartedTime = DateTime.UtcNow;
    var round = 1;  // Round #1
    

    ,并在 NoQL 数据库中创建一个表:

    // Get a reference to the Table Service storage
    dynamic storage = new DynStorage("UseDevelopmentStorage=true");
    
    // Get reference to the Players table, it's created if it doesn't already exist
    dynamic playersTable = storage.Players;
    

    现在,在游戏过程中你可以像这样不断更新玩家信息:

    UpdatePlayerData(playersTable, gameStartedTime, round, "Micheal", WeaponList.Axe, 45, 12313, 2332);
    UpdatePlayerData(playersTable, gameStartedTime, round, "Kyle", WeaponList.Knife, 100, 13343, 2323);
    

    如果您需要在每个存储事件之间等待 50 毫秒,您可以执行以下操作:

    System.Threading.Thread.Sleep(50);
    

    ,然后再存储一些玩家事件数据:

    UpdatePlayerData(playersTable, gameStartedTime, round, "Micheal", WeaponList.Axe, 12, 14555, 1990);
    UpdatePlayerData(playersTable, gameStartedTime, round, "Kyle", WeaponList.Sling, 89, 13998, 2001);
    

    当其中一名玩家死亡时,您可以调用相同的方法,但其生命值为零,并带有杀死他/她的玩家的姓名:

    PlayerKilled(playersTable, gameStartedTime, round, "Micheal", "Kyle");
    

    现在,稍后在您的游戏分析器中,您可以查询从游戏开始 (0 毫秒) 到 10,000 毫秒进入游戏的所有位置,如下所示:

    // Get a reference to the table storage and the table
    dynamic queryableStorage = new QueryableStorage<DynEntity>("UseDevelopmentStorage=true");
    QueryableTable<DynEntity> queryablePlayersTable = queryableStorage.PlayersTable;
    
    var playerPositionsQuery = GetPlayerPositions(queryablePlayersTable, "Micheal", 0, 10000, round);
    
    // Cast the query result to a dynamic so that we can get access its dynamic properties
    foreach (dynamic player in playerPositionsQuery)
    {
        // Show player positions in the console
        Console.WriteLine("Player position: Name=" + player.PrimaryKey + ", Game time MS " + player.RowKey + ", X-position=" + player.X  + ", Y-position=" + player.Y;
    }
    

    【讨论】:

    • 假设玩家移动了大约 30000 个位置。 (X、Y、Z、LookLocation、速度)。抢到这些位置的表现如何?我们还假设有 100 人试图做同样的事情。 Azure 的表存储没有限制吗?除此之外,我将对此进行试验,看看它对我的效果如何。
    • @MohamadBataineh Azure 存储可以处理 20,000 IOPS(每秒读/写操作),这对于大多数应用程序来说应该足够了,有关更多详细信息,请参阅 azure.microsoft.com/en-us/documentation/articles/…。您可以一次性获取所有 30,000 个位置,或者在 Slazure 查询中使用分页来一次仅检索部分查询结果。
    • @MohamadBataineh 您可以一次性获取所有 30,000 个位置,或者在 Slazure 查询中使用分页一次仅检索部分查询结果,例如 .Take(1000).Skip(10000) 将检索 1,000 行并跳过因此,前 10,000 行仅检索第 10,000 到 11,000 行。有关详细信息,请参阅示例 syssurge.com/Help/Slazure/html/…syssurge.com/Help/Slazure/html/…
    猜你喜欢
    • 1970-01-01
    • 2018-12-01
    • 2013-09-10
    • 2014-07-28
    • 2012-05-10
    • 2011-11-26
    • 2018-12-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多