`
zhangzhenjj
  • 浏览: 27210 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

一、关于爬虫的一点想法

阅读更多
                    
                      
关于爬虫的一点想法(一)


小弟毕业后参加工作的过程中在iteye汲取了很多营养,一直想写点什么回报这种无私共享的精神,之前策划了一篇文章《论工作的那些事》由于比较长,还需等一段时间.

关于爬虫的一点想法:
爬虫的大致业务流程是:下载、解析、存储,不断重复(任务调度),那么想提高爬虫的运行效率是不是可以从以下几个方面着手.这个系统的上下文大概是这样的,网络、服务器(cpu、cache、disk)、爬虫程序
1、爬虫对网络带宽要求较高,在有限的资源下,我们能做的就是提高带宽的利用率,那么这里的话我们就可以尽量增加同一时刻执行下载线程的数量(不是一味的多就好),但是由于linux系统的特性,所有对其的调用都是文件的操作(大概是这意思,之前在一本书上看到的,非专业),linux对文件打开数量有限制,即使可以修改参数,所以对于一个大规模的爬虫增加服务器是必要的也是必然的,这样就涉及分布式,涉及分布式就涉及任务的调度,实现一个无延迟的任务调度需要思考,在数据结构上可能disruptor比较适合,另外jdk的blockqueue(需要理解内部机制)也不错。DNS解析耗费大量不必要的时间,可以在本地增加DNS缓冲池,之前测过一般我们要访问一台www服务器一般要经过2~3个路由器,更远的可能3~5个。linux下可以使用tracert命令进行路由的跟踪。
2、提高cache的工作效率。Java不同于C可以为任务指定cpu执行,这里有个非常关键的问题,就是CPU伪共享问题,现在pc都有了三级缓存,但大多树都是cpu共享的,这样就产生了伪共享问题,例如cpu1访问cache段C1的A字节,同时CPU2访问cache段C1中的B字节,实际上A和B毫无关系,但是由于CPU寻址的问题,他每次都是加载16字节,所以就造成CPU2需要重新去内存加载B的内容,而B没有变更,造成了无形的效率问题,redis中有这个的解决方案,就是在自己的变量前后各添加16字节的byte数组。
3、disk问题。爬虫服务器没必要弄个ssd,基本都是机械硬盘,机械硬盘的特点是顺序读写比较好,整个数据操作的过程中磁头寻道的时间是主导,所以有效减少磁盘寻道时间会比较有效,这里有两种解决方案:A,优化程序尽量按磁盘块大小进行操作,当然jdk已经考虑到这个问题,buffer大小默认好像是4K ,这个和磁盘块大小是一致的,我也写过简单程序测试过。
B,添加raid卡,不过这个成本好像比较高。添加了raid卡的一个磁盘阵列,具有多个磁头,理论上相当于磁盘效率正比例提高了,因为同一时刻可以多个磁头同时工作。
C,尽量顺序操作。虽然现在linux对磁盘的随机读写有优化,例如,预读策略,但这应该是最后一个关卡,我们可以通过程序尽量避免随机操作。
4、cpu+数据总线+磁盘的工作方式,这个之前在《大话处理器》上看到的,没见过类似的具体应用。cpu读取数据的过程cpu是不参与的,而是通过数据总线,在数据从磁盘加载到内存中间的操作是由数据总线完成的,这段时间cpu是空闲的,是否有某些方法可以充分利用这段时间。这里就跟操作系统的cpu调度策略有关了。
5、多线程的合理运用,不是开的越多越好。线程主要解决是阻塞问题,不论是锁还是资源等待都会造成这个问题,下面从这两个方面写。
A,资源等待。在爬虫系统中大量运行时间都花在网络资源获取上,这个可能是由于带宽问题或服务器超出了打开最大文件数限制引起(httpclient运用不好就会造成这个bug),之前看过一篇文章,大概意思是系统要运行一个任务T,T运行时间=T资源等待时间+T处理时间,程序为这个任务开的任务数同CPU有个比例函数关系。大概是线程数=(T处理时间/T等待时间+1)*CPU内核数(可能不准确)。
B,锁问题。锁是影响程序执行性能的一大问题,能不用就不用,jdk也自带了current包。举个例子,disruptor是一个无锁的高效队列,非常适合生产者/消费者问题,并且支持消费者层级关系,即消费者A处理完由消费者B处理。数据结构模型也不复杂。
6、对象缓存运用,apache有开源实现。java是个面向对象的语言,现在开发程序更是无所不用其及,想法用面向对象的思想,一切都是对象,有利有避,开发/维护简单了,运行效率就不好说了,大量的GC不但会影响程序性能,同时其自身也有个bug----GC回收不及时,这个平常可能很少见,但是对于淘宝那样的高并发,他们就曾经遇到过这个问题,最后是由他们的jvm团队修改jvm代码解决的。apache这个开源的实现包不大,使用也比较简单,简单的说有一个对象池,每次对特定对象的操作都需要找这个池去要,使用完归还,途径这个池时可以进行对对象的处理,例如初始化、挂起、激活、销毁等动作。对于爬虫这样大量对象的操作我认为对象缓存是必要的。我们的微薄采集程序用了,我们是采用api调用的方式处理的,在每次通过api获取数据时都可以指定页数,例如我要获取JERRY的新浪微薄数据,因为jerry刷微薄都是在一个很短的时间段完成的,大部分时间没有更新,所以每次只请求很少的记录条数,减少网络传输的数据,我们将api的请求参数封装成一个对象Query,请求是调用toString()即可,这个方法用反射将query对象的成员变量和值组成url形式返回,并且进行了缓存,通过去重机制判断是否有下一页,如果有下一页就对这个query进行一个深拷贝,形成一个子任务返回给调度器,并且设置启动时间为立即。
7、NIO的运用,最新的httpclient包已经支持NIO。
8、JVM参数配置。我们一般都将jvm设置成server模式,虽然启动会慢,但有一个显著的性能提升,尤其明显的是对方法的调用提高很多很多,当然jvm自身也会判断该工作在client模式还是server模式。jvm还有很多参数,一般我们都关心内存,对于jvm的内存模型,也许有必要深入研究,尤其是几个区的分配,避免自动扩增,之前也看了一遍《深入理解JVM》,但基本没应用到工作也没记住多少。
9、JDK版本的选择,如果你对内存的大小没太大要求,还是用32位的比较好,如果需要大量内存那只能用64位的了,对于JDK的32位和64位有过这么一个测试,前者运行效率全面超过后者。JDK64位有个自动补全机制。
10、nosql的运用。heritrix是个特别好的单机开源爬虫框架,内部有很多好的思想,其内部运用了BDB做为其内存数据库。现在key-value类型的nosql比较多,redis比较好用,之前看memlink正在研发,是基于redis的,官方说实现了完全的读写分离。


非常愿意和大家一起讨论,谢谢!!!!
分享到:
评论
1 楼 SE_XiaoFeng 2013-07-30  
表示只看懂了一半.

相关推荐

Global site tag (gtag.js) - Google Analytics