问题导读
1.Lucene中,以什么的形式作为搜索的主体?
2.Lucene与HBase的组合实现方法是什么?
3.HBase Table的实现有哪两种方式?
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中,其会操作两个单独的数据集:
如果要实现将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存储了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存储了Document数据,其结构如下: RowKey:documentId Column.name:fieldName Column.value:fieldValue
对比: 由HBasene的表结构可以知道,对于每一个document的更新以及每一次term的查询,都需要操纵多行数据才能实现,逻辑上相对复杂;而Lucandra只需要处理两行(documents及TermInfo各一行)。但是HBasene的优势在于单行的存储结构小,每次与HBase交互只需要传输少量的数据及可,并且不像Lucandra结构一样需要而外的AVRO组件来序列化与反序列化数据。
HBasene的缺陷: 1、HBasene在三年前已经停止了其项目的更新,开源的支持断开了。 2、在对HBasene的最新源码分析过程中,首先是发现其设计上保留着segment的概念,但是其设计阻碍着document的查询。其documentId由segmentId/sequenceId组成,每次segment的commit,sequenceId恢复为-1。而search时返回的TopDocs中只包含了sequenceId[int]。 3、每次添加document时,只有fm.Fields以及fm.doc2int的数据被实时flush到了HBase中,而其他数据需要segment大小到了一定程度[默认1000条document]才commit,容易造成数据缺失。在segment没被commit的情况下,是无法查询到新添加的document的。 4、HBase Table的设计中,没有考虑到当document中fieldName相同的情况。当fieldName相同的情况下,后面添加的会覆盖前面添加数据的,最后只保留最后添加的一份。 5、fm.termVector中,Column.value保存的并不是positionVector信息,而是segment中所有document里出现的次数。 6、fm.termFrequencies只是单纯地存储了,并没有被应用上。 7、IndexWriter的重写并没有实现所有函数,只实现了最基本的addDocument 8、对IndexSearch的重写和扩展也不够。
|