搜索引擎Lucene(4):索引的创建过程

搜索引擎Lucene(4):索引的创建过程,第1张

创建索引的过程如下:

索引结构如下:

IndexWriter结构:

IndexWriter通过指定存放的目录(Directory)以及文档分析器(Analyzer)来构建,direcotry代表索引存储在哪里;analyzer表示如何来分析文档的内容;similarity用来规格化文档,给文档算分;IndexWriter类里还有一些SegmentInfos对象用于存储索引片段信息,以及发生故障回滚等。

添加文档使用addDocument()方法,删除文档使用deleteDocuments(Term)或者deleteDocuments(Query)方法,而且一篇文档可以使用updateDocument()方法来更新(仅仅是先执行delete在执行add *** 作而已)。当完成了添加、删除、更新文档,应该需要调用close方法。

这些修改会缓存在内存中(buffered in memory),并且定期地(periodically)刷新到(flush)Directory中(在上述方法的调用期间)。一次flush *** 作会在如下时候触发(triggered):当从上一次flush *** 作后有足够多缓存的delete *** 作(参见setMaxBufferedDeleteTerms(int)),或者足够多已添加的文档(参见setMaxBufferedDocs(int)),无论哪个更快些(whichever is sooner)。对被添加的文档来说,一次flush会在如下任何一种情况下触发,文档的RAM缓存使用率(setRAMBufferSizeMB)或者已添加的文档数目,缺省的RAM最高使用率是16M,为得到索引的最高效率,你需要使用更大的RAM缓存大小。需要注意的是,flush处理仅仅是将IndexWriter中内部缓存的状态(internal buffered state)移动进索引里去,但是这些改变不会让IndexReader见到,直到commit()和close()中的任何一个方法被调用时。一次flush可能触发一个或更多的片断合并(segmentmerges),这时会启动一个后台的线程来处理,所以不会中断addDocument的调用,请参考MergeScheduler。

一个IndexReader或者IndexSearcher只会看到索引在它打开的当时的状态。任何在索引被打开之后提交到索引中的commit信息,在它被重新打开之前都不会见到。

DocumentsWriter结构:

DocumentsWriter 是由IndexWriter 调用来负责处理多个文档的类,它通过与Directory 类及Analyzer 类、Scorer 类等将文档内容提取出来,并分解成一组term列表再生成一个单一的segment 所需要的数据文件,如term频率、term 位置、term 向量等索引文件,以便SegmentMerger 将它合并到统一的segment 中去。

该类可接收多个添加的文档,并且直接写成一个单独的segment 文件。这比为每一个文档创建一个segment(使用DocumentWriter)以及对那些segments 执行合作处理更有效率。

每一个添加的文档都被传递给DocConsumer类,它处理该文档并且与索引链表中(indexing chain)其它的consumers相互发生作用(interacts with)。确定的consumers,就像StoredFieldWriter和TermVectorsTermsWriter,提取一个文档的摘要(digest),并且马上把字节写入“文档存储”文件(比如它们不为每一个文档消耗(consume)内存RAM,除了当它们正在处理文档的时候)。

其它的consumers,比如FreqProxTermsWriter和NormsWriter,会缓存字节在内存中,只有当一个新的segment制造出的时候才会flush到磁盘中。

一旦使用完我们分配的RAM缓存,或者已添加的文档数目足够多的时候(这时候是根据添加的文档数目而不是RAM的使用率来确定是否flush),我们将创建一个真实的segment,并将它写入Directory中去。

索引创建的调用过程:

一个Directory对象是一系列统一的文件列表(a flat list of files)。文件可以在它们被创建的时候一次写入,一旦文件被创建,它再次打开后只能用于读取(read)或者删除(delete) *** 作。并且同时在读取和写入的时候允许随机访问。

FSDirectory类直接实现Directory抽象类为一个包含文件的目录。目录锁的实现使用缺省的SimpleFSLockFactory,但是可以通过两种方式修改,即给getLockFactory()传入一个LockFactory实例,或者通过调用setLockFactory()方法明确制定LockFactory类。

目录将被缓存(cache)起来,对一个指定的符合规定的路径(canonical path)来说,同样的FSDirectory实例通常通过getDirectory()方法返回。这使得同步机制(synchronization)能对目录起作用。

RAMDirectory类是一个驻留内存的(memory-resident)Directory抽象类的实现。目录锁的实现使用缺省的SingleInstanceLockFactory,但是可以通过setLockFactory()方法修改。

IndexInput类是一个为了从一个目录(Directory)中读取文件的抽象基类,是一个随机访问(random-access)的输入流(input stream),用于所有Lucene读取Index的 *** 作。BufferedIndexInput是一个实现了带缓冲的IndexInput的基础实现。

IndexOutput类是一个为了写入文件到一个目录(Directory)中的抽象基类,是一个随机访问(random-access)的输出流(output stream),用于所有Lucene写入Index的 *** 作。BufferedIndexOutput是一个实现了带缓冲的IndexOutput的基础实现。RAMOuputStream是一个内存驻留(memory-resident)的IndexOutput的实现类。

域索引选项通过倒排索引来控制文本是否可被搜索。

当lucene建立起倒排索引后,默认情况下它会保存所有必要的信息以实施Vector Space Model。该Model需要计算文档中出现的Term数,以及它们出现的文职(这是必要的,比如通过词组搜索时用到)。但有时候这些域只是在布尔搜索时用到,他们并不为相关评分做贡献,一个常见的例子是,域只是被用作过滤,如权限过滤和日期过滤。在这种情况下,可以通过调用Field.setOmitTermFreqAndPositions(true)方法让lucene跳过对改选项的出现频率和出现位置的索引。该方法可以节省一些索引在磁盘上的储存空间,还可以加速搜索和过滤过程,但会悄悄阻止需要位置信息的搜索,如阻止PhraseQuery和SpanQuery类的运行。

域存储选项是用来确定是否需要存储域的真实值,以便后续搜索时能回复这个值。

lucene支持想一个域中写入多个不同的值。

这种处理方式是完全可以接受并鼓励使用的,因为这是逻辑上具有多个域值的域的自然表示方式。在lucene内部,只要文档中出现同名的多域值,倒排索引和项向量都会在逻辑上将这些语汇单元附加进去,具体顺序由添加该域的顺序决定。

文档和域的加权 *** 作可以在索引期间完成。而搜索期间的加权 *** 作会更加动态化,因为每次搜索都可以根据不同的加权因子独立选择加权或不加权,但这个策略也可能多消耗一些CPU效率。搜索期间的动态加权可以更灵活控制。

默认情况下,所有文档的加权因子都是1.0,通过改变文档的加权因子,就可以影响文档在索引中的重要程度。调整加权 *** 作的API为:setBoost(float);

同文档加权一样,可以对进行加权 *** 作。文档加权时,lucene内部会采用同一加权因子来对该文档中的域进行加权。域加权API:Field.setBoost(fliat)。

Analyzer类构建用于分析文本的TokenStream对象,因此(thus)它表示(represent)用于从文本中分解(extract)出组成索引的terms的一个规则器(policy)。典型的(typical)实现首先创建一个Tokenizer,它将那些从Reader对象中读取字符流(stream of characters)打碎为(break into)原始的Tokens(raw Tokens)。然后一个或更多的TokenFilters可以应用在这个Tokenizer的输出上。警告:你必须在你的子类(subclass)中覆写(override)定义在这个类中的其中一个方法,否则的话Analyzer将会进入一个无限循环(infinite loop)中。

StandardAnalyzer:

StandardAnalyzer类是使用一个English的stop words列表来进行tokenize分解出文本中word,使用StandardTokenizer类分解词,再加上StandardFilter以及LowerCaseFilter以及StopFilter这些过滤器进行处理的这样一个Analyzer类的实现。

将索引写在Hadoop2.x的HDFS上,写入2.x的Hadoop相对1.x的Hadoop来说要简单的

说了,因为默认solr(4.4之后的版本)里面自带的HDFSDirectory就是支持2.x的而不支持1.x的,使用2.x的Hadoop平台,可

以直接把solr的corejar包拷贝到工程里面,即可使用建索引,散仙,是在eclipse上使用eclipse插件来运行hadoop程序,具体要

用到的jar包,除了需要用到hadoop2.2的所有jar包外,还需增加lucene和solr的部分jar包,截图如下,散仙本次使用的是

Lucene4.8.1的版本:

具体的代码如下:

package com.mapreduceindex

import org.apache.hadoop.conf.Configuration

import org.apache.hadoop.fs.FileSystem

import org.apache.hadoop.fs.Path

import org.apache.lucene.analysis.Analyzer

import org.apache.lucene.analysis.standard.StandardAnalyzer

import org.apache.lucene.document.Document

import org.apache.lucene.document.Field.Store

import org.apache.lucene.document.StringField

import org.apache.lucene.document.TextField

import org.apache.lucene.index.DirectoryReader

import org.apache.lucene.index.IndexReader

import org.apache.lucene.index.IndexWriter

import org.apache.lucene.index.IndexWriterConfig

import org.apache.lucene.index.Term

import org.apache.lucene.queryparser.classic.QueryParser

import org.apache.lucene.search.IndexSearcher

import org.apache.lucene.search.Query

import org.apache.lucene.search.ScoreDoc

import org.apache.lucene.search.TopDocs

import org.apache.lucene.store.Directory

import org.apache.lucene.util.Version

import org.apache.solr.store.hdfs.HdfsDirectory

import org.wltea.analyzer.lucene.IKAnalyzer

/**

*

* 将索引存储在Hadoop2.2的HDFS上

*

* @author qindongliang

* QQ技术交流群:

* 1号群: 324714439 如果满员了请加2号群

* 2号群: 206247899

*

*

* **/

public class MyIndex {

public static void createFile()throws Exception{

Configuration conf=new Configuration()

FileSystem fs=FileSystem.get(conf)

Path p =new Path("hdfs://192.168.46.32:9000/root/abc.txt")

fs.createNewFile(p)

//fs.create(p)

fs.close()//释放资源

System.out.println("创建文件成功.....")

}

public static void main(String[] args)throws Exception {

//createFile()

//long a=System.currentTimeMillis()

// add()

// long b=System.currentTimeMillis()

// System.out.println("耗时: "+(b-a)+"毫秒")

query("8")

//delete("3")//删除指定ID的数据

}

/***

* 得到HDFS的writer

*

* **/

public static IndexWriter getIndexWriter() throws Exception{

Analyzer analyzer=new IKAnalyzer(true)

IndexWriterConfigconfig=new IndexWriterConfig(Version.LUCENE_48, analyzer)

Configuration conf=new Configuration()

conf.set("fs.defaultFS","hdfs://192.168.46.32:9000/")

//conf.set("mapreduce.framework.name", "yarn")

//conf.set("yarn.resourcemanager.address", "192.168.46.32:8032")

//Path p1 =new Path("hdfs://10.2.143.5:9090/root/myfile/my.txt")

//Path path=new Path("hdfs://10.2.143.5:9090/root/myfile")

Path path=new Path("hdfs://192.168.46.32:9000/qin/myindex")

//HdfsDirectory directory=new HdfsDirectory(path, conf)

HdfsDirectory directory=new HdfsDirectory(path, conf)

IndexWriter writer=new IndexWriter(directory, config)

return writer

}

public static void add()throws Exception{

IndexWriter writer=getIndexWriter()

// Document doc=new Document()

// doc.add(new StringField("id", "3", Store.YES))

// doc.add(new StringField("name", "lucene是一款非常优秀的全文检索框架", Store.YES))

// doc.add(new TextField("content", "我们的工资都不高", Store.YES))

// Document doc2=new Document()

// doc2.add(new StringField("id", "4", Store.YES))

// doc2.add(new StringField("name", "今天天气不错呀", Store.YES))

// doc2.add(new TextField("content", "钱存储在银行靠谱吗", Store.YES))

//

// Document doc3=new Document()

// doc3.add(new StringField("id", "5", Store.YES))

// doc3.add(new StringField("name", "没有根的野草,飘忽的命途!", Store.YES))

// doc3.add(new TextField("content", "你工资多少呀!", Store.YES))

// writer.addDocument(doc)

// writer.addDocument(doc2)

// writer.addDocument(doc3)

for(int i=6i<10000i++){

Document doc=new Document()

doc.add(new StringField("id", i+"", Store.YES))

doc.add(new StringField("name", "lucene是一款非常优秀的全文检索框架"+i, Store.YES))

doc.add(new TextField("content", "今天发工资了吗"+i, Store.YES))

writer.addDocument(doc)

if(i%1000==0){

writer.commit()

}

}

//writer.forceMerge(1)

writer.commit()

System.out.println("索引3条数据添加成功!")

writer.close()

}

/***

* 添加索引

*

* **/

public static void add(Document d)throws Exception{

IndexWriter writer=getIndexWriter()

writer.addDocument(d)

writer.forceMerge(1)

writer.commit()

System.out.println("索引10000条数据添加成功!")

writer.close()

}

/**

* 根据指定ID

* 删除HDFS上的一些数据

*

*

* **/

public static void delete(String id)throws Exception{

IndexWriter writer=getIndexWriter()

writer.deleteDocuments(new Term("id", id))//删除指定ID的数据

writer.forceMerge(1)//清除已经删除的索引空间

writer.commit()//提交变化

System.out.println("id为"+id+"的数据已经删除成功.........")

}

public static void query(String queryTerm)throws Exception{

System.out.println("本次检索内容: "+queryTerm)

Configuration conf=new Configuration()

conf.set("fs.defaultFS","hdfs://192.168.46.32:9000/")

//Path p1 =new Path("hdfs://10.2.143.5:9090/root/myfile/my.txt")

// Path path=new Path("hdfs://192.168.75.130:9000/root/index")

Path path=new Path("hdfs://192.168.46.32:9000/qin/myindex")

Directory directory=new HdfsDirectory(path, conf)

IndexReader reader=DirectoryReader.open(directory)

System.out.println("总数据量: "+reader.numDocs())

long a=System.currentTimeMillis()

IndexSearcher searcher=new IndexSearcher(reader)

QueryParser parse=new QueryParser(Version.LUCENE_48, "content", new IKAnalyzer(true))

Query query=parse.parse(queryTerm)

TopDocs docs=searcher.search(query, 100)

System.out.println("本次命中结果: "+docs.totalHits+" 条" )

for(ScoreDoc sc:docs.scoreDocs){

System.out.println("评分: "+sc.score+" id : "+searcher.doc(sc.doc).get("id")+" name: "+searcher.doc(sc.doc).get("name")+" 字段内容: "+searcher.doc(sc.doc).get("content"))

}

long b=System.currentTimeMillis()

System.out.println("第一次耗时:"+(b-a)+" 毫秒")

// System.out.println("============================================")

// long c=System.currentTimeMillis()

// query=parse.parse(queryTerm)

//

// docs=searcher.search(query, 100)

// System.out.println("本次命中结果: "+docs.totalHits+" 条" )

// for(ScoreDoc sc:docs.scoreDocs){

//

// System.out.println("评分: "+sc.score+" id : "+searcher.doc(sc.doc).get("id")+" name: "+searcher.doc(sc.doc).get("name")+" 字段内容: "+searcher.doc(sc.doc).get("content"))

//

// }

// long d=System.currentTimeMillis()

// System.out.println("第二次耗时:"+(d-c)+" 毫秒")

reader.close()

directory.close()

System.out.println("检索完毕...............")

}

}

使用IK的分词器,建立索引完毕后,在HDFS上的索引如下截图:

检索数据时,第一次检索往往比较慢,第一次之后因为有了Block Cache,所以第二次,检索的速度非常快,当然这也跟你机器的配置有关系:

本次检索内容: 8

WARN - NativeCodeLoader.<clinit>(62) | Unable to load native-hadoop library for your platform... using builtin-java classes where applicable

总数据量: 9994

本次命中结果: 1 条

评分: 4.7582965 id : 8 name: lucene是一款非常优秀的全文检索框架8 字段内容: 今天发工资了吗8

第一次耗时:261 毫秒

============================================

本次命中结果: 1 条

评分: 4.7582965 id : 8 name: lucene是一款非常优秀的全文检索框架8 字段内容: 今天发工资了吗8

第二次耗时:6 毫秒

INFO - HdfsDirectory.close(97) | Closing hdfs directory hdfs://192.168.46.32:9000/qin/myindex

检索完毕...............


欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/bake/11755721.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-18
下一篇 2023-05-18

发表评论

登录后才能评论

评论列表(0条)

保存