原理分析:Lucene reader计数与索引视图更新的异步线程化

简介: 假期梳理了之前在新浪博客的文档,将一些有用的内容搬到这里。本文分析Lucene reader计数与索引视图更新的异步线程化原理。

Lucene reader计数

Lucene读索引是reader共享、写索引是indexWriter,二者分离开来的。

对于读索引reader是并发的、资源释放是通过计数来实现的。关键代码如下

参考lucene 3.4

//定义计数器

  private final AtomicInteger refCount = new AtomicInteger();

   publicintgetRefCount() {

     returnrefCount.get();

}

//访问时候++

publicvoidincRef() {

       ensureOpen();

      refCount.incrementAndGet();

}

 

//退出的时候--

publicvoiddecRef()throwsIOException {

   ensureOpen();

  if(refCount.getAndDecrement() == 1) {

    booleansuccess =false;

    try{

       commit();

       doClose();

       success =true;

     }finally{

      if(!success) {

        // Put reference back on failure

        refCount.incrementAndGet();

       }

     }

     readerFinished();

   }

 }

//初始化打开reader的时候计数为1

protectedIndexReader() {

     refCount.set(1);

}

 

工作原理:

初始化reader的时候计数为1,包括reopen的时候也是设置1

通过getSearcher打开SolrIndexReader,这个时候SolrIndexReader里面的具体reader会计数++

getSearcher完毕,会执行close,调用SolrIndexReader里面具体reader计数--

这样在正常情况下,reader总是打开的,因为计数1.只有在SolrCore关闭的时候调用close方法,才会计数--,释放reader资源。

 

使用注意:

    solrCore自身也是计数的,如果solrCore close的时候,没有触发SolrCore真正关闭,那么reader也没关闭,间接导致内存泄露。

        这也是之前终搜老版本通过while 强制solrCore decf 来关闭,会导致资源未释放,在某些场景。或者提前释放,导致句柄存在

        solrCore solr的使用规范是通过CoreContaner获取,每次用完都会close的。我们破坏了这个用法,导致某些场景资源关闭异常。

        未来需要定制的、并且需要扩展reader的场景,计数地方大家留个心。

 

虚拟分组计数

        虚拟分组,计数较为复杂些。主要是存在内存reopenflushreopenmain reopenmerger reopen

        启动时候:reader Open时候++-  --> splitReader时候++  --> CoreReader(SolrIndexReader++反应),也就是说默认时候索引reader计数是3

        Ram reopen的时候splitnew ,计数++,这个时候core 层不变

        Flush时候reopenopen自身++splitnew 计数++core层不变

        切换后 split整个替换,计数回到3

        从而close split close flush 直接调用close不行,需要多执行2decf

 

索引视图更新的异步reopen

   索引视图更新,在solrCore是通过getSearcher间接调用readerreopen来完成。

   当前tsearcher版本都是遵循这个机制。

Searcher的共享使得reader可共享,内存之外的reader不轻易reopen

   实时模式下,reopen ram,而磁盘、主索引的非reopen,使得内存索引可见得到及时完成。

   当时reopen是非常消耗资源的,因为需要加载索引的“词典信息”。

   异步打开就是首选了。

   非虚拟分组的时候,采取的是下面的线程池参数

 finalBlockingQueueblockQueue=newArrayBlockingQueue(

        2);

 finalExecutorServicesearcherExecutor=newThreadPoolExecutor(1, 1,

        (long) 300, TimeUnit.SECONDS,blockQueue);

   其中参数用的非常巧妙!否则会出现DeckSearcher,导致资源泄露、性能下降

 

   虚拟分组,由于每个分片独立提交并触发reopen。在重启或者add更新量非常巨大瞬间,上面的线程池结构会出现下面的异常。

java.util.concurrent.RejectedExecutionException

at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:1768)  

   at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:767) at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:658)  

 

    由于,不能强制关闭已经merger的索引,因为可能有请求在使用,也就不能立即清除已经合并的子索引目录。只好在正在close的时候关闭。

改进异步提交的参数

     blockQueue =new  LinkedBlockingQueue();

     searcherExecutor   =newThreadPoolExecutor(1, 1,

             (long) 0, TimeUnit.SECONDS,blockQueue);

   

任务以job形式、串行工作。Job里面,会在searcher打开后执行close资源,然后close里面判断是否清理cache、删除目录等。

    内存reopen在虚拟分组场景下是可以合并的。在flushmerge时候需要reopen,内存reopen被合并。

    线程池的参数选择以及关闭和释放的资源,直接影响了局部、资源泄露等。再多次尝试后,才稳定下来。

    关于线程池参数这部分,这篇博文写的比较简单明了http://dongxuan.iteye.com/blog/901689

所有BlockingQueue都可用于传输和保持提交的任务。可以使用此队列与池大小进行交互:

·      如果运行的线程少于corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。(什么意思?如果当前运行的线程小于corePoolSize,则任务根本不会存放,添加到queue,而是直接抄家伙(thread开始运行

·      如果运行的线程等于或多于corePoolSize,则 Executor 始终首选将请求加入队列而不添加新的线程

·      如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝

·      ------------------------------------------------------------------

虚拟分组异步打开源码部分如下。

public voidreopenSearcher(booleanisDisk) {

     if(isRamRuning.get() )return;

     else{

         isRamRuning.set(true) ;

          FreshJob4RamReopen job =newFreshJob4RamReopen(solrCore);

          job.run();

         isRamRuning.set(false);

         //searcherExecutor.submit(job);

         //blockQueue.add(job);

      }

    }

  public voidreopenSearcher(booleanisDisk,RealTimeIndexReader4VroutExtend toCloseReader,IndexWriter oldRamWriter) {

          FreshJob4RamFlush job =newFreshJob4RamFlush(solrCore,isRamRuning);

          job.setToCloseReader( toCloseReader );

          job.setOldRamWriter( oldRamWriter );  

         searcherExecutor.submit(job);

         //blockQueue.add(job);

   }

 

  public voidreopenSearcher(booleanisDisk,List flushMergeReaders) {

           FreshJob4Merger job =newFreshJob4Merger(solrCore,isRamRuning);

          job.setFlushMergeReaders(flushMergeReaders);

          searcherExecutor.submit(job);

         //blockQueue.add(job);

   }

目录
相关文章
|
1月前
|
编解码 数据安全/隐私保护 计算机视觉
Opencv学习笔记(十):同步和异步(多线程)操作打开海康摄像头
如何使用OpenCV进行同步和异步操作来打开海康摄像头,并提供了相关的代码示例。
73 1
Opencv学习笔记(十):同步和异步(多线程)操作打开海康摄像头
|
26天前
|
存储 NoSQL Redis
Redis 新版本引入多线程的利弊分析
【10月更文挑战第16天】Redis 新版本引入多线程是一个具有挑战性和机遇的改变。虽然多线程带来了一些潜在的问题和挑战,但也为 Redis 提供了进一步提升性能和扩展能力的可能性。在实际应用中,我们需要根据具体的需求和场景,综合评估多线程的利弊,谨慎地选择和使用 Redis 的新版本。同时,Redis 开发者也需要不断努力,优化和完善多线程机制,以提供更加稳定、高效和可靠的 Redis 服务。
31 1
|
1月前
线程CPU异常定位分析
【10月更文挑战第3天】 开发过程中会出现一些CPU异常升高的问题,想要定位到具体的位置就需要一系列的分析,记录一些分析手段。
61 0
|
2月前
|
存储 缓存 Java
什么是线程池?从底层源码入手,深度解析线程池的工作原理
本文从底层源码入手,深度解析ThreadPoolExecutor底层源码,包括其核心字段、内部类和重要方法,另外对Executors工具类下的四种自带线程池源码进行解释。 阅读本文后,可以对线程池的工作原理、七大参数、生命周期、拒绝策略等内容拥有更深入的认识。
137 29
什么是线程池?从底层源码入手,深度解析线程池的工作原理
|
1月前
|
安全 调度 C#
STA模型、同步上下文和多线程、异步调度
【10月更文挑战第19天】本文介绍了 STA 模型、同步上下文和多线程、异步调度的概念及其优缺点。STA 模型适用于单线程环境,确保资源访问的顺序性;同步上下文和多线程提高了程序的并发性和响应性,但增加了复杂性;异步调度提升了程序的响应性和资源利用率,但也带来了编程复杂性和错误处理的挑战。选择合适的模型需根据具体应用场景和需求进行权衡。
|
1月前
|
网络协议 安全 Java
难懂,误点!将多线程技术应用于Python的异步事件循环
难懂,误点!将多线程技术应用于Python的异步事件循环
61 0
|
1月前
|
Java 编译器 程序员
【多线程】synchronized原理
【多线程】synchronized原理
57 0
|
1月前
|
Java 应用服务中间件 API
nginx线程池原理
nginx线程池原理
31 0
|
2月前
|
设计模式 缓存 Java
谷粒商城笔记+踩坑(14)——异步和线程池
初始化线程的4种方式、线程池详解、异步编排 CompletableFuture
谷粒商城笔记+踩坑(14)——异步和线程池
|
3月前
|
Java 数据库
异步&线程池 CompletableFuture 异步编排 实战应用 【终结篇】
这篇文章通过一个电商商品详情页的实战案例,展示了如何使用`CompletableFuture`进行异步编排,以解决在不同数据库表中查询商品信息的问题,并提供了详细的代码实现和遇到问题(如图片未显示)的解决方案。
异步&线程池 CompletableFuture 异步编排 实战应用 【终结篇】