并行十大分布式数据算法

时间:2013-03-25 11:37:52

标签: algorithm language-agnostic parallel-processing distributed-computing

这是一个面试问题。假设有几台计算机,每台计算机都保存一个非常大的访问URL日志文件。找到十大访问量最大的网址。

例如:假设只有3台计算机,我们需要前两个访问最多的网址。

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

日志文件太大而无法容纳在RAM中并通过网络复制它们。据我了解,重要的是使计算并行并使用所有给定的计算机。

你会如何解决它?

6 个答案:

答案 0 :(得分:16)

这是一个非常标准的问题,有一个众所周知的解决方案。您只需按URL对每台计算机上的日志文件进行排序,然后将它们合并到“主”计算机上的大小为k的优先级队列(所需的项目数)。自20世纪60年代以来,这种技术一直存在,并且今天仍以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唯一网址的数量。

当然,这些排序是并行完成的,每台计算机都准备了自己的排序列表。但是,排序的“合并”部分是在主计算机合并的同时完成的,因此存在一些协调,并且所有机器都参与该阶段。

答案 1 :(得分:3)

鉴于日志文件的规模和问题的一般性质,这是一个非常难以解决的问题。我认为没有一种适用于所有情况的最佳算法。这取决于日志文件内容的性质。例如,在所有日志文件中,所有URL都是唯一的。在这种情况下,基本上任何解决方案都需要很长时间来得出这个结论(如果它甚至达到了那么远......),甚至没有答案你的问题,因为没有前十名。

我没有可以提供的防水算法,但我会探索一种解决方案,该解决方案使用URL的哈希值的直方图而不是URL本身。这些直方图可以通过一次通过文件读取来计算,因此它可以处理任意大小的日志文件。在伪代码中,我会选择这样的东西:

  • 使用具有有限目标空间的哈希函数(比如10,000,请注意预期会发生冲突哈希值)来计算日志文件中每个项目的哈希值,并计算每个值有多少次出现。将生成的直方图传达给服务器(尽管通常可以通过将结果多播到每个其他节点来避免使用中央服务器 - 但我会坚持使用更明显的服务器方法)
  • 服务器应合并直方图并将结果传回。根据网址的分布情况,可能会有许多清晰可见的峰值,其中包含访问量最大的网址。
  • 然后,每个节点应该关注直方图中的峰值。它应该再次通过其日志文件,使用额外的哈希函数(再次使用有限的目标空间)来计算那些在其中一个峰中具有第一个哈希值的URL的新哈希直方图(要聚焦的峰的数量) on将是算法中要调整的参数,具体取决于URL的分布),并使​​用新的哈希值计算第二个直方图。结果应该传达给服务器。
  • 服务器应该再次合并结果并分析新的直方图与原始直方图。根据清晰可见的峰值,它可能已经得出关于前十个URL的两个哈希值的结论。或者它可能必须指示机器使用第二个散列函数计算更多散列值,并且可能在此之后通过另一个散列函数进行第三次散列计算。这必须继续,直到可以从直方图的集体组中得出峰值URL的哈希值是什么,然后节点可以从中识别不同的URL。

请注意,此机制需要针对算法和散列函数的几个方面进行调整和优化。它还需要服务器进行编排,以确定应该随时进行哪些计算。它可能还需要设置一些边界,以便在不能得出结论时得出结论,换句话说,当URL哈希值的“频谱”太平以至于不值得继续计算时。

如果URL中有明确的分布,这种方法应该可以正常工作。我怀疑,实际上,这个问题在这种情况下才有意义。

答案 2 :(得分:1)

预处理:每个计算机系统处理完整的日志文件,并准备唯一的URL列表,并对其进行计数。

获取热门网址:

  1. 计算每个计算机系统的URL计数
  2. 在中央系统整理流程(虚拟)
    • 以DESC顺序(即从最顶层)逐个将带有计数的URL发送到中央处理单元
    • 在中央系统整理传入的网址详情
    • 重复此操作,直到传入URL中的所有计数总和小于主列表中第十个URL的计数。 绝对肯定的重要步骤
  3. PS:您将跨系统排名前十的网址不一定按此顺序排列。要获得实际订单,您可以反向整理。对于前十名的给定URL,从dist-computers获得个人计数并形成最终订单。

答案 3 :(得分:1)

假设以下条件为真:

  • 您需要m个主机的前n个网址。
  • 您无法将文件存储在RAM中
  • 有一个主节点

我会采取以下方法:

每个节点读取文件的一部分(即MAX url,其中MAX可以是1000 url)并保持数组arr [MAX] = {url,hits}。

当一个节点从文件中读取MAX url时,它会将列表发送到主节点,并重新开始读取,直到再次达到MAX url。

当一个节点到达EOF时,他将剩余的网址列表和一个EOF标志发送给主节点。

当主节点收到网址列表时,会将其与最后一个网址列表进行比较,并生成一个新的更新网址。

当主节点从每个节点收到EOF标志并完成阅读他自己的文件时,他列表的最后一个版本的前n个url是我们正在寻找的。

<击>或者

<击>

让主人完成所有工作的另一种方法可能是:

每个节点读取其文件并存储与上面相同的数组,读数直到EOF。

当EOF时,节点会将列表的第一个网址和点击次数发送给主人。

当主人收集了每个节点的第一个网址和点击数时,它会生成一个列表。如果主节点少于n个url,它将要求节点发送第二个节点,依此类推。直到主人有n个网址排序。

答案 4 :(得分:1)

在每个节点上计算 URL 的出现次数。 然后使用分片功能将 url 分发到另一个拥有 URL 密钥的节点。现在每个节点都有唯一的键。 然后在每个节点上再次减少以获取 URL 的出现次数,然后找到前 N 个 URL。最后只将前 N 个 url 发送给主节点,主节点将在 K*N 个项目中找到前 N 个 URL,其中 K 是节点数。

Eg: K=3
N1 - > url1,url2,url3,url1,url2
N2 - > url2,url4,url1,url5,url2
N3 - > url1,url4,url3,url1,url3

第 1 步:计算每个节点中每个 url 的出现次数。

 N1 -> (url1,2),(url2,2),(url3,1)
 N2 -> (url4,1),(url2,2),(url5,1),(url1,1)
 N3 -> (url1,2),(url3,2),(url4,1)

第 2 步:分片使用哈希函数(为简单起见,将其设为 url 编号 % K)

 N1 -> (url1,2),(url1,1),(url1,2),(url4,1),(url4,1)
 N2 -> (url2,2),(url2,2),(url5,1)
 N3 -> (url3,2),(url3,1)

第 4 步:再次查找节点内每个键出现的次数。

N1 -> (url1,5),(url4,2)
N2 -> (url2,4),(url5,1)
N3 -> (url3,3)

第 5 步:仅将前 N 个发送给 master。让 N=1

Master -> (url1,5),(url2,4),(url3,3)

对结果进行排序并获得前 1 个项目,即 url1

Step 1 称为 map side reduce,这样做是为了避免 Step2 中发生的巨大 shuffle。

答案 5 :(得分:0)

以下描述是解决方案的想法。它不是伪代码 考虑一下你有一系列系统 1.每个A:收藏(系统)
   1.1)在每台计算机上运行一个daemonA,它在日志文件上探测是否有变化    1.2)当发现变化时,唤醒AnalyzerThreadA
   1.3)如果AnalyzerThreadA使用某些正则表达式找到URL,则使用count ++更新localHashMapA       (key = URL,value = count)。
2)将localHashMapA的topTen条目推送到将运行AnalyzeAll守护程序的ComputerA。

上述步骤将是每个系统的最后一步,它将topTen条目推送到主系统,例如:computerA。

3)AnalyzeAll在computerA中运行将解决重复项并更新URL的masterHashMap中的计数。

4)从masterHashMap打印topTen。