这是一个面试问题。假设有几台计算机,每台计算机都保存一个非常大的访问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中并通过网络复制它们。据我了解,重要的是使计算并行并使用所有给定的计算机。
你会如何解决它?
答案 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本身。这些直方图可以通过一次通过文件读取来计算,因此它可以处理任意大小的日志文件。在伪代码中,我会选择这样的东西:
请注意,此机制需要针对算法和散列函数的几个方面进行调整和优化。它还需要服务器进行编排,以确定应该随时进行哪些计算。它可能还需要设置一些边界,以便在不能得出结论时得出结论,换句话说,当URL哈希值的“频谱”太平以至于不值得继续计算时。
如果URL中有明确的分布,这种方法应该可以正常工作。我怀疑,实际上,这个问题在这种情况下才有意义。
答案 2 :(得分:1)
预处理:每个计算机系统处理完整的日志文件,并准备唯一的URL列表,并对其进行计数。
获取热门网址:
PS:您将跨系统排名前十的网址不一定按此顺序排列。要获得实际订单,您可以反向整理。对于前十名的给定URL,从dist-computers获得个人计数并形成最终订单。
答案 3 :(得分:1)
假设以下条件为真:
我会采取以下方法:
每个节点读取文件的一部分(即MAX url,其中MAX可以是1000 url)并保持数组arr [MAX] = {url,hits}。
当一个节点从文件中读取MAX url时,它会将列表发送到主节点,并重新开始读取,直到再次达到MAX url。
当一个节点到达EOF时,他将剩余的网址列表和一个EOF标志发送给主节点。
当主节点收到网址列表时,会将其与最后一个网址列表进行比较,并生成一个新的更新网址。
当主节点从每个节点收到EOF标志并完成阅读他自己的文件时,他列表的最后一个版本的前n个url是我们正在寻找的。 p>
<击>或者击>
<击>让主人完成所有工作的另一种方法可能是:
每个节点读取其文件并存储与上面相同的数组,读数直到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。