缓存和刷新是比较重要的问题,它涉及到lucene如何管理内存和磁盘。前面提到索引的结果是缓存在内存中的,等到一定时候才会将其刷新到硬盘上去。缓存在这里的目的无非是缓解高速设备到低速设备的不匹配。下面这些问题都比较重要:调用增删改索引后此时索引时已经写入磁盘还是仍然驻留内存,即索引的刷新时间是什么?其次,缓存会占用多少内存?另外,刷新的效率如何?最后,lucene允许多个线程并发刷新索引,具体实现是怎么做的?
flush的一个总入口是DocumentWriter中的doFlush,随后严格按照索引链层层向下传递,直到FreqProxTermWriter的flush方法,FreqProxTermWriter中最后会调用如下语句,将所有对索引文件的操作交给codec去做。
- state.segmentInfo.getCodec().postingsFormat().fieldsConsumer(state).write(fields);
那么到底哪些地方会调用flush呢?实际上,下面这5个地方都会最终调用到flush,可以发现,这5个地方已经涵盖了对索引的大部分操作,可见flush的重要。
从源码上看,addIndexes, forceMerge, close和commit4个操作都是进行full flush,入口为DocumentWriter.flushAllThreads,他们会对所有active的DocumentsWriterPerThread进行flush;而updateDocument则只会flush一个DocumentsWriterPerThread。
在lucene中,flush是由DocumentsWriterFlushControl来统一控制的,好处就是统一入口,便于维护和管理。这个控制器用了很多方式来保持其扩展灵活性以及健壮性。
刷新时机策略
DocumentsWriterFlushControl中有一个FlushPolicy。我们每次更新文档都需要将缓存内容写入磁盘吗?如果每次索引的内容只有几十字节,那么频繁写入其实是不划算的,会带来极大的开销,也就失去使用缓存的意义了。FlushPolicy就是定义了这个刷新时机的策略,默认策略在FlushByRamOrCountsPolicy中定义。如下图,我们可以看出来,FlushByRamOrCountsPolicy中实际上有3种方式来控制缓存的刷新。
其中flushOnRAM会在删除或者插入时被应用,也就是内存达到设定值时候才允许刷新;flushOnDocCount则只在插入时生效,即文档数达到设定值生效;flushOnDeleteTerms在删除时的缓存大于设定值时生效;而这3个设定值分别对应indexWriterConfig中的RAMBufferSizeMB,MaxBufferedDocs, MaxBufferedDeleteTerms。也就是说,我们可以通过控制这几个值来控制刷新的方式。因而一旦设定了RAMBufferSizeMB这种刷新方式,则缓存占用的最大内存就是RAMBufferSizeMB
- public void onDelete(DocumentsWriterFlushControl control, ThreadState state) {
- if (flushOnDeleteTerms()) {
- // Flush this state by num del terms
- final int maxBufferedDeleteTerms = indexWriterConfig
- .getMaxBufferedDeleteTerms();
- if (control.getNumGlobalTermDeletes() >= maxBufferedDeleteTerms) {
- control.setApplyAllDeletes();
- }
- }
- if ((flushOnRAM() &&
- control.getDeleteBytesUsed() > (1024*1024*indexWriterConfig.getRAMBufferSizeMB()))) {
- control.setApplyAllDeletes();
- if (infoStream.isEnabled("FP")) {
- infoStream.message("FP", "force apply deletes bytesUsed=" + control.getDeleteBytesUsed() + " vs ramBuffer=" + (1024*1024*indexWriterConfig.getRAMBufferSizeMB()));
- }
- }
- }
- public void onInsert(DocumentsWriterFlushControl control, ThreadState state) {
- if (flushOnDocCount()
- && state.dwpt.getNumDocsInRAM() >= indexWriterConfig
- .getMaxBufferedDocs()) {
- // Flush this state by num docs
- control.setFlushPending(state);
- } else if (flushOnRAM()) {// flush by RAM
- final long limit = (long) (indexWriterConfig.getRAMBufferSizeMB() * 1024.d * 1024.d);
- final long totalRam = control.activeBytes() + control.getDeleteBytesUsed();
- if (totalRam >= limit) {
- if (infoStream.isEnabled("FP")) {
- infoStream.message("FP", "flush: activeBytes=" + control.activeBytes() + " deleteBytes=" + control.getDeleteBytesUsed() + " vs limit=" + limit);
- }
- markLargestWriterPending(control, state, totalRam);
- }
- }
- }
健康管理
在多线程下,有可能出现索引线程的速度快于刷新线程的速度,积累下去很容易导致JVM内存耗尽。
DocumentsWriterFlushControl中的DocumentsWriterStallControl便是用来做健康管理的。所谓的健康管理,就是在索引线程速度过快时阻塞索引线程,从而保证内存的占用不会持续上涨。判断是否需要健康管理的程序如下
- final boolean stall = ((activeBytes + flushBytes) > limit) &&
- (activeBytes < limit) &&
- !closed;
FlushAllThreads
具体到一次完全刷新具体做了什么事,可以参考下图,实际上主要就是3件事,首先做一些准备工作,因为刷新是非常复杂的过程,当中涉及多线程操作以及状态的变化,所以需要mark,接下来对每个pending状态的DocumentsWriterPerThread做flush,最后是等待所有flush结束。
markForFullFlush这个方法实际上有如下这些步骤。 实际上这个方法有许多多线程处理的技巧,都是为了尽可能的少用锁。
1. 确定删除队列。这里会新建一个generation加1的删除队列来替换原来的删除队列,而后续操作则使用老的删除队列,这么做无非是想在fullFlush时可以同时做其他操作比如删除而不用整个锁住。
2. 改变每个待刷新的ThreadState的状态,从active变为flushing。这步其实还有一个最重要的操作,就是internalTryCheckOutForFlush,他的机制就是将所有checkout的ThreadState放到一个Map(flushingWriters)中,这样就类似上了锁,只要在Map中的都是已经加锁的,比每次加锁判断要高效。
3. 对blockedFlushes阻塞的每个DocumentWriterPerThread,将其状态设置为flushing并从blockedFlushes中删除。
4. 准备好flushQueue,主要就是将上面步骤中fullFlushBuffer搜集到的DocumentWriterPerThread全部填充到flushQueue中
5. 更新健康状态,这步就是去判断是否需要健康管理。
如图,实际的刷新工作是多个线程并发的做,他们都会调用doFlush,很有意思的一点是它会把每次刷新作为一个ticket放到ticketQueue中来管理,这样会有一个统一地方获知正在刷新的ticket,也便于上锁等操作。在flush结束时会进入doAfterFlush,这里会将前面的flushingWriters删除本次flush用的DocumentsWriterPerThread,然后更新健康管理的状态并且使用notifyAll来通知其他等待的线程(主要就是waitForFlush的等待线程)。至于doFlush中实际如何顺着索引链向下的调用,这里就不细细说明了,整体流程到这里已经比较清楚,需要细节可以自行查阅相关代码。
waitForFlush是主线程与刷新线程同步的过程,每次都判断flushingWriters这个Map是否没有可用的threadState(即前面的flush有没有完成),如果没有完成,则继续等待。这正是《java多线程设计模式》中的Balking的使用。
http://blog.csdn.net/liweisnake/article/details/11659799
相关推荐
### Lucene3源码分析知识点概述 #### 一、全文检索的基本原理 ##### 1. 总论 全文检索系统是一种高效的信息检索技术,能够帮助用户在海量文档中快速找到包含特定关键词的信息。Lucene是Java领域内最受欢迎的全文...
Lucene学习总结之四:Lucene索引过程分析(1) Lucene学习总结之四:Lucene索引过程分析(2) Lucene学习总结之四:Lucene索引过程分析(3) Lucene学习总结之四:Lucene索引过程分析(4) www.chinaandroid.com
在本教程中,我们将探讨如何将IK分词器集成到Lucene 4.5版本中,以提升中文文本处理的效率和准确性。 首先,让我们了解一下IKAnalyzer。IKAnalyzer是由国人开发的一款开源中文分词库,它具有较好的分词效果和较高的...
6. **优化性能**:根据需求,可以考虑使用多线程并行索引、缓存策略、近实时搜索(NRT)等技术提高系统性能。例如,使用`NRTManager`可以实现快速响应的增量索引。 7. **安全与权限**:确保索引和搜索过程符合网站...
《深入剖析Lucene:从源码到实践》 Lucene是一个高性能、全文本搜索库,由Apache软件基金会开发,主要用于构建搜索引擎。它提供了一个简单但功能强大的API,使得开发者能够轻松地在应用中实现文本检索功能。Lucene...
10. **性能调优**:通过分析源码,开发者可以了解到如何调整各种参数,如缓存大小、合并策略等,来优化Lucene的性能。 总的来说,深入学习Lucene 3.5.0的源码,可以帮助开发者掌握全文检索的核心技术,了解其内部...
在IT行业中,搜索引擎技术是数据检索的关键组成部分,而Apache Lucene是一个开源的全文搜索引擎库,提供了高效的文本分析和索引功能。在这个项目中,我们看到开发者利用Lucene结合jdbcTemplate来封装API,并引入缓存...
《深入剖析Lucene.NET 2.9.1:源码解析与应用开发》 Lucene.NET 2.9.1是开源搜索引擎库Lucene的.NET版本,它为.NET开发者提供了强大的全文检索和索引功能。这个版本的源码提供了一个宝贵的资源,帮助我们理解其内部...
赠送jar包:lucene-core-7.7.0.jar; 赠送原API文档:lucene-core-7.7.0-javadoc.jar; 赠送源代码:lucene-core-7.7.0-sources.jar; 赠送Maven依赖信息文件:lucene-core-7.7.0.pom; 包含翻译后的API文档:lucene...
Lucene通过分析这些文本,将其拆分为术语,并在倒排索引中存储每个术语的位置信息,以便快速定位到包含特定术语的文档。 **2. 增量索引的概念** 增量索引的目的是避免重新构建整个索引,尤其是在大型数据集上,这...
《Lucene 2.4.1 源码分析——Analyzer深入解析》 在Lucene这个强大的全文搜索引擎库中,Analyzer扮演着至关重要的角色。它负责将输入的文本进行分词,过滤,标准化等预处理操作,使得搜索引擎能够正确理解和索引...
以上就是关于“Lucene索引的简单使用”的详细介绍,包括其核心概念、创建和查询索引的步骤以及一些高级特性。希望对你理解和应用Lucene有所帮助。在实际开发中,可以根据需求选择合适的Analyzer,优化索引策略,以...
Lucene 5.0的API更加精炼和直观,主要分为几个核心接口:`IndexWriter`用于创建和更新索引,`IndexReader`用于读取索引,`Directory`代表存储索引的物理位置,`Analyzer`处理文本分析,`Query`表示查询条件,`...
通过阅读和学习 Luke 的源码,我们可以了解到如何与 Lucene 索引进行交互,以及索引结构是如何组织和存储的。 在提供的压缩包 "luke-3.3.0" 中,包含了 Luke 工具的旧版本。这个版本可能不支持最新的 Lucene 版本,...
《深入理解Lucene 4.5:核心概念与应用实践》 Lucene是一个高性能、全文检索库,由Apache软件基金会开发并维护。它为Java开发者提供了强大的文本搜索功能,是构建搜索引擎的重要工具。本文将围绕Lucene 4.5版本进行...
在这个主题中,我们将对Lucene的源码进行深入的分析。 1. **Lucene的基本结构** - Lucene的核心组件包括索引(Index)、查询解析器(Query Parser)、搜索器(Searcher)和分词器(Tokenizer)。索引是搜索引擎的...
《Lucene5源码教程:拼音检索与分词器实战》 在当今的信息化社会,搜索引擎已经成为我们获取信息的重要工具。Lucene,作为Apache软件基金会的开源全文检索库,为开发者提供了强大的文本检索功能。本教程将深入探讨...
反向信息词--->文档文件有:数据类型:Lucene索引文件中Byte:最基本的数据类型Uint32:由4个byte组成VInt:长度变化的数据类型,可能包含多
### Lucene对XML文档建立索引的技术解析与实践 #### 一、引言 随着互联网技术的迅猛发展,非结构化数据(如XML文档)在企业和组织中的应用日益广泛。如何高效地处理这些非结构化的数据,特别是进行快速检索成为了一...
Lucene.Net 的源码结构清晰,分为多个模块,如索引、查询解析、分词器等,每个模块都有明确的职责。源码主要由 C# 编写,遵循面向对象的设计原则,包括封装、继承和多态。通过阅读源码,开发者可以深入了解搜索引擎...