在最后一分钟内计算活跃用户的最快捷/最简单的方法是什么?

时间:2012-06-14 18:29:00

标签: algorithm data-structures time-series

您为Zynga工作,并希望计算不同游戏的当前活跃玩家数量。您的Web服务器处理来自许多不同游戏的ping,每个用户都有一个唯一的GUID。必须能够一次查询一个游戏的活跃用户数。活跃用户是那些在最后一刻获得ping的用户。

日志行连续进入Web服务器:

10.1.12.13 - - "http://zynga.com/ping?guid=<guid>&game=<gameID>" -

计算活跃用户的最快捷/最简单的方法是什么? 请使用一些代码建议45分钟的答案。


我的版本

// web server interface, every time ping comes in count() will be called
// void count(String gameId, String guid)
// int getNumberActivePlayers(String gameId)

struct Record{
  String gameID;
  String guid;
};

class PingStorage{
private:
  max_heap<long, Record> storage;
public:
  //    O(log(n))
  //  n = total number of elements in storage
  void count(String gameId, String guid){
    long currentTimeStamp = getUnixTimeStamp();
    Record rec ;
    rec.gameId = gameId;
    rec.guid = guid;
    storage.add(currentTimeStamp, rec);
  }
  //N = numner of records in last ,minutes in storage
  //O(N)
  int getNumberActivePlayers(String gameId){
    map<String, Set<string> > game2user;
    long tillTimeStamp = getUnixTimeStampNow() - 60;
    while(true){
      pair<long, Record> rec = storage.getMax(); //O(1)
      if(rec.first <= tillTimeStamp) break;  
      Set<String> temp = game2user[rec.gameid]; //O(1)
      temp.add(rec.userid); //O(log(N)) - O(1)
    }
    return game2user[gameID].size();
  }
};

4 个答案:

答案 0 :(得分:7)

假设这是一个实时解决方案,您可以在O(1)中处理ping请求,在O(1)中生成当前播放器统计信息,并通过牺牲一些准确性来使用O(num_player)空间。关键是要时间安排。

<强>概述

基本思想是将离散时间间隔表示为对象,并在这些对象中存储以下属性:在此时间间隔内未执行ping的不同玩家的数量。要查询活动用户数,请计算构成最后一分钟的最后x个时间间隔的加权总和。

<强>详情

首先,选择可接受的时间分辨率。在这个例子中,我选择15秒的间隔。

维护五个PingInterval数据结构以表示其中五个间隔(跨越1个间隔而不是1分钟)。 PingInterval包含一个属性:一个计数器。这些PingIntervals在PingMonitor中维护。每次玩家ping,都会更新PingMonitor中的地图,将每个玩家映射到当前时间间隔。执行此映射时,请执行以下步骤,以维护PingIntervals中的计数(根据概述部分中描述的特征)。

  • 如果播放器已经映射到一个间隔并且它是当前间隔,则不执行任何操作。
  • 否则,如果播放器被映射到不是当前间隔的间隔,
    • 减少旧间隔的计数,
    • 增加当前间隔的计数,
    • 并将该玩家映射到该间隔。
  • 否则,如果玩家没有完全映射到间隔,
    • 增加当前间隔的计数,
    • 将玩家映射到当前间隔。

(如果尚未存在表示当前时间的PingInterval,请将最旧的PingInterval设置为null,以线程安全的方式创建新的PingInterval,然后照常继续。)

如果要查询活动用户数,请计算最后五个时间间隔的时间过去加权总和。例如,如果您距离当前时间间隔仅5秒(意味着该间隔的下一个10秒尚未发生),请计算此值:2/3 *最早的间隔+ 4个最新间隔的总和。

其他想法

五个间隔非常保守;我们可以大幅度扩大数量以获得更高的准确度(可能是每秒一次),它仍然可以为我们节省大量成本。重要的是,我们的时代现在是不连续的时间间隔。这意味着当我们计算活跃用户的数量时,我们不必查看每个单独的时间(这等于用户数);相反,我们可以查看我们预定义的x个时间段。

答案 1 :(得分:3)

我的方法是使用deque(在本文的其余部分中称为队列)将所有GUID推送到观察到的,即按年龄排序。另外,我会使用一个hashmap,其中包含指向队列中任何GUID条目的指针。

当新的GUID被推送到队列时,旧的条目(如果有的话)将在hashmap中被查找,从队列中删除,而新的条目被分配给hashmap。

随着时间的推移,队列中超过年龄阈值的所有条目都将被弹出(并从散列图中删除)。

队列的长度(也就是活动用户数)可以作为单独的变量跟踪,以避免每次查询跳过队列。

要支持多个游戏,只需为每个游戏ID添加此类结构。

复杂性:O(1)插入/删除观察(给定完美的散列,即没有冲突),O(1)查询,O(n)空间。

答案 2 :(得分:0)

编辑:我认为这个问题不是为了获得“有多少用户活跃现在”的问题的实时答案,而是获取历史价值 - 有多少用户活跃于3:25 PM。我将旧解决方案保留在新解决方案之下:

所以,你想知道现在有多少用户活跃,每场比赛保持一个队列。每当您看到新的日志条目时,找出它所属的游戏,并将其添加到游戏的队列中。每次添加后,清理队列开头的旧条目(清理时超过1分钟的所有条目)。

当被问及游戏中活跃用户的数量时,在游戏队列中进行相同的清理,并返回队列的深度。

保持哈希映射游戏到队列,你得到一个O(N)操作,其中N是日志中的行数 - 每行最多处理两次 - 一次用于添加,一次用于删除。你还可以在每次添加和查找时进行额外的比较(当确定队列条目足够老时),但这是N的常数时间。所以O(N)都是。

以前对其他问题的回答: 看到没有那么多分钟(每天1440),我会为每场比赛创建一个矢量,每分钟都有一个插槽。

遍历日志文件,为每一行获取时间,将其四舍五入到最近的分钟,并将1添加到阵列中的相应插槽。一旦完成,您就会确切知道每分钟每个游戏的活跃用户数量。

复杂性 - O(N),其中N是日志文件中的行数。

要支持多个游戏,只需使用哈希从游戏名称映射到其矢量。

现在,假设您只检查整个分钟边界(1:00:00,1:01:00等)的活跃用户。这可能是你需要做的事情。

答案 3 :(得分:0)

这将是我的答案序列:

  1. 为什么要这么麻烦?最简单的计算每分钟有多少用户是活跃的。知道这个还不够吗?
  2. 如果您真的关心最新信息,那么让我们计算一下二分之一(如Cheeken所述)。这将精确到几分之一秒。
  3. 好的,如果实时准确性是“必要的”,并且您想要采访我的数据结构,那就让我们使用按上次活动时间评分的一堆客户(如尤达大师所描述的那样)。
  4. 如果需要实时准确性,并且我们要在生产中执行此操作,那么让我们使用数据结构服务器Redis。我们按照上次活动时间维持sorted set个客户评分。我们可以使用zcount命令查询最后一分钟或最后一小时内有多少客户处于活动状态。这是有用和可靠的。