首页 > 编程语言 >lucene4.5源码分析系列:索引缓存以及刷新

lucene4.5源码分析系列:索引缓存以及刷新

时间:2023-03-27 10:32:29浏览次数:42  
标签:control 缓存 flush 索引 源码 线程 刷新 lucene4.5


缓存和刷新是比较重要的问题,它涉及到lucene如何管理内存和磁盘。前面提到索引的结果是缓存在内存中的,等到一定时候才会将其刷新到硬盘上去。缓存在这里的目的无非是缓解高速设备到低速设备的不匹配。下面这些问题都比较重要:调用增删改索引后此时索引时已经写入磁盘还是仍然驻留内存,即索引的刷新时间是什么?其次,缓存会占用多少内存?另外,刷新的效率如何?最后,lucene允许多个线程并发刷新索引,具体实现是怎么做的?

  flush的一个总入口是DocumentWriter中的doFlush,随后严格按照索引链层层向下传递,直到FreqProxTermWriter的flush方法,FreqProxTermWriter中最后会调用如下语句,将所有对索引文件的操作交给codec去做。


    1. state.segmentInfo.getCodec().postingsFormat().fieldsConsumer(state).write(fields);


      那么到底哪些地方会调用flush呢?实际上,下面这5个地方都会最终调用到flush,可以发现,这5个地方已经涵盖了对索引的大部分操作,可见flush的重要。

    lucene4.5源码分析系列:索引缓存以及刷新_Lucene

     

      从源码上看,addIndexes, forceMerge, close和commit4个操作都是进行full flush,入口为DocumentWriter.flushAllThreads,他们会对所有active的DocumentsWriterPerThread进行flush;而updateDocument则只会flush一个DocumentsWriterPerThread。

      在lucene中,flush是由DocumentsWriterFlushControl来统一控制的,好处就是统一入口,便于维护和管理。这个控制器用了很多方式来保持其扩展灵活性以及健壮性。

     刷新时机策略

      DocumentsWriterFlushControl中有一个FlushPolicy。我们每次更新文档都需要将缓存内容写入磁盘吗?如果每次索引的内容只有几十字节,那么频繁写入其实是不划算的,会带来极大的开销,也就失去使用缓存的意义了。FlushPolicy就是定义了这个刷新时机的策略,默认策略在FlushByRamOrCountsPolicy中定义。如下图,我们可以看出来,FlushByRamOrCountsPolicy中实际上有3种方式来控制缓存的刷新。

    lucene4.5源码分析系列:索引缓存以及刷新_缓存_02

      其中flushOnRAM会在删除或者插入时被应用,也就是内存达到设定值时候才允许刷新;flushOnDocCount则只在插入时生效,即文档数达到设定值生效;flushOnDeleteTerms在删除时的缓存大于设定值时生效;而这3个设定值分别对应indexWriterConfig中的RAMBufferSizeMB,MaxBufferedDocs, MaxBufferedDeleteTerms。也就是说,我们可以通过控制这几个值来控制刷新的方式。因而一旦设定了RAMBufferSizeMB这种刷新方式,则缓存占用的最大内存就是RAMBufferSizeMB

     

      1. public void onDelete(DocumentsWriterFlushControl control, ThreadState state) {  
      2. if (flushOnDeleteTerms()) {  
      3. // Flush this state by num del terms  
      4. final int maxBufferedDeleteTerms = indexWriterConfig  
      5.         .getMaxBufferedDeleteTerms();  
      6. if (control.getNumGlobalTermDeletes() >= maxBufferedDeleteTerms) {  
      7.       control.setApplyAllDeletes();  
      8.     }  
      9.   }  
      10. if ((flushOnRAM() &&  
      11. 1024*1024*indexWriterConfig.getRAMBufferSizeMB()))) {  
      12.     control.setApplyAllDeletes();  
      13. if (infoStream.isEnabled("FP")) {  
      14. "FP", "force apply deletes bytesUsed=" + control.getDeleteBytesUsed() + " vs ramBuffer=" + (1024*1024*indexWriterConfig.getRAMBufferSizeMB()));  
      15.    }  
      16.  }  
      17. }  
      18.   
      19. public void onInsert(DocumentsWriterFlushControl control, ThreadState state) {  
      20. if (flushOnDocCount()  
      21.       && state.dwpt.getNumDocsInRAM() >= indexWriterConfig  
      22.           .getMaxBufferedDocs()) {  
      23. // Flush this state by num docs  
      24.     control.setFlushPending(state);  
      25. else if (flushOnRAM()) {// flush by RAM  
      26. final long limit = (long) (indexWriterConfig.getRAMBufferSizeMB() * 1024.d * 1024.d);  
      27. final long totalRam = control.activeBytes() + control.getDeleteBytesUsed();  
      28. if (totalRam >= limit) {  
      29. if (infoStream.isEnabled("FP")) {  
      30. "FP", "flush: activeBytes=" + control.activeBytes() + " deleteBytes=" + control.getDeleteBytesUsed() + " vs limit=" + limit);  
      31.       }  
      32.       markLargestWriterPending(control, state, totalRam);  
      33.     }  
      34.   }  
      35. }



        健康管理
         在多线程下,有可能出现索引线程的速度快于刷新线程的速度,积累下去很容易导致JVM内存耗尽。

       

         DocumentsWriterFlushControl中的DocumentsWriterStallControl便是用来做健康管理的。所谓的健康管理,就是在索引线程速度过快时阻塞索引线程,从而保证内存的占用不会持续上涨。判断是否需要健康管理的程序如下

       


       


      1. final boolean stall = ((activeBytes + flushBytes) > limit)  &&  
      2.                           (activeBytes < limit) &&  
      3.                           !closed;


         FlushAllThreads

         具体到一次完全刷新具体做了什么事,可以参考下图,实际上主要就是3件事,首先做一些准备工作,因为刷新是非常复杂的过程,当中涉及多线程操作以及状态的变化,所以需要mark,接下来对每个pending状态的DocumentsWriterPerThread做flush,最后是等待所有flush结束。

       

      lucene4.5源码分析系列:索引缓存以及刷新_sed_03

        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中实际如何顺着索引链向下的调用,这里就不细细说明了,整体流程到这里已经比较清楚,需要细节可以自行查阅相关代码。

      lucene4.5源码分析系列:索引缓存以及刷新_缓存_04

        waitForFlush是主线程与刷新线程同步的过程,每次都判断flushingWriters这个Map是否没有可用的threadState(即前面的flush有没有完成),如果没有完成,则继续等待。这正是《java多线程设计模式》中的Balking的使用。




       

      标签:control,缓存,flush,索引,源码,线程,刷新,lucene4.5
      From: https://blog.51cto.com/u_2650279/6151322

      相关文章

      • Spring源码核心剖析
        作者:京东科技韩国凯前言SpringAOP作为Spring最核心的能力之一,其重要性不言而喻。然后需要知道的是AOP并不只是Spring特有的功能,而是一种思想,一种通用的功能。而SpringAOP只......
      • Spring源码核心剖析
        作者:京东科技韩国凯前言SpringAOP作为Spring最核心的能力之一,其重要性不言而喻。然后需要知道的是AOP并不只是Spring特有的功能,而是一种思想,一种通用的功能。而SpringAO......
      • swap交换空间设置及清空缓存的命令:
        linuxswap空间的swappiness=0 linux会使用硬盘的一部分做为SWAP分区,用来进行进程调度--进程是正在运行的程序--把当前不用的进程调成‘等待(standby)‘,甚至‘睡眠(slee......
      • [FastAPI-32]依赖注入缓存
        fromfastapiimportDepends,FastAPIapp=FastAPI()'''依赖注入缓存现象-依赖条件`get_num`被依赖了两次,但是你会发现其内部打印语句只打印了一次。也就是说,第......
      • HashTable源码分析
        HashTable是一个线程安全的HashMap,是jdk早期版本的产物,但其效率较低1.初始化可以看到,与HashMap不同,HashTable无参构造是默认会构造一个容量为11的数组,而HashMap在无参......
      • 第八篇:Spring源码-DI的过程
        Spring源码-DI的过程  接下来我们分析下Spring源码中Bean初始化过程中的DI过程。也就是属性的依赖注入。一、构造参数依赖1.如何确定构造方法  在Spring中生成Bean实例......
      • 第九篇:Spring源码-AOP分析
        Spring源码-AOP分析一、手写AOP回顾  本文我们开始讲解Spring中的AOP原理和源码,我们前面手写了AOP的实现,了解和自己实现AOP应该要具备的内容,我们先回顾下,这对我们理解Spri......
      • 选股公式代写 MACD佛手二次翻红公式源码
        原理解析:输出DIF:收盘价的6日指数移动平均-收盘价的19日指数移动平均,,NODRAW输出DEA:DIF的9日指数移动平均,NODRAW输出平滑异同平均:2*(DIF-DEA),COLORSTICK当满足条件MAC......
      • Qt源码阅读(二) moveToThread
        Qt源码分析之moveToThread这一次,我们来看Qt中关于将一个QObject对象移动至一个线程的函数moveToThread目录Qt源码分析之moveToThreadQt使用线程的基本方法源码分析一些......
      • Mybatis源码(十一):Mybatis与Spring的整合
        一、搭建mybtais-spring运行环境1、创建数据表并初始化CREATETABLE`user`(`id`int(8)NOTNULLAUTO_INCREMENTCOMMENT'主键',`name`varchar(32)CHARACTE......