19

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

Например: предположим, что есть только 3 компьютера, и нам нужны два верхних самых посещаемых URL-адресов.

 
Computer A: url1, url2, url1, url3 
Computer B: url4, url2, url1, url1 
Computer C: url3, url4, url1, url3 

url1 appears 5 times in all logs 
url2 2 
url3 3 
url4 2 

So the answer is url1, url3 

Файлы журнала слишком велики, чтобы вставлять их в ОЗУ и копировать их по сети. Насколько я понимаю, важно также сделать вычисление параллельным и использовать все данные компьютеры.

Как бы Вы это разрешили?

+0

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

+3

@SparKot Вы уверены, что мы можем взять только первую десятку с каждого компьютера и выбросить все остальные URL-адреса? Что делать, если первая десятка содержит верхнюю десятку – Michael

+0

Упс! Я согласен с вышеприведенным комментарием. Хороший вопрос +1 – SparKot

ответ

15

Это довольно стандартная проблема, для которой существует хорошо известное решение. Вы просто сортируете файлы журнала на каждом компьютере по URL-адресу и затем объединяете их через очередь приоритетов размера k (количество элементов, которые вы хотите) на «главном» компьютере. Этот метод существует с 1960-х годов и до сих пор используется (хотя и слегка изменен) в форме MapReduce.

На каждом компьютере извлеките URL-адрес и счетчик из файла журнала и отсортируйте по URL-адресу. Поскольку файлы журналов больше, чем вписываются в память, вам необходимо выполнить слияние на диске. Это подразумевает чтение фрагмента файла журнала, сортировку по URL-адресу, запись фрагмента на диск. Чтение следующего фрагмента, сортировка, запись на диск и т. Д. В какой-то момент у вас есть фрагменты файла журнала M, каждый из которых отсортирован. Затем вы можете выполнить слияние M-way. Но вместо того, чтобы записывать элементы на диск, вы представляете их в отсортированном порядке (отсортированном по URL-адресу, то есть), «хозяину».

Каждая машина сортирует свой собственный журнал.

«Главный» компьютер объединяет данные с отдельных компьютеров и выполняет выбор K верхнего уровня. Это на самом деле две проблемы, но их можно объединить в одну.

Мастер создает две очереди очередности: одну для слияния и одну для выбора K. Первый имеет размер N, где N - количество компьютеров, с которых они объединяют данные. Второй - размер K: количество элементов, которые вы хотите выбрать. Для этого я использую кучу минут, так как это легко и быстро.

Чтобы настроить очередь слияния, инициализируйте очередь и получите первый элемент с каждого из «рабочих» компьютеров. В псевдокоде ниже «получить наименьший элемент из очереди слияния» означает получение корневого элемента из очереди слияния, а затем получение следующего элемента из того, какой рабочий компьютер представил этот элемент. Поэтому, если очередь содержит [1, 2, 3], а элементы поступают с компьютеров B, C, A (в указанном порядке), то получение младшего элемента означает получение следующего элемента с компьютера B и добавление его в очередь приоритетов.

Мастер затем выполняет следующие действия:

working = get lowest item from merge queue 
while (items left to merge) 
{ 
    temp = get lowest item from merge queue 
    while (temp.url == working.url) 
    { 
     working.count += temp.count 
     temp = get lowest item from merge queue 
    } 
    // Now have merged counts for one url. 
    if (topK.Count < desired_count) 
    { 
     // topK queue doesn't have enough items yet. 
     // so add this one. 
     topK.Add(working); 
    } 
    else if (topK.Peek().count < working.count) 
    { 
     // the count for this url is larger 
     // than the smallest item on the heap 
     // replace smallest on the heap with this one 
     topK.RemoveRoot() 
     topK.Add(working) 
    } 
    working = temp; 
} 
// Here you need to check the last item: 
if (topK.Peek().count < working.count) 
{ 
    // the count for this url is larger 
    // than the smallest item on the heap 
    // replace smallest on the heap with this one 
    topK.RemoveRoot() 
    topK.Add(working) 
} 

В этот момент, topK очереди имеет элементы K с самыми высокими пунктами.

Таким образом, каждый компьютер должен выполнить сортировку слиянием, которая представляет собой O (n log n), где n - это количество элементов в журнале этого компьютера. Слияние на ведущем устройстве - это O (n), где n - сумма всех предметов с отдельных компьютеров. Выбор верхних позиций k - O (n log k), где n - это число уникальных адресов.

Сортировка выполняется параллельно, конечно, с каждым компьютером, который готовит свой отсортированный список. Но «слияние» части сортировки выполняется одновременно с подключением главного компьютера, поэтому происходит некоторая координация, и на этом этапе задействованы все машины.

+0

Вы делали какие-либо предположения относительно содержимого журнала вы указываете: * Но вместо того, чтобы записывать элементы на диск, вы представляете их в отсортированном порядке (отсортированном по URL-адресу, то есть), «хозяину». * Если URL-адреса в файле журнала уникальны, в основном, означает, что вы отправляете весь файл поверх мастера. Но в этом вопросе говорится: * Файлы журналов слишком велики, чтобы поместиться в ОЗУ и скопировать их по сети. * –

+0

@ReinierTorenbeek: Во-первых, мы не отправляем весь файл : только URL и счет. Файл журнала, по-видимому, содержит гораздо больше информации. Во-вторых, мастер должен видеть счетчик для каждого URL-адреса, иначе он не может достоверно узнать количество. Есть другие способы решения этой проблемы (назначьте диапазоны например, URL-адресов для отдельных машин), но все они включают в себя много сетевого трафика и более сложная связь между несколькими компьютерами. –

+0

Ваша презумпция на размер файла журнала звучит довольно случайным образом для меня. Если вы считаете правильным, что «файл журнала, по-видимому, содержит гораздо больше информации, чем только URL-адреса, поэтому мы можем отправлять все URL-адреса по сети», вы можете также заявить, что «файл журнала, по-видимому, содержит гораздо больше информации, чем просто URL-адреса, чтобы мы могли хранить все URL-адреса в памяти ».Тем не менее вы просматриваете длины слияния на диске. –

0

Приведенное ниже описание представляет собой идею для решения. это не псевдокод.
У вас есть коллекция систем.
1. для каждого A: Коллекции (системы)
1.1) Запустите daemonA на каждом компьютере, который проверяет файл журнала на предмет изменений.
1.2) Когда замечено изменение, пробуждение AnalyzerThreadA
1.3) Если AnalyzerThreadA находит URL-адрес с использованием некоторого регулярного выражения, обновите localHashMapA с помощью count ++.
(key = URL, value = count).
2) Вставьте topTen записи localHashMapA в ComputerA, где будет запущен демон AnalyzeAll.

Вышеуказанный шаг будет последним шагом в каждой системе, который будет толкать записи topTen в главную систему, например, например, computerA.

3) AnalyzeAll работает в компьютереA будет разрешать дубликаты и обновлять счет в masterHashMap URL.

4) Распечатайте topTen из masterHashMap.

+0

Что делать, если URL-адрес находится в 11-й позиции на всех системах, и если рассмотренный для обработки может быть в верхней 3? – SparKot

1

Предварительная обработка: каждая компьютерная система обрабатывает полный файл журнала и подготавливает уникальный список URL-адресов с подсчетом против них.

Получения лучших ссылок:

  1. Вычислить URL-адрес имеет значение в каждой компьютерной системе
  2. процесса упорядочения в центральной системе (виртуальный)
    • Отправить URL-адрес с подсчетом к центральному блоку одной обработки по одному в DESC-порядок (т. Е. Сверху)
    • В центральной системе сопоставлять входящие URL-адреса
    • Повторяйте до тех пор, пока сумма всех отсчетов от входящих URL-адресов не будет меньше счет десятого URL-адреса в основном списке. Жизненно важный шаг, чтобы быть абсолютно уверенным

PS: Вы будете иметь лучшие десять URL-адресов в различных системах, не обязательно в таком порядке. Для получения фактического заказа вы можете обратная сортировка. Для данного URL в первой десятке получите индивидуальный счет от dist-компьютеров и сформируйте окончательный заказ.

1

Предполагая, что приведенные ниже условия:

  • Вам нужна верхняя п URLs из м хостов.
  • Вы не можете хранить файлы в памяти
  • Существует главный узел

я бы подход ниже:.

Каждый узел считывает часть файла (т.е. Max URLs , где MAX может быть, скажем, 1000 URL-адресов) и сохраняет массив arr [MAX] = {url, hits}.

Когда узел считывает MAX URL-адрес с файла, он отправляет список на главный узел и перезапускает чтение до тех пор, пока МАКС-адреса снова не будут достигнуты.

Когда узел достигает EOF, он отправляет оставшийся список URL-адресов и флаг EOF на главный узел.

Когда главный узел получает список URL-адресов, он сравнивает его со своим последним списком URL-адресов и генерирует новый, обновленный.

Когда главный узел получает флаг EOF от каждого узла и заканчивает чтение собственного файла, верхние n URL-адресов последней версии его списка - это те, которые мы ищем.

Или

Другой подход, который будет выпускать мастер делать все работы могут быть:

Каждый узел считывает файл и хранит массив так же, как описано выше, чтение до конца файла.

Когда EOF, узел отправит первый URL-адрес списка и количество обращений к мастеру.

Когда мастер собрал первый URL-адрес и количество обращений к каждому узлу, он создает список. Если главный узел имеет меньше n URL-адресов, он попросит узлы отправить второй и т. Д. Пока у мастера не будет отсортировано n URL.

+1

Если вы отправляете только главный URL-адрес для каждого узла, следующее не будет получать URL-адреса max: 4, 3 ПК, получить верхнюю 3, counts = '[1,2,0,0], [1,0,2, 0], [1,0,0,2] '. Первый URL ('1 + 1 + 1 = 3') является максимальным, но мастер получает только URL 2-4 (' 2,2,2'). – Dukeling

+0

Да, я знаю. Это не проблема. Вот почему, если у мастера недостаточно URL-адресов, он будет запрашивать больше, пока список не будет завершен. Однако я думаю, что этот параметр не будет работать, потому что третий URL-адрес одного узла может быть первым в другом узле и никогда не попасть в мастер ... :-( – ophintor

2

Учитывая масштаб лог-файлов и общий характер вопроса, это довольно сложная проблема. Я не думаю, что есть один лучший алгоритм для всех ситуаций. Это зависит от характера содержимого файлов журнала. Например, обратите внимание на то, что все URL-адреса являются уникальными во всех файлах журнала. В этом случае в принципе любое решение займет много времени, чтобы сделать этот вывод (если он даже дойдет до этого ...), и на ваш вопрос нет ответа, потому что нет десятки.

У меня нет водонепроницаемого алгоритма, который я могу представить, но я бы исследовал решение, которое использует гистограммы хеш-значений URL-адресов, а не самих URL-адресов. Эти гистограммы могут быть рассчитаны с помощью однопроходного файла, поэтому он может обрабатывать файлы журнала произвольного размера. В псевдокоде, я бы что-то вроде этого:

  • Используйте хэш-функцию с ограниченным целевым пространством (скажут 10000, обратите внимание, что сталкивающийся хэш-значение, как ожидается), чтобы вычислить значение хэша-функцию для каждого элемента файл журнала и подсчитать, сколько раз каждое из значений имеет значение. Сообщайте полученную гистограмму с сервером (хотя, вероятно, также возможно избежать централизованного сервера путем многоадресной передачи результатов на каждый другой узел, но я буду придерживаться более очевидного подхода к серверу здесь)
  • Сервер должен объединить гистограммы и передать результат обратно. В зависимости от распределения URL-адресов может существовать целый ряд четко видимых пиков, содержащих URL-адреса, которые были посещены сверху.
  • Каждый из узлов должен сфокусироваться на вершинах гистограммы.Он должен снова пройти через свой файл журнала, использовать дополнительную хеш-функцию (снова с ограниченным целевым пространством), чтобы вычислить новую хэш-гистограмму для тех URL-адресов, которые имеют первое значение хэш-функции в одном из пиков (количество пиков для фокусировки on будет параметром, который будет настроен в алгоритме, в зависимости от распределения URL-адресов) и вычислить вторую гистограмму с новыми значениями хеширования. Результат должен быть передан серверу.
  • Сервер должен снова объединить результаты и проанализировать новую гистограмму по сравнению с исходной гистограммой. В зависимости от четко видимых пиков он может сделать выводы о двух хэш-значениях десяти лучших URL-адресов уже. Или это может потребовать, чтобы машины вычисляли больше значений хэша со второй хэш-функцией и, вероятно, после этого прошли третий проход хеш-вычислений с еще одной хэш-функцией. Это должно продолжаться до тех пор, пока из коллективной группы гистограмм не будет сделан вывод о том, каковы значения хеша пиковых URL-адресов, а затем узлы могут идентифицировать разные URL-адреса.

Обратите внимание, что этот механизм потребует настройки и оптимизации в отношении нескольких аспектов алгоритма и хэш-функций. Также потребуется серверная настройка для того, какие вычисления должны выполняться в любое время. Вероятно, также необходимо будет установить некоторые границы, чтобы сделать вывод, когда нельзя сделать вывод, другими словами, когда «спектр» значений хеш-адресов URL слишком плоский, чтобы сделать все возможное для продолжения расчетов.

Этот подход должен работать хорошо, если в URL-адресах имеется четкое распределение. Я подозреваю, что, фактически, вопрос в этом случае имеет смысл.

+0

Ничего себе. Это очень сложный и дорогостоящий способ получить результат, который вы не можете доказать, является правильным. –

+0

Я бы не стал его внедрять, пока не доказал, что это правильно :-) Почему вы говорите, что это дорого? Я думаю, что для решения требуются вычисления «O (n)» и гораздо меньше сетевого трафика, чем это (с «n» общее количество записей в каждом файле на каждом узле). –