Каков самый быстрый/простой способ подсчета активных пользователей за последнюю минуту?

Вы работаете в Zynga и хотите подсчитать количество активных игроков в настоящее время для разных игр. Ваш веб-сервер обрабатывает пинги из разных игр, и каждый пользователь имеет уникальный идентификатор GUID. Должна иметь возможность запрашивать количество активных пользователей за одну игру за раз. Активными пользователями являются те, кто получил ping в последнюю минуту.

Строки журнала поступают непрерывно на веб-сервер:

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();
  }
};

Ответы

Ответ 1

Предполагая, что это решение в реальном времени, вы можете обрабатывать запросы ping в O (1), генерировать текущую статистику игрока в O (1) и использовать пространство O (num_player), жертвуя некоторой точностью. Ключ должен дискретировать тайминги.

Обзор

Основная идея состоит в том, чтобы представлять дискретные временные интервалы в качестве объектов и хранить в этих объектах следующее свойство: количество отдельных игроков, которые пинговали в течение этого интервала времени, которые не были отсканированы. Чтобы запросить количество активных пользователей, вычислите взвешенную сумму последних х временных интервалов, которые составляют последнюю минуту.

Подробнее

Сначала выберите приемлемое временное разрешение. В этом примере я выбираю 15-секундные интервалы.

Поддерживайте пять структур данных PingInterval, чтобы представить пять из этих интервалов (охватывая еще 1 интервал, чем 1 минуту). PingInterval содержит одно свойство: счетчик. Эти PingIntervals поддерживаются в PingMonitor. Каждый раз, когда игрок пингует, обновляйте карту в PingMonitor, которая отображает каждого игрока на текущий временной интервал. Когда вы выполняете это сопоставление, выполните следующие шаги, которые поддерживают подсчеты в пределах PingIntervals (в соответствии с характеристиками, описанными в разделе обзора).

  • Если игрок уже отображен на интервал и является текущим интервалом, ничего не делайте.
  • Иначе, если игрок сопоставляется с интервалом, который не является текущим интервалом,
    • уменьшает счетчик старых интервалов,
    • увеличить счетчик текущих интервалов,
    • и сопоставьте этого игрока с этим интервалом.
  • Иначе, если игрок не отображен на интервал вообще,
    • увеличить счетчик текущих интервалов,
    • сопоставить плеер с текущим интервалом.

(Если PingInterval для представления текущего времени еще не существует, установите самый старый PingInterval равным null, создайте новый PingInterval поточно-безопасным способом, а затем продолжите как обычно.)

Если вы хотите запросить количество активных пользователей, вычислите взвешенную по времени сумму последних пяти интервалов времени. Например, если вы находитесь всего на 5 секунд в текущем временном интервале (что означает, что следующие 10 секунд этого интервала еще не были выполнены), вычислите это значение: 2/3 * самый старый интервал + сумма из четырех последних интервалов.

Другие мысли

Пять интервалов очень консервативны; мы могли бы увеличить число значительно для большей точности (возможно, один раз в секунду), и это все равно позволит нам значительно сэкономить. Важная часть состоит в том, что наши времена теперь являются дискретными интервалами. Это означает, что, когда мы переходим к подсчету числа активных пользователей, нам не нужно смотреть на каждое индивидуальное время (равное количеству пользователей); вместо этого мы можем посмотреть на х бит времени, которые мы предопределили.

Ответ 2

Мой подход состоял бы в том, чтобы использовать deque (называемый очередью в остальной части этого сообщения), чтобы все GUID были нажаты на это, что наблюдается, т.е. таким образом упорядочивается по возрасту. Кроме того, я бы использовал hashmap, содержащий указатели на записи любого GUID, присутствующего в очереди.

Когда новый GUID помещается в очередь, старая запись (если она есть) будет просматриваться в hashmap, удалена из очереди, а новая назначается вместо этого в hashmap.

По прошествии времени все записи в очереди, которые превышают порог возраста, будут выведены (и удалены из хэш-карты).

Длина очереди (число активных пользователей a.k.a.) может отслеживаться как отдельная переменная, чтобы избежать перескока через очередь для каждого запроса.

Для поддержки нескольких игр просто добавьте такую ​​структуру для каждого идентификатора игры.

Сложность: O (1) вставка/удаление наблюдений (с учетом идеального хэша, т.е. никаких столкновений), O (1) запрос, O (n) пространство.

Ответ 3

EDIT: Я полагаю, что этот вопрос касался не получения ответа в реальном времени на вопрос "сколько пользователей сейчас активен", а скорее получение исторических значений - сколько пользователей было активным в 15:25. Я сохраняю прежнее решение ниже нового:

Итак, вы хотите знать, сколько пользователей сейчас активны, сохраняйте очередь за игру. Всякий раз, когда вы видите новую запись в журнале, узнайте, к какой игре она принадлежит, и добавьте ее в очередь игры. После каждого добавления очистка старых записей в начале очереди (все записи старше 1 минуты на момент очистки).

Когда вас попросят количество активных пользователей в игре, выполните ту же самую очистку в очереди игры и верните глубину очереди.

Держите хэш, сопоставляя игры с очередями, и вы получили операцию O (N), где N - количество строк в журнале - каждая строка обрабатывается не более двух раз - один раз для ее добавления, один раз для ее удаления, Вы также делаете дополнительное сравнение для добавления и поиска (при принятии решения о записи в очередь не достаточно старо), но это постоянное время времени N. So O (N) во всех.

Предыдущий ответ на этот другой вопрос: Увидев, что не так много минут (1440 в день), я бы создал вектор для каждой игры с слотом для каждой минуты.

Перейдите по файлу журнала, чтобы каждая строка получила время, округлите его до ближайшей минуты и добавьте 1 к соответствующему слоту в массиве. Как только вы закончите, вы будете точно знать, сколько активных пользователей было для каждой игры в каждую минуту.

Сложность - O (N), где N - количество строк в файле журнала.

Чтобы поддерживать несколько игр, просто используйте хеш для отображения из названия игры в вектор.

Теперь это предполагает, что вы проверяете только активных пользователей на всех минутах (1:00:00, 1:01:00 и т.д.). Это, вероятно, то, что вам нужно делать в любом случае.

Ответ 4

Это будет моя последовательность ответов:

  • Зачем беспокоиться? Проще всего подсчитывать, сколько пользователей активно. Разве это недостаточно, чтобы знать это?
  • Если вы действительно заботитесь о новейшей информации, включите подсчет во второй раз (как описано Cheeken). Это будет точно до доли секунды.
  • Хорошо, если точность в реальном времени является "необходимой", и вы хотите взять интервью у меня о структурах данных, позвольте использовать кучу клиентов, набранных по последнему времени активности (как описано Мастер Йодой).
  • Если нужна точность в реальном времени, и мы должны были сделать это в процессе производства, позвольте использовать сервер структуры данных Redis. Мы сохраняем отсортированный набор клиентов, забитых последним временем активности. Мы можем запросить, сколько клиентов было активным в последнюю минуту или в последний час с помощью команды zcount. Это полезно и надежно.