利用Xapian构建自己的搜索引擎:Database

2022-04-27 00:00:00 创建 修改 数据 操作 文件


       在Xapian1.0之前,是使用quartz作为database文件格式的,不过自从1.0之后,便改用Flint作为database的文件格式了。有时候,我们会将database称为“索引”,在Xapian中,索引通常比被索引的documents还要多,这表示Xapian做一个信息检索系统比做一个信息存储系统更适合。

   Database的存储结构

Xapian的database是所有用于检索的信息表的集合,以下的表是必需的:

l         posting list table 保存了被每一个term索引的document,实际上保存的应该是document在database中的Id,此Id是的。

l         record table 保存了每一个document所关联的data,data不能通过query检索,只能通过document来获取。

l         term list table 保存了索引每个document的所有的term。

以下的表是可选的,即当有以下的类型的数据需要被存储的时候才会出现(在1.0.1以前,position和value表就算是没有数据的时候也会被创建,而spelling和synonym表是1.0.2后才出现的)。

l         position list table 保存了每一个Term出现在每一个document中的位置

l         value table 保存了每一个document的values,values是用作保存、排序或其它作用的。

l         spelling table 保存了拼写纠正的数据。

l         synonym table 保存术语的字典,例如NBA、C#或C++等。

以上的每一个集合是保存在独立的文件中,以便允许管理员查看其中的数据。刚刚说了,有一些表不是必需的,例如当您不需要词组搜索的时候,没必要存储任何的postionlist信息。

如果你看过Xapian的database,你会发现以上的每一个表其实是使用了2到3个文件的,如果您正在使用“flint”作为database的存储格式,那么termlist表会被存储为以下三个文件“termlist.baseA”、“termlist.baseB”、“termlist.dB”。在这些文件中,其实只有”.db”文件存储了真实的数据,“.baseA”和“baseB”文件是用作跟踪如果于“.dB”文件中查找数据。通常只会出现一个“.baseA”文件和一个“.baseB”文件。

在前一篇《利用Xapian构建自己的搜索引擎:简介》中提到过,Xapian现在的版本默认是使用flint作为存储系统,“.dB”文件是以块的形式来存储,默认每块是8K,块是用作信息头,如果使用UltraEdit等二进制查看工具,会发现所有“.dB”文件的前三个字节都是0x00。因此,当“.dB”中仅有一条数据的时候整个文件也会有16KB时,切莫大惊小怪。

改变“.dB”文件的默认块大小会导致性能变化,但结果很难说是好是坏,因为这是跟所承载的硬件平台与操作系统平台有关的。一般来说,B树的分支因子(即每个结点能容纳的关键字的数量)越大,B树的查找性能就越强;但由于通常情况下,B树的结点都是存储在存储系统(例如硬盘/磁带)中,每次访问某个结点都会将整个结点由存储系统读入到内存中,这是一个博弈的过程:假设一棵数据量很大的B树,将B树的分支因子设到很大,这棵B树会长得很矮,从理论上来说查找性能可能很高。但这样就带来了一个弊端,每个结点所占的内存非常多,如果在一个并发访问量很大的IR系统中采用这种方式的话所使用的内存必定是非常可观的。因此在调整“.dB”文件的默认块大小的时候一定要充分考虑cpu体系和操作系统平台,以便调整到佳性能。

原子性修改

Xapian能保证对database的所有修改都是原子性的,这意味着:

l         从一个独立的进程(或一个独立的database对象在同一个进程)角度来看,在读取数据库的时候,直到修改成功提交,所有对数据库的修改都是不可见的。

l         Database在硬盘中的状态始终是保持一致的。

l         如果在修改的过程中系统发生中断,只要硬件不发生故障(硬盘损坏),就算电源被切断,database应该总是被还原到有效的状态。

提交一个修改需要几次的系统调用,以便使所有缓存的修改能刷新到硬盘上,这样能确保就算系统在任何时候发生错误,database也能处于一致的状态。当然,这样相对于说会慢了一点(因为系统已经准备好往硬盘上写数据了),因此将几次的修改组合在一起往硬盘上写会有一定的性能提升。

多个修改操作可以显式地组合在一个事务中,如果一个应用程序不显式地使用事务来保护修改操作,Xapian会将这些修改组合在一个事务中,然后批量进行修改。请注意,Xapian现在暂时还不能跨database进行事务操作。

如果要想迅速地生成非常大的database,请使用“DANGEROUS”关键字搜索Xapian的邮件列表,其提供了可以重新编译Xapian而不采用原子性修改的方法,这功能已经不再整合在xapian的标准版本中了。

Single writer, multiple reader

Xapian实现了“单写多读”的模式,这意味着任何时候,同一时刻只允许一个对象可以修改database,但允许多个对象可以读取database。

在*nix系统下,Xapian使用“lock-files”强制约束来实现此模式,在一个flint database中,每一个Xapian的database目录包含了一个名为“flintlock”的文件以作锁定用途。此文件总在存在于database的目录里,当database被打开用作写入的时候,此文件会被fcntl()方法锁定。每一个WritableDatabase打开的时候,都会产生一个子进程以便进行锁定操作。如果某个database写入器(一般是指WritableDatabase)还没有机会执行释放锁的清除操作便退出了(例如这个WritableDatabase所在的应用程被杀死了),fcntl()产生的锁会自动被操作系统释放。

在Microsoft Windows下,使用的是另一种锁定的技术,此技术不需要产生了子进程来进行锁定操作,但同理,当写入器退出时,操作系统依然会自动释放锁。熟悉Windows机制的朋友知道,Windows是使用文件句柄来操作文件的,而文件句柄是属于内核资源的一种,在任何情况下,Windows都能保证应用程序在退出时能释放所有的资源。

网络文件系统

Xapian现在可以工作在一个网络文件系统中,但存在着大量的潜在问题。因此建议在部署前要大量地在特定的网络中测试。

请注意,Xapian是非常依赖I/O操作的,除非处于一个性能非常的网络,在一个网络文件系统中进行操作会相对的慢一点。

Xapian需要可以在database的目录里创建一个lock file,在某些网络文件系统中(例如NFS),这需要一个锁定的守护进程在运行,也就是上面所提到的子进程。

 

创建database

       说了一堆的理论,下面我们来实战创建一个database。Xapian里的所有类都处于Xapian这个命名空间里,Xapian::Database是所有Database的基类,实际上,它只有一个子类,那就是WritableDatabase。从面向对象的角度来看,这两个类设计得非常好,Xapian::Database拥有大部分只读或内存操作的方法,而Xapian::WritableDatabase则拥有事务操作,刷新数据到硬盘等方法。

       有几种创建Database的方法:

l         Flint 如果你是使用远程后端(指网络文件系统),请使用Xapian::Flint::open()方法来创建database。使用此方法你能得到更多的控制,例如创建只读的database,或创建可写的database。

l         Auto auto并不是一种database格式,你可以创建一个“database存根”文件,此文件能列出一到多个database的路径,这些路径可以作为Xapian::Database的构造函数的参数,从而被自动检测是哪种类型的database。还有,如果将一个文件路径名称而非目录名称作为参数传入到Xapian::Database的构造函数中,Xapian::Database会认为你传入了一个“database存根”文件;当然,你也可以使用Xapian::Auto::open_stub()来显式打开一个存根文件。上面说的可能有点绕口,“database存根文件”的格式是每个database一行,例如:

remote localhost:23876
flint /var/spool/xapian/webindex

这下该明白了。

l         Inmemory 还可以创建内存databse,这种类型的database是保存在内存中。请注意,通过Xapian::InMemory::open()返回的类型是WritableDatabase,这意味着这是一个可刷新到硬盘上的database,它初是为测试之用,但在建立临时的小数据库也可能是有用的。

实际上,创建一个database还有更通用的方法,例如通过将一个database所在的完整目录作为一个字符串传入到Xapian::Database的构造函数中实例化一个对象后,即可得到一个只读的database。而Xapian::WritableDatabase则复杂一点,除了要传入database的路径外,还需要设定如何打开database。有以下几个参数:

l         Xapian::DB_CREATE_OR_OPEN 打开以便读写,如果不存在则创建。

l         Xapian::DB_CREATE 总是创建新的database,如果存在则失败。

l         Xapian::DB_CREATE_OR_OVERWRITE 如果database存在的话则覆盖之,如果不存在则创建。

l         Xapian::DB_OPEN 打开以便读写,如果不存在则失败。

成功打开一个datababse是Xapian所有后续操作如检索,写入的基础。你甚至可以将通过add_database()方法则多个database组合在一起访问;如果想将database刷新到硬盘中,则执行flush()方法则可。后,如果不想使用database了,将database对象销毁即可。

小结

在这一章里,似乎并没有多少具体的操作,但database是Xapian的存储系统,在Xapian所有操作的基础,只有清楚明白了Xapian的存储方式才能更好更高效地构造自己的搜索引擎。同时,如果您之前并没有对大型的文件存储系统有所了解的话,这篇文件可以多多少少带给您一些启示。在下一章里,我会继续介绍Document和Term等Xapian的组成部分。

相关文章