甲乙小朋友的房子

甲乙小朋友很笨,但甲乙小朋友不会放弃

0%

系统设计-爬虫系统与搜索建议系统

大纲

  • 设计一个web爬虫
  • 线程安全的生产者-消费者模式
  • 搜索建议系统

爬虫

搜索引擎如何爬取网页呢?也就是我们想获取如下所示的表:

那么想要用程序完成这个目标,我们首先要知道一个事实,也就是互联网上的内容其实是互相索引的,也就是就像如下的大网一样:

假设一共有1trillion个网页,而且至少一周更新一次。那么每秒要爬取1.6m网页。每页大概10k,那么些网页存储下来大概需要10p的存储空间。

单线程爬虫

一般来说,爬虫是用BFS进行爬取的。对于单线程的爬虫来说,就是搞一个队列:

生产者-消费者模型

也就是生产者一直往里怼任务,消费者拿任务。中间有一个Buffer(原因是存取速度不一样)。

那么回头看看我们的爬虫,其实URL extractor就是生产者,而Web page loader就是消费者。

但是单线程有一个问题,就是慢的很。解决——多线程爬虫

多线程爬虫

这些线程共享同一个URL队列,那么这个队列就要加锁。(DFS没有办法实现这个多线程,因此用BFS)

需要注意的是,争取共享资源,就要考虑以下三个机制:

  • sleep —— 就是随机睡一会,但问题在于无法立马知道资源可以使用了
  • 信号量(实现互斥)—— 相当于所有的线程都在等着,然后信号量通知他们能用的时候,就抢着占用资源
  • semaphore —— 就像门上挂上五把钥匙一样,能够解决抢占资源的问题

单线程 --> 多线程:

  • 线程来回切换(context switch)还是有花费(需要保存线程执行状态,还需要切换二级缓存等)的,因此线程不能太多
  • 线程端口数目是有限的(TCP/IP协议中,端口只有2个字节,也就是65536个端口)

因此我们可以改进一下:

分布式爬虫

分布式爬虫虽然解决了线程不能太多的问题,但是又带来了一个问题:URL队列在内存中放不下了(假设1trillion,差不多要40T的内存)!这是不行的。那么我们考虑一下存在硬盘里,也就是数据库里。

最要命的限制是:没有办法控制网页的顺序和优先级啊!

解决:给数据库加入一个优先级的列,再加一个频率的列

每一行是一个任务:

  • ID,URL
  • state:是否正在运行的状态(即能去重,也能防止重复运算)
  • priority:优先级
  • available_time:控制抓取频率,也就是这个时刻之后再进行抓取。如果本次有更新,那么就把时间设置地更近一些;如果本次没有更新,那么就把时间设置地更远一些

  • webPageStorage : 分布式存储,存储爬取的东西
  • 爬虫
  • Task table : 就是上面的任务表

总结

场景:多少网页?频率?有多大?

服务:爬虫、Task Service、存储系统

存储:用db存储task,一个超大的BigTable存储web pages

遇到的问题

task table 最终会非常大!1 trillion 个task,而且会越来越大

解决——拆表,加速select

那么拆表就有一个细节需要注意,需要一个scheduler,用来安排去哪里要数据。

网页抓取频率问题

如何控制抓取频率呢?有一个暴力的方法:

  • 如果本次抓了有更新,那就把下次抓取的时间提前一半
  • 如果本次抓了没更新,就把下次抓取的时间往后移2倍

如何处理死循环问题?

有些网站是互相指向的,比如sina.com。而且所有的URL都是与sina.com差不多的。这样就会使用大量的资源去抓取这种巨型网站,太耗费资源了。对于那些小博客就不公平。

解决办法:单位时间对同一个网站,不要分配过多的计算资源就好啦。

Typeahead

Google Type ahead ——

我们以google Suggesion为例

场景

  • 日活跃:500m
  • 搜索量:4 x 6 x 500m = 12b (每人搜索6次,输入4个单词 = 使用了typeahead四次)
  • QPS : 12b / 86400 = 138k
  • Peak QPS = QPS X 2 = 276k

服务

要求:敲下一个字母,就赶紧返回热门词才行

  • QuerySerivce : 完成typeahead行为
  • DataCollectionService : 收集数据、将热门词数据提供给QueryService

影响QueryService的执行时间的最关键因素就是数据结构,其实用Trie树就够了。这里不做过多的介绍。

DataCollectionService对Trie的更新就是个问题。当然不可以直接更新线上Memory中的索引。解决方式,就是在另一台机器上进行更新,另一台机器上的Trie跟这台机器磁盘上的Trie一样的。那就在另一台机器上先加载Trie树到内存,然后更新,再写入硬盘。再同步过去。

Trie树一台机器的内存存不下咋办?

分机器存哇。

那么怎么分呢?

  • 按照字母分? 会存在严重的不平衡问题,因为有的字母的词超多。
  • 分布式存:

存储