简单的说,Berkeley DB就是一个HashTable,它能够按“key/value”方式来保存数据。它是由美国Sleepycat公司开发的一套开放源代码的嵌入式数据库,它为应用程序提供可伸缩的、高性能的、有事务保护功能的数据管理服务。
那么,为什么不使用一个传统的关系型数据库呢?这是因为当使用BerkeleyDB时,数据库和应用程序在相同的地址空间中运行,所以数据库操作不需要进程间的通讯。然而,当使用传统关系型数据库时,就需要在一台机器的不同进程间或在网络中不同机器间进行进程通讯,这样所花费的开销,要远远大于函数调用的开销。 另外,Berkeley DB中的所有操作都使用一组API接口。因此,不需要对某种查询语言(比如SQL)进行解析,也不用生成执行计划,这就大大提高了运行效率。
当然,做为一个数据库,最重要的功能就是事务的支持,Berkeley DB中的事务子系统就是用来为其提供事务支持的。它允许把一组对数据库的修改看作一个原子单位,这组操作要么全做,要么全不做。在默认的情况下,系统将提供严格的ACID事务属性,但是应用程序可以选择不使用系统所作的隔离保证。该子系统使用两段锁技术和先写日志策略来保证数据的正确性和一致性。这种事务的支持就要比简单的HashTable中的Synchronize要更加强大。
注意:在Heritrix中,使用的是Berkeley DB的Java版本,这种版本专门为Java语言做了优化,提供了Java的API接口以供开发者使用。
为什么Heritrix中要用到Berkeley DB呢?这就需要再回过头来看一下Frontier了。
当一个链接被处理后,也即经过处理器链后,会生成很多新的链接,这些新的链接需要被Frontier的一schedule方法加入到队列中继续处理。但是,在将这些新链接加入到队列之前,要首先做一个检查,即alreadyIncluded这个HashMap中,查看当前要加入到队列中的链接是否在先前已经被处理过了。 当使用HashMap来存储那些已经被处理过的链接时,HashMap中的key为url,而value则为一个对url封装后的对象。很显然的,这里有几个问题。
1 对这个HashMap的读取是多线程的,因为每个线程都需要访问这个HashMap,以决定当前要加入链接是否已经存在过了。
2 对这个HashMap的写入是多线程的,每个线程在处理完毕后,都会访问这个HashMap,以写入最新处理的链接。
3这个HashMap的容量可能很大,可以试想,一次在广域网范围上的网页抓取,可能会涉及到上十亿个URL地址,这种地址包括网页、图片、文件、多媒体对象等,所以,不可能将这么大一张表完全的置放于内存中。
综合考虑以上3点,仅用一个HashMap来保存所有的链接,显然已经不能满足“大数据量,多并发”这样的要求。因此,需要寻找一个替代的工具来解决问题。Heritrix中的BdbFrontier就采用了Berkeley DB,来解决这种URL存放的问题。事实上,BdbFrontier就是Berkeley DB Frontier的简称。
为了在BdbFrontier中使用Berkeley DB,Heritrix本身构造了一系列的类来帮助实现这个功能。这些类如下:
1 BdbFrontier
2BdbMultipleWorkQueues
3BdbWorkQueue
4BdbUriUniqFilter
上述的4个类,都以Bdb3个字母开头,这表明它们都是使用到了Berkeley DB的功能。其中:
(1)BdbMultipleWorkQueues代表了一组链接队列,这些队列有各自不同的key。这样,由Key和链接队列可以形成一个“Key/Value”对,也就成为了Berkeley DB里的一条记录(DatabaseEntry)
(2)BdbWorkQueue代表了一个基于Berkeley DB的队列,与BdbMutipleWorkQueues所不同的是,该队列中的所有的链接都具有相同的键值。事实上,BdbWorkQueue只是对BdbMultipleWorkQueues的封装,在构造一个BdbWorkQueue时,需传入一个健值,以此做为该Queue在数据库中的标识。事实上,在工作线程从Frontier中取出链接时,Heritrix总是先取出整个BdbWorkQueue,再从中取出第一个链接,然后将当前这个BdbWorkQueue置入一个线程安全的同步容器内,等待线程处理完毕后才将该Queue释放,以便该Queue内
的其他URI可以继续被处理。
(3)BdbUriUniqFilter是一个过滤器,从名称上就能知道,它是专门用来过滤当前要进入等待队列的链接对象是否已经被抓取过。很显然,在BdbUriUniqFilter内部嵌入了一个Berkeley DB数据库用于存储所有的被抓取过的链接。它对外提供了 public void add(String key, CandidateURI value) 这样的接口,以供Frontier调用。当然,若是参数的CandidateURI已经存在于数据库中了,则该方法会禁止它加入到等待队列中去。
(4)BdbFrontier就是Heritrix中使用了Berkeley DB的链接制造工厂。它主要使用BdbUriUniqFilter,做为其判断当前要进入等待队列的链接对象是否已经被抓取过。同时,它还使用了BdbMultipleWorkQueues来做为所有等待处理的URI的容器。这些URI根据各自的内容会生成一个Hash值成为它们所在队列的键值。
在Heritrix1.10的版本中,可以说BdbFrontier是惟一一个具有实用意义的链接制造工厂了。虽然Heritrix还提供了另外两个Frontier:
org.archive.crawler.frontier.DomainSensitiveFrontier
org.archive.crawler.frontier.AdaptiveRevisitFrontier
但是,DomainSensitiveFrontier已经被废弃不再推荐使用了。而AdaptiveRevisitFrontier的算法是不管遇到什么新链接,都义无反顾的再次抓取,这显然是一种很落后的算法。因此,了解BdbFrontier的实现原理,对于更好的了解Heritrix对链接的处理有实际意义。
OK!