LUCENE简介
Lucene是apache软件基金会jakarta项目组的一个子项目,是一个开放源代码的全文检索引擎工具包,即它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎(英文与德文两种西方语言)。Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎。
作为一个开放源代码项目,Lucene从问世之后,引发了开放源代码社群的巨大反响,程序员们不仅使用它构建具体的全文检索应用,而且将之集成到各种系统软件中去,以及构建Web应用,甚至某些商业软件也采用了Lucene作为其内部全文检索子系统的核心。apache软件基金会的网站使用了Lucene作为全文检索的引擎,IBM的开源软件eclipse的2.1版本中也采用了Lucene作为帮助子系统的全文索引引擎,相应的IBM的商业软件Web Sphere中也采用了Lucene。Lucene以其开放源代码的特性、优异的索引结构、良好的系统架构获得了越来越多的应用。
Lucene是一个高性能、可伸缩的信息搜索(IR)库。它使你可以为你的应用程序添加索引和搜索能力。Lucene是用java实现的成熟的、免费的开源项目,是著名的Apache Jakarta大家庭的一员,并且基于在Apache软件许可 [ASF, License]。同样,Lucene是当前与近几年内非常流行的免费的Java信息搜索(IR)库。
Lucene作为一个全文检索引擎,其具有如下突出的优点:
(1)索引文件格式独立于应用平台。Lucene定义了一套以8位字节为基础的索引文件格式,使得兼容系统或者不同平台的应用能够共享建立的索引文件。
(2)在传统全文检索引擎的倒排索引的基础上,实现了分块索引,能够针对新的文件建立小文件索引,提升索引速度。然后通过与原有索引的合并,达到优化的目的。
(3)优秀的面向对象的系统架构,使得对于Lucene扩展的学习难度降低,方便扩充新功能。
(4)设计了独立于语言和文件格式的文本分析接口,索引器通过接受Token流完成索引文件的创立,用户扩展新的语言和文件格式,只需要实现文本分析的接口。
(5)已经默认实现了一套强大的查询引擎,用户无需自己编写代码即使系统可获得强大的查询能力,Lucene的查询实现中默认实现了布尔操作、模糊查询(Fuzzy Search)、分组查询等等。
面对已经存在的商业全文检索引擎,Lucene也具有相当的优势。
首先,它的开发源代码发行方式(遵守Apache Software License),在此基础上程序员不仅仅可以充分的利用Lucene所提供的强大功能,而且可以深入细致的学习到全文检索引擎制作技术和面相对象编程的实践,进而在此基础上根据应用的实际情况编写出更好的更适合当前应用的全文检索引擎。在这一点上,商业软件的灵活性远远不及Lucene。
其次,Lucene秉承了开放源代码一贯的架构优良的优势,设计了一个合理而极具扩充能力的面向对象架构,程序员可以在Lucene的基础上扩充各种功能,比如扩充中文处理能力,从文本扩充到HTML、PDF等等文本格式的处理,编写这些扩展的功能不仅仅不复杂,而且由于Lucene恰当合理的对系统设备做了程序上的抽象,扩展的功能也能轻易的达到跨平台的能力。
最后,转移到apache软件基金会后,借助于apache软件基金会的网络平台,程序员可以方便的和开发者、其它程序员交流,促成资源的共享,甚至直接获得已经编写完备的扩充功能。最后,虽然Lucene使用Java语言写成,但是开放源代码社区的程序员正在不懈的将之使用各种传统语言实现(例如.net framework),在遵守Lucene索引文件格式的基础上,使得Lucene能够运行在各种各样的平台上,系统管理员可以根据当前的平台适合的语言来合理的选择。
在简单了解Lucene的用途之后,接下来我们就开始对其进行架构分析。
一、 与架构商业周期的关系
本章所要研究的是构架商业周期中将构架与所期望的质量属性相联系的部分。图中给出了Lucene的构架商业周期。Lucene的客户是使用到全文检索的开发者,最终用户是使用开发出的软件的普通大众。Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎。
Lucene是由apache软件基金会jakarta项目组开发的,它适用于大量数据的索引及搜索。因此,它在性能方面要求很高。不仅如此,为了使更多的开发者能够更加简便的开发程序,Lucene的易用性也需要很高。值得注意的是,Lucene是一个开源项目,为了使开发者能够自己修改Lucene以充分利用其优势,Lucene必须具备很好的可修改性。
二、 需求与质量
关键字:可修改性性能易用性
为了更好的认识Lucene,了解其需求与质量,我们先来了解一下什么是全文检索系统。
全文检索系统中最为关键的部分是全文检索引擎,各种应用程序都需要建立在这个引擎之上。一个全文检索应用的优异程度,根本上由全文检索引擎来决定。因此提升全文检索引擎的效率即是我们提升全文检索应用的根本。另一个方面,一个优异的全文检索引擎,在做到效率优化的同时,还需要具有开放的体系结构,以方便程序员对整个系统进行优化改造,或者是添加原有系统没有的功能。
Lucene从本质上来说就是一种全文检索系统,但它并不是具体的实现,而是一种全文检索系统的框架。当然,Lucene的主要需求以及质量应该与全文检索系统一致。
从根本上来说,Lucene有两大块主要功能,一是文本内容经分词后索引入库,二是根据查询条件返回结果。对于文本内容分词入库这个功能,在多数情况下应该是由系统自动完成的。例如,如果要搜索一篇pdf文档,基于Lucene的全文检索系统应该首先将本篇pdf经过分词等处理后导入索引库,这样才能在读者希望进行搜索操作时快速查询到搜索结果。而对于Lucene的查询条件返回结果这一功能,多数情况下应该是人机交互的,当用户搜索某个关键词如“java”,基于Lucene的全文检索系统应该在很短的时间内反应并且返回结果。这就要求了Lucene很高的性能(performance)。
作为一种检索系统框架,Lucene并不直接提供系统的实现,而仅仅是系统框架而已。因此,为了构建一个真正可用的全文检索系统,开发人员必须熟悉Lucene的基本框架以及API,这样才能进行高效的开发。这一需求要求了Lucene要具备一种简明、方便的构架与函数接口来方便用户(即开发人员)的使用。这体现了Lucene需要很高的易用性(usability)。
不仅如此,开源是Lucene的一个重大属性。相比Google的pagerank搜索方案,Lucene必须不断改进其算法以及各种辅助措施来使得其运行更加高效,并支持多种语言等。因此,Lucene必须具备很好的可修改性(modifiability)。
三、 构架解决方案
1. Lucene的基本构架
Lucene 作为一个优秀的全文检索引擎,其系统结构具有强烈的面向对象特征。首先是定义了一个与平台无关的索引文件格式,其次通过抽象将系统的核心组成部分设计为抽象类,具体的平台实现部分设计为抽象类的实现,此外与具体平台相关的部分比如文件存储也封装为类,经过层层的面向对象式的处理,最终达成了一个低耦合高效率,容易二次开发的检索引擎系统。
Lucene的一大优势在于其开源。这样我们对于Lucene的构架分析可以直接从它的包以及云代码入手。由于Lucene的版本正在不断升级,本文我们将从其最初级的版本——lucene-1.4.3入手。
下图为Lucene-1.4.3的包图
Lucene软件包的分析
Package:
org.apache.lucene.document: 提供为分装要索引的文档所需要的类
org.apache.lucene.analysis: 对文档进行分词
org.apache.lucene.search: 对在建立好的索引上进行搜索所需要的类
org.apache.lucene.index: 提供一些类来协助创建索引以及对创建好的索引进行更新
org.apache.lucene.store: 数据存储管理,包括一些底层的I/O操作
org.apache.lucene.queryParser: 作为语法解析器存在
org.apache.lucene.util: 一些公用类
这些包经过一定的关系组合最终构造出整个Lucene框架。
不管是怎样的框架,最终的目的还是为了整个系统的功能服务。只有良好的框架而没能实现具体功能的系统还是一个差的系统。因此我们现在从整个系统的运作方式上来看Lucene的构架。
Lucene功能实现分析
我们已经知道,Lucene其实只是一个软件库或者说是工具包,它并不具备搜索应用程序的完整特征。但是无论如何使用Lucene开发系统就必须使用Lucene的框架。下图解释了一个集成Lucene的典型应用程序的结构。
很明显,Lucene可以被视为应用程序之下的一个接口层,它提供了两个主要的接口:search与index。
在需求与分析中,我们已经介绍了Lucene有两大块主要功能,一是文本内容经分词后索引入库,二是根据查询条件返回结果。将Lucene的功能实现分配按照实现模块来划分,我们得到以下功能逻辑视图:
从上图中,查询与入库两大功能的不同之处主要在于其接口。查询操作必须调用查询分析器与查询器,而入库操作必须调用语言分析器。这两大功能的底层模块是一致的。这让我们很容易想到运用分层结构来设计整个系统,因为分层结构能够将整个系统分为多个层次,清晰地将系统的接口、中间层、底层分离开来。
Lucene系统结构的分层视图如下:
从图中我们清楚的看到,Lucene 的系统由基础结构封装、索引核心、对外接口三大部分组成。其中直接操作索引文件的索引核心又是系统的重点。Lucene 的将所有源码分为了7 个模块(在java 语言中以包即package 来表示),各个模块所属的系统部分也如上图所示。需要说明的是org.apache.lucene.queryPaser 是做为org.apache.lucene.search 的语法解析器存在,不被系统之外实际调用,因此这里没有当作对外接口看待,而是将之独立出来。从面向对象的观点来考察,Lucene 应用了最基本的一条程序设计准则:引入额外的抽象层以降低耦合性。首先,引入对索引文件的操作org.apache.lucene.store 的封装,然后将索引部分的实现建立在(org.apache.lucene.index)其之上,完成对索引核心的抽象。在索引核心的基础上开始设计对外的接口org.apache.lucene.search 与org.apache.lucene.analysis。在每一个局部细节上,比如某些常用的数据结构与算法上,Lucene 也充分的应用了这一条准则。在高度的面向对象理论的支撑下,使得Lucene的实现容易理解,易于扩展。
Lucene 在系统结构上的另一个特点表现为其引入了传统的客户端服务器结构以外的应用结构。Lucene 可以作为一个运行库被包含进入应用本身中去,而不是作为一个单独的索引服务器存在。这自然和Lucene 开放源代码的特征分不开,但是也体现了Lucene在编写上的本来意图:提供一个全文索引引擎的架构,而不是实现。
2. Lucene质量属性的实现
在大概分析清楚Lucene构架中各个模块的关系之后,我们将研究Lucene怎样通过其构架来实现系统的高性能、高易用性与高可修改性。接下来,我们便直接从Lucene的源代码入手,对三种质量属性依次进行分析。
可修改性
可修改性的战术包括以下几种:
局部化修改
局部化修改就是要在设计起见为模块分配责任,把语气的变更限制在一定的范围内。
Lucene选用了维持语义一致性、预期期望变更、泛化模块以及抽象通用服务这四种战术实现了局部化修改。
首先要再次重申的是,Lucene各个模块(即各个Java包)的责任是非常明确的。七个模块各司其职,并能够协同工作,不需要过多地依赖其他模块。这种方法满足了维持语义一致性战术。
在每个模块内部的子模块中,分工仍然十分明确,且不过多依赖其他模块。
以org.apache.lucene.analysis包为例。下图给出了在该包中几个重要的类的关系视图。
Analyzer类为抽象类,用于对文本内容的切分词规则。SimpleAnalyzer继承了Analyzer类。
分词会返回一个TokenStream对象,TokenStream中有方法next()以取到下一个词。
可见,各个子模块(即类)的分工明确。不仅如此,Lucene将每次变更的影响减到最小。
以SimpleAnalyzer类的源代码为例。
在这个类中仅涉及到了TokenStream与Tokenizer类。TokenStream与Tokenizer中的方法仅有三个,且TokenStream为Tokenizer的父类。如果要修改切分词规则那么仅需去考虑Tokenizer类中的方法。这样做就使得每次变更的影响减到最小,满足了预期期望变更战术。
Lucene还满足了抽象通用服务战术。在org.apache.lucene.analysis包中,Token类即为一个通用服务,因为它的作用是处理分词。在整个包中,各个类(或子模块)都需要处理分词,这样使用了Token类之后,各个类公用这一个类,假设要修改分词的处理方法,只需去修改Token类而避免了其他类的修改。
在Lucene中,泛化模块也是一个很重要的战术。例如,在org.apache.lucene.analysis包中Tokenizer跟TokenFilter即泛化了TokenStream。
防止连锁反应
防止连锁反应就是要避免修改一个模块时直接影响到另一个模块的情况。
Lucene选用了信息隐藏与维持现有接口这两种战术实现了防止连锁反应。
Lucene的封装性很好,因为各个模块职责已被分解到最小。
在开发人员修改Lucene的过程中,并不会去修改Lucene的现有接口。这一方面可以在各个版本的更新交替中体现。Lucene 的最早版本为1.4.3,而现在的版本已经是3.0.3。尽管版本升级很多,但是主要的接口都没有改变,只是扩充了一些方法或者子模块而已。
这两种战术均为构架设计的常用战术,在这里就不再过多赘述。
易用性
易用性的战术包括以下几种:
Lucene选用了分离用户接口这一种战术实现易用性。
在之前的Lucene架构中我们已经分析过,Lucene采用的是分层模型。分层模型的好处就是将用户接口与系统的其余部分分离出来。因此,实际在开发者运用Lucene的过程中,只需要注意高层的模块(即org.apache.lucene.search与org.apache.lucene.analysis)和核心模块org.apache.lucene.index即可。其余模块的使用并不频繁。这样就大大简化了开发的难度。
性能
性能的战术包括以下几种:
Lucene选用了提高计算效率、引入并发与维持数据或计算的多个副本这三种战术实现高性能。
在提高计算效率方面,Lucene选用了许多优秀的算法。在建立索引时Lucene选用倒排索引的方式建立索引。倒排索引结构比顺序索引结构或者其他索引结构有很大的优势。例如,假设要查询单词 “live”,Lucene先对词典二元查找、找到该词,通过指向频率文件的指针读出所有文章号,然后返回结果。词典通常非常小,因而,整个过程的时间是毫秒级的。而用普通的顺序匹配算法,不建索引,而是对所有文章的内容进行字符串匹配,这个过程将会相当缓慢,当文章数目很大时,时间往往是无法忍受的。不仅如此,倒排索引不需要太大的内存空间,大大提高了效率。
除了倒排索引,Lucene还有一些其他的算法,例如评分算法、快速排序等。
在资源管理方面,Lucene是可以引入并发,即引入多线程工作。为了更好的使用多线程,Lucene的管件类是线程级安全的。Lucene还保证了多进程的使用。其规则如下:
1. 多个索引搜索器能同时读取Lucene索引文件;
2. 一个索引写者或读者能在搜索进行时编辑Lucene索引文件;
3. 多个索引写者或读者会试着同时编辑Lucene索引文件。
同样是在资源管理方面,Lucene使用了维持数据或计算的多个副本战术。高速缓存是不同速度的存储库或单独的存储库上复制数据的战术,目的是减少争用。
Lucene在建立Dictionary时使用了这一战术。
Dictionary在Org.apache.lucene.store包中,该包的内部类结构如下:
你也可以把索引存在一个Lucene Directory对象里面。Lucene Directory是对Java filesystem类的抽象。用RAM-based Directory类在内存中维护文件缓存以获得高性能。
四、 小结
Lucene的关键质量属性包括:可修改性、易用性、性能。
Lucene的架构选取的战术包括:
可修改性
维持语义的一致性
预期期望变更
泛化模块
信息隐藏
维持现有接口
易用性
分离用户接口
性能
提高计算效率
引入并发
维持多个副本
五、 可进一步参阅的文献
1. 《Lucene in action》作者:Otis Gospodnetic / Erik Hatcher
2. http://www.chedong.com/tech/lucene.html 车东的博客
3. http://www.ibm.com/developerworks/cn/java/j-lo-lucene1/ IBM关于Lucene的讲解
4. http://baike.baidu.com/view/371811.htm 百度百科
5. http://lucene.apache.org/ Lucene官方网站
6. 其他互联网资源