关于SolrCore引发的总结--持续更新

简介: 假期重新把之前在新浪博客里面的文字梳理了下,搬到这里。本文是SolrCore原理分析的连载之一,介绍SolrCore的原理。理解了org.apache.solr.core.SolrCore也就理解了solr,SolrCore作为solr的一个最小完备管理单元,覆盖了查询、更新、cache、分布式等全部内容。在工程上的 plugin、安全性上的权限、线程安全上的计数、扩展上的加载。。。。如果相关依赖lucene实现自己的分布式,那么SolrCore不能不学习和借鉴。

1. solrHome/corename/instanceDir/dataDir

    solrHome下面挂多个coreName,每个coreName对应一个instanceDir,相对solrHome的。dataDir是任意的,默认是相对instanceDirdataDir的送耦合,意味着多磁盘时候,IO可以均衡。  每个SolrCore管理一个instanceDir、对应一个dataDir。在权限上都是final,并且没有set方法。也就是说,solrcore一旦确定,就不允许动态调整dataDir等参数,new一个新的solrCore,配置新的dataDir。这种绑定,意味着索引的安全!dataDir的篡改是不安全的!一种扩展方法就是委托coreDecriptor来间接的调整dataDir,并且依赖reader来改变真实路径。这需要依赖getSearcher来打开。这明显违背了solrcore的初衷。


2. SolrCore成员变量全部 private 或者private final  

    注意final修饰的变量。这些变量意味着运行时不推荐调整。          

SolrResourceLoader  resourceLoader 这里有个坑,那就classload问题。第三方的loader的加载和潜入需要注意。  

UpdateHandler updateHandler 这是solrconfig中配置的,意味着变更能自动reload   RequestHandlers reqHandlers 注意这是多个handlers对象,不同reqHandler 关联他自己的Map searchComponents。尽管有多个path,每个path对应自己的reqhandlercomponent,但是,底层solrIndexSearcher是共享的,由SolrCore分配出来。也就是说,不同类型的query,可有自己的queryparse、进入lucenereader之前的各种不同的处理逻辑,但是索引是一份、reader是一份、SolrCore协调管理向外提供的SolrIndexSearcher也是一个共享的,计数来维护资源释放。          

Map updateProcessorChains;这是链式加载,意味着扩展的方便上面都是读相关,下面  

 private IndexDeletionPolicyWrapper solrDelPolicy;
      private DirectoryFactory directoryFactory;
      private IndexReaderFactory indexReaderFactory;
   
针对readermergerdirectory,直接影响性能、索引文件如何提高服务的。这个直接连接luceneAPI。这3个点的扩展是高级活,意味着reader管理、性能优化。   当然,如果需要扩展,那么,可以通过solrconfig配置updateHandlercomponents等直接的对象,通过这个对象间接的 插入其他本地逻辑。  

这样由SolrCore严格接管updateHandlerresourceLoaderreqhandler目的:使得任何对索引的操作,访问最小单元限制在SolrCore层,统一对SolrCore做计数和引用,从而SolrCore下面的readersearcher共享的生命周期和线程安全问题,统一在SolrCore层,从高层屏蔽底层细节,使得扩展在SolrCore之上的话,更加安全、更加符合solrCore设计目标。   所以,不管是eslucid等,各种在lucene基础上搭建的分布式,一定少不了一个类似SolrCore的模块,以不同实现方式,来完成和扮演SolrCore相关的角色和功能。


3. 关于计数  

SolrCore open的时候 计数++close的时候--,在-- 结果是0的时候关闭。从CoreContainer get SolrCore务必在使用完毕之后记得close一下。否则,计数不对称。      

SolrIndexSearcher是共享的,每次获取new SolrIndexSearcher对象,优先共享当前对象。new之前优先获取已经已经打开但没有注册的对象。如果是强制new searcher是一定是构建一个新的。这个在每次commitoptimizermergern等索引发生变更的地方需要重新打开searcher对象,间接打开reader。不变的部分直接引用,变的部分reopen,这样reopen速度更快。reader打开慢,是因为要加载indexreader对于index的 倒排信息。    

整个solr管理的最小完备单元(add\update\request\cache...)就是SolrCore,所有对solrCore的操作、同步,间接体现在SolrCore的计数上。    

getSearcher计数开始,往地下reader走,屏蔽细节。往上走,要求对象每次请求,每次new。也就是responsequeryparse都是一次一new的。  

kafkasolr lucene的活跃用户和使用、研究者,属于这方面的专家啊。


4. Listeners

     solrCore提供的listener 应该说是listener 链,有两条链,firstSearcherListeners newSearcherListeners。也就是说可以firstsearcher这一事件,可以通知消息给多个listenerlistener的配置顺序,决定了回调顺序。从而潜在表明:listener之间的前后顺序,最好不影响业务逻辑,或者说listener之间不共享数据,或者说顺序化本身能确保数据的不冲突。       listener被回调看,是串行通知的。也就是说是挨个挨个触发消息。这个不适合并发通知。或者需要并发通知,那么在自己的listener中完成并发转发通知。  

    listener的一个巧用:通过自定义的listener,来改变searcher行为。也就是说,当listern事件触发了,那么在这个listener事件处理中,完成SolrIndexSearcher的获取,进而获取reader信息,进而可以直接基于reader层,来完整某些特殊业务需求。例如:term list query,批量and or的优化等。

5. 各种plugin

updatehandler request handlerevent listenerdeletePolicyindex参数、qparse。。。核心入口就是 构造函数public SolrCore(String name, String dataDir, SolrConfig config, IndexSchema schema, CoreDescriptor cd)
   
这个构造函数,完成了最小、最完备、自包含的管理单元的构建。corereload也是基于这个构造函数来实现的。 而对SolrCore的管理:reloadswapremovecreateregister都是在CoreContainer中完成。也就是说SolrCore他自己内部,不存在对自己的reload,需要在外部控制下reload    

也就是说在SolrCore在具体使用时,是工作在 CoreContainer的管理下面的。SolrCore只是直接执行任务,而应用层面对SolrCoreCore层面的操作是在CoreContainer完成。    

有一个缺点就是 CoreContainer不能对SolrCore执行资源隔离,尽管不同SolrCore实例是隔离的。如果在同一个jvm进程内,多个solrCore部署在一个CoreContaner中,当某个solrCore资源消耗大的时候,就会影响其他SolrCore的运行。    

在整个plugin体系中,除了solrconfig.xml支持的灵活的配置,从查询业务角度看,能适应非常多的场景需求。另外一个非常头疼、也必须认真对待的就是classload了。搞不好就会发生加载自定义bean冲突或者加载失败。    

配置load SolrResourceLoader,这个load的构造函数,生成了一个parent= null classloader    

如果配置了共享的lib,也就是solrhome实例层面的,而不是具体core层面的lib的时候,需要重新生成libLoader。这个生成parent 是当前线程的ClassLoader,也就是调用new SolrCoreclassload  

 注意了,如果不是通过corecontainer来创建SolrCore,而是自己主动的new SolrCore,有可能 当前new SolrCore的线程所在classLoad与系统默认其他的不一样,这样后面创建的SolrCore与 之前默认启动的SolrCore之间就是不同上下文了。     URLClassLoader.newInstance(new URL[0],parent) 生成一个新的classload对象,然后replace SolrResourceerclassloader,这个classloader用来加载lib下面的jar。也就是传入lib下面的jars,并结合之前classloader的 父load,构建新的classloader
   
而其他的非lib 下面jar的 配置在solrconfig里面的默认支持的class 加载,是SolrResourceLoader 里,直接Class newIntance 生成的。    

到此基本上一个SolrCore的初始化完事了。接下来就是具体如何update 新数据、如何query索引了。这两块一个是writer、一个reader,对于writer需要加锁,对于reader是只读、共享,并且在writer完毕需要fresh
reader

6. update流程梳理    

solrCore里面,有初始化updateHanlerupdateProcessorChains。更新或者adddelete的操作,一种理解是request的一种,那么,统一在SolrRequestHandler handleRequestBody 里面执行正在操作。这里面顺便提下:solr是基于http协议来查询的是主流,全部请求都与path相关,而每个path都对应注册了自己的SolrRequestHandler,包括了查询的业务、adddeleteupdate等业务都是封装为SolrRequestHandler.同时,solr也提供了embeded操作,那就是直接架SolrCore上,直接调用SolrCore提供的方法接口。    

在具体调用线上,有三条线    

线1:直接调用SolrCore的方法,updatehandler 处理各个UpdateCommand的时候,直接调用addDocdelte等操作。    

线2:通过EmbededSolrServer,的request,间接执行core.execute(handler,req,rsp),这里面handlerhandleRequest 间接调用抽象方法handleRequestBody。具体的handler实现,会有自己的handlerRequestBody内容。对于XmlUpdateRequestHandler 就是用来处理增删改的。XmlUpdateRequestHandler extends ContentStreamHandlerBase ContentStreamHandlerBase handleRequestBody 调用了UpdateRequestProcessorChain来处理相关请求。    

线3DirectSolrConnection SolrServlet,直接从 core.execute(handler, solrReq, solrRsp ); 入口线3已经不推荐,线2solrj的默认实现了,线1是定制实现。线1对于默认实现来说,丢失了事务性。

参照ContentStreamHandlerBase .handleRequestBody,其中处理update是两阶段提交的,实现了事务特性。  

回到ContentStreamHandlerBase.handleRequestBody中,注意流程一个是documentLoader.load,一个是 handleCommit。后者是事务的操作。  

这里在真正提交时候,commit调用了UpdateRequestProcessorprocessCommit方法,processor会链式处理下一个processoorprocessCommit方法。其中UpdateRequestProcessorChain 关联不同path,对应一个该pathUpdateRequestProcessor链,注createProcessor方式,加载的是List哦,获取其实是get第一个的。public UpdateRequestProcessor createProcessor(SolrQueryRequest req, SolrQueryResponse rsp)
 {
   UpdateRequestProcessor processor = null;
   UpdateRequestProcessor last = null;
   for (int i = chain.length-1; i>=0; i--) {
     processor = chain[i].getInstance(req, rsp, last);
     last = processor == null ? last : processor;
   }
   return last;
 }
   
通常默认的时候,没有配置chain,此时默认加载LogUpdateProcessorFactory RunUpdateProcessorFactory
   
LogUpdateProcessorFactory中,只是log信息变更和转发,没有执行其他操作。RunUpdateProcessorFactory中,首先调用updateHandler.commit(cmd),然后执行 next操作。    

关注ContentStreamLoader.load,回到XMLLoader.load ,进入processUpdate逻辑,其中adding doc 进入processor processAdd逻辑,也就回到RunUpdateProcessorFactory中,其中调用了updateHandler.add(AddUpdateCommand)
 
至此,整个update的流程基本有一个全貌的认识了。


7. 关于queryquery

也是通过handler处理的。基于EmbeddedSever流程,进入SolrServerquery方法。其中QueryRequest.process方法处理请求。process又调用SolrServerrequest,回到EmbeddedServerrequest方法中来了。最后进入 core.execute( handler, req, rsp );回到与add的类似处理流程。只是,这次的handler是默认的StandardRequestHandler SearchHandler,到RequestHandlerBase,其中searcherHandlerhandleRequestBody 处理真正的请求查询计算。  这里分两个分支流程走向,一种是非shard query,一种是shard query  

shard query,会遍历执行SearchComponentprpareprocess过程。其中 SearchComponent都是在solrcore初始化的时候注入的。多个searchComponent从配置看,没有关联关系,并且代码页没有对顺序做限制,只是将debugQueryComponent放置最后。其实QueryComponent必须先执行,当有facetcomponent的时候。

 注意,如果isDebug打开了,会对各部分执行计时。  

prepare 阶段,非shardshard是共享的,  

processor部分针对shard 与非shard有分支执行。  shard部分,核心就是QueryComponent了,其中获取结果、排序、以及各种groupfacethl的结果都在里面。分布式的逻辑也有这部分。本部分细节在

9)再细说。下面讲解分布式shard的这部分的并发流程。

(8)shard搜索部分的处理逻辑。

(9)QueryComponent的process部分详解 

 (10)getSearcher()分析

目录
相关文章
|
5月前
常用工具方法--持续更新
常用工具方法--持续更新
34 0
|
存储 移动开发 JSON
前端面试题汇总大全 -- 持续更新!(一)
前端面试题汇总大全 -- 持续更新!
415 0
|
编译器 C语言 C++
【C/C++】从 C 到 C++ (持续更新)
从 C语言转到 C++ 需要注意的事项
86 1
|
移动开发 编解码 前端开发
前端面试题汇总大全 -- 持续更新!(三)
前端面试题汇总大全 -- 持续更新!
79 0
|
存储 缓存 JSON
前端面试题汇总大全 -- 持续更新!(四)
前端面试题汇总大全 -- 持续更新!
93 0
|
存储 缓存 JavaScript
前端面试题汇总大全 -- 持续更新!(二)
前端面试题汇总大全 -- 持续更新!
77 0
|
编译器 C语言 C++
【C++系列P1】带上这篇基础小宝典,进发C++!(持续更新ing~)
【C++系列P1】带上这篇基础小宝典,进发C++!(持续更新ing~)
|
Go Android开发
AS常用插件-持续更新
AS常用插件-持续更新
153 0
|
Kubernetes Perl 容器
k8s常用命令总结---持续更新
k8s常用命令总结---持续更新
196 0
|
C++ 容器
C++使用小细节--持续更新
文章目录 1. fixed 2. C++中结构体内重载运算符 3. reserve() resize() 4. 优先队列重载运算符的三种方式 方式1 友元函数 方式2 常引用 方式3 结构体之外 5. OJ数据制作(文件读写) 读取文件 写入文件
116 0
C++使用小细节--持续更新