Lucene简介数据库
Lucene中,以document的形式做为搜索的主体。document由fieldName和fieldValue所组成,每一个fieldValue又能够由一个或多个term元素来组成。基于不一样的分词及索引规则,可用于搜索fieldValue的term少于组成fieldValue的term。Lucene的搜索基于反向索引,包含着可用于搜索document的field信息。经过Lucene,能够正向查找document,以便了解其包含哪些field信息;也能够经过反向索引,经过搜索字段的term,来查询包含该term的document。后端
[ 图1 ] Lucene整体架构缓存
由图1所示,IndexSearcher实现了搜索的逻辑,IndexWriter实现了文档的插入与反向索引的创建,IndexReader由IndexSearcher调用以便读取索引的内容。IndexReader和IndexWriter都依赖于抽象类Directory,Directory提供操做索引数据及的API。架构
标准的Lucene是基于文件系统和基于内存的。负载均衡
标准基于文件系统的后端的缺点在于,随着索引增长性能会降低,人们使用了各类不一样的技术来解决这个问题,包括负载均衡和索引分片(index sharding,在多个Lucene实例之间切分索引)。尽管分片功能很强大,但它让整体的实现架构变得更复杂,而且须要大量对指望文档的预测知识,才能对Lucene索引进行合适地分片。另外一方面,在大数据量的状况下,segment的合并花销巨大;频繁的update数据将使得Lucene对Disk io产生巨大的影响。一个新的数据的update,可能致使一部分根本没有变化的索引被重写不少次,而且可能致使不少的小的index segment,形成了search的性能降低。分布式
Lucene的优点在于索引查找的迅速,而非document的存储。为解决上述问题,基于NoSQL数据库存储索引的后端结构应运而生。函数
如下,将基于HBase的实现来进行分析。源码分析
实现方法性能
在Lucene中,其会操做两个单独的数据集:大数据
文档数据集中存储了全部文档,包括存储的字段等。
索引数据集中存储了全部字段/词汇/词频/位置等信息,以及包含当前字段的document
若是要实现将Lucene的后端移植到HBase上,直接构建一个Directory的实现并不会是最简单的。在已有的开源项目中,Lucandra和HBasene均采用了直接重写IndexReader和IndexWriter的方式,直接绕开了Directory的API。此实现并不会重写Lucene的索引查询机制。如若重载IndexSearcher,则能够在使用现有的Lucene索引查询机制上,根据后端的功能加强性能。
[ 图2 ] Lucene的后端从新设计
图2的设计,能够将Lucene后端与HBase整合起来,将索引数据存储到HBase中,从而利用HBase的大数据存储以及分布式性能。
架构设计
在架构设计上,将HBase用做索引的持久化后端,同时能够如网上所说,基于内存实现一套缓存机制,用来提升数据读取速度。实现一套高效的缓存同步机制,也将有利于数据读写速率的提升。
[ 图3 ] 带有内存缓存以及同步缓存的HBase后端实现
对于HBase的访问,每一次交互都须要经过以太网,以太网的运行状态将大大影响系统的使用状况,而索引的创建又但愿能达到实时且高响应。为了平衡这两种相互冲突的需求,在内存中,缓存可以最小化HBase用于搜索和文件返回的数据读取量,从而极大提高性能;按照须要运行为多个Lucene示例以支持日益增加的搜索客户端的能力。后者须要最小化缓存的生命周期,从而和HBase实例(上面提到实例的副本)中的内容同步。经过为活动参数实现可配置的缓存时间,限制每一个Lucene实例中展示的缓存,咱们能够达成一种折中方案。
根据上述所描述的结构,对于读操做,首先会检查所需数据是否在内存中且没有过时,若是有效将直接使用,否者将从HBase中获取数据并更新到内存中。而对于写操做,能够简化到直接将数据写入到HBase中,进而不须要考虑是否须要创建或更新缓存这种复杂的问题,这也将提升系统的实时响应性。
HBase Table的实现
当前了解到的两种可参考的实现方式:HBasene类型、Lucandra类型。
1-- HBasene
其索引表由如下几个column family组成:
fm.sequence:记录sequenceId,表示当前添加的第几个document。在执行createLuceneIndexTable时建立该行,且rowKey为segmentId,Column.qulifier为qual.sequence,Column.value=-1。每add一个document,当前segmentId的Column.value将自增1。
fm.doc2int:每一个document的存储都将被分配一个惟一的id,若是document的Field.Store=YES,则可以经过该id获取到对应的document的所有信息。
fm.fields:记录了Field中value的内容,rowKey为documentId,Column.qulifier为FieldName,Column.value为FieldValue的内容。
fm.termVector:向量偏移数据,用于模糊查找,记录了偏移量等信息,rowKey为FileldName/Term的组合,Column.qulifier为documentId,Column.value为指向的document中的全部位置偏移量。Column.value的结构为:[A][size][position]……[position]
fm.termFrequencies:关键词在每一个document中出现的频率,rowKey结构为zfm/FileName/Term,Column.qulifier为documentId,Column.value为出现的次数。
2-- Lucandra
在Lucendra中,只有两个ColummFamily来存储数据,分别是TermInfo和Documents
<Keyspace Name="Lucandra"> <ColumnFamily Name="TermInfo" CompareWith="BytesType" ColumnType="Super" CompareSubcolumnsWith="BytesType" KeysCached="10%" /> <ColumnFamily Name="Documents" CompareWith="BytesType" KeysCached="10%" /> </Keyspace>
TermInfo
TermInfo存储了Lucandra逆向索引的信息,用来存储index的Field信息,其结构以下:
RowKey:field/term
SuperColumn.name:documentId
[ SubColumn.name:"frequencies" Column.value:count ]
[ SubColumn.name:"position" Column.value:position vector ]
[ SubColumn.name:"offsets" Column.value:offsets vector ]
[ SubColumn.name:"norms" Column.value:norms vector ]
因为HBase中不存在SuperColumn和SubColumn的概念,咱们能够将其简化为:
RowKey:field/term
Column.qulifier:documentId
Column.value:fieldInfo [ 此fieldInfo能够经过AVRO类定义 ]
Documents
Documents存储了Document数据,其结构以下:
RowKey:documentId
Column.name:fieldName
Column.value:fieldValue
对比:
由HBasene的表结构能够知道,对于每个document的更新以及每一次term的查询,都须要操纵多行数据才能实现,逻辑上相对复杂;而Lucandra只须要处理两行(documents及TermInfo各一行)。可是HBasene的优点在于单行的存储结构小,每次与HBase交互只须要传输少许的数据及可,而且不像Lucandra结构同样须要而外的AVRO组件来序列化与反序列化数据。
HBasene的缺陷:
一、HBasene在三年前已经中止了其项目的更新,开源的支持断开了。
二、在对HBasene的最新源码分析过程当中,首先是发现其设计上保留着segment的概念,可是其设计阻碍着document的查询。其documentId由segmentId/sequenceId组成,每次segment的commit,sequenceId恢复为-1。而search时返回的TopDocs中只包含了sequenceId[int]。
三、每次添加document时,只有fm.Fields以及fm.doc2int的数据被实时flush到了HBase中,而其余数据须要segment大小到了必定程度[默认1000条document]才commit,容易形成数据缺失。在segment没被commit的状况下,是没法查询到新添加的document的。
四、HBase Table的设计中,没有考虑到当document中fieldName相同的状况。当fieldName相同的状况下,后面添加的会覆盖前面添加数据的,最后只保留最后添加的一份。
五、fm.termVector中,Column.value保存的并非positionVector信息,而是segment中全部document里出现的次数。
六、fm.termFrequencies只是单纯地存储了,并无被应用上。
七、IndexWriter的重写并无实现全部函数,只实现了最基本的addDocument
八、对IndexSearch的重写和扩展也不够。