下面关于虚拟分组全量切换做一个文字说明。
对于solr3.X 和2.X序列,虚拟分组全量的优势:使得全量切换可以分块实现,事先定义当前core有多少个虚拟子组,然后每个子组的去切换,从而全量在线切换开销大大降低。指导者 taobao sns架构师少昊!
对于solr4.X 5.X序列,暂时没有实现,不过基本流程和下面类似,不同点在reader视图更新上,细节也会发生不同,因为4.X 已经没有multiReader了。
实现基本流程和注意事项:
。预备知识
reader关系:SolrIndexSearcher-->SolrIndexReader-->MultiReader-->IndexReader
reader关闭:SolrIndexSearcher.close-->SolrIndexReade.close-->MultiReader for(;;) subReader.close
计数情况:solrCore.getSearcher() new新的searcher,然后调用IndexFactory的new IndexReader新打开reader,reader计数++; 如果是先new底层reaner,new的时候计数为1,然后由MultiReader封装的时候,计数又会+1;然后MultiReader里面如果closeSubReader的话,就关闭了,如果非closeSubReader,计数继续++
getSearcher:通过getSearcher来打开新的索引视图和管理计算
solrCore.getSearcher() 获取SolrIndexSearcher对象,同时根据参数执行new SolrIndexReader,并关闭SolrIndexSearcher,然后关闭SolrIndexReader
Future[] waitSearcher = new Future[1];
currentCore.getSearcher(true, false, waitSearcher);//参数必须是true false
if (waitSearcher != null && waitSearcher[0] != null) {
try {
waitSearcher[0].get();
} catch (InterruptedException e1) {
logger.error("InterruptExeption===> ", e1);
} catch (ExecutionException e2) {
logger.error("ExecutionException===>", e2);
}
}
MultiReader的继承或者重写(由与包权限,需要自己将一些方法弄出来,内容一样权限打开而已)的时候,getSequentialSubReaders() 方法,只在需要显示控制路由的时候不返回Null,否则需要返回Null。
eg IndexRaderfactory的new IndexReader 里面 new SolrIndexReader 传入RealTimeMultiReader4VroutExtend(RealTimeMultiReader4VroutExtend代表当前core管理的 多个虚拟子目录的Reader),此时RealTimeMultiReader4VroutExtend里面的getSequentialSubReaders务必返回null。返回Null表示这一层表示由显示控制路由信息。
而RealTimeMultiReader4VroutExtend对于core这一层的最顶层reader,里面的每个子reader 就对于每个子目录。由于子目录是遍历查询,传入RealTimeMultiReader4VroutExtend的每个子目录RealTimeRamFlushMainReaders reader 也是一个MultiReader,此时MultiReader查询是遍历,getSequentialSubReaders 直接返回全部的subReaders 就ok了
关于MultiReader的Reopen和initalize 方法
reopen里面的默认参数closeSubReaders和reader计数需要注意,initialize里面的参数是否closeSubReaders需要注意。eg 在索引提交或者索引切换的时候,由与多个子目录,每次重新getSearcher、重新new SolrIndexReader、new RealTimeMultiReader4VroutExtend、new 需要切换或者commit的RealTimeRamFlushMainReaders ,而RealTimeRamFlushMainReaders里面只替换部分,遍历的时候其他的reader计数会递增,那么dec的时候需要while 循环来关,最后finally 关闭本地cache资源
。基于core的虚拟分组原理
就是将当前core虚拟分为多个子目录,每个子目录管理自己的主磁盘索引、内存索引、从磁盘索引、本地内存等。建索引的时候,根据id将数据路由到各子组。检索的时候,根据id将请求路由到对于子组。全量切换的时候,执行单个组的切换,从而内存最大开销多了一份:max(单组资源开销),比之前maxCore开销大大降低内存、磁盘负载。例如:原理core 100M,切换就额外100M,如果分为5组,每组25M(分组后额外的开销还是有的),切换就额外25M,总体150M,比200M省了50M。
另外,通过自定义的reader,引入本地内存来管理固定部分的数据,并且采取一定压缩算法,从而使得大内存的gc问题得以解决。
。基于core虚拟分组工作关键流程
(1) solrconfig.xml中配置IndexReaderFactory
(2)在发生索引commit的时候,设置是否更新索引视图参数,接着通过SolrCore的getSearcher方法来间接地调用 IndexReaderFactory的new IndexReader方法来更新索引目录,从而使得新的索引可见。
(3)在发生索引core的子组切换的时候,设置那个子组需要切换的参数,接着通过SolrCore的getSearcher方法来间接地调用 IndexReaderFactory的new IndexReader方法来更新索引目录,从而使得新的索引可见。
(4)在SolrIndexSearcher关闭的时候,调用close方法,间接调用reader.decRef,然后调用SolrIndexReader 的defRef方法里面的in.decRef,进入在IndexReaderFactory new SolrIndexReader中的参数reader里面,例如
RealTimeMultiReader4VroutExtend的 decRef方法,在decRef 中之下doClose方法,RealTimeMultiReader4VroutExtend的doClose方法里面遍历关闭每个子目录
protected void doClose() throws IOException {
// TODO Auto-generated method stub
for (int i = 0; i < subReaders.length; i++) {
//logger.warn("[lev1] doCloseSubDir="+i+",RefCount="+subReaders[i].getRefCount());
if (decrefOnClose[i]) {
subReaders[i].decRef();
} else {
subReaders[i].close();
}
}
}
每个子目录对应RealTimeRamFlushMainReaders,进入RealTimeRamFlushMainReaders的 decRef方法里面。这里面根据遍历subReader的 decRef,进入最底层的 decRef 方法中,这个最底层的reader就是业务自定义、封装了本地cache的reander。如果是遍历关闭某个子目录,而其他子目录会因为其他目录的关闭导致的new 新的引用,计数会增多。所以关闭的时候需要while来减少计数。
。基于core虚拟分组example
solrconfig.xml 配置
<indexReaderFactory name="IndexReaderFactory" class="com.taobao.terminator.core.realtime.vrout.RealTimeIndexReaderFactory4VroutExtend">在
RealTimeIndexReaderFactory4VroutExten里面间接地调用自定义的reader
SolrIndexSearcher--》SolrIndexReader(RealTimeMultiReader4VroutExtend)//
RealTimeMultiReader4VroutExtend 也是一个multiReader,core层的Reader抽象
RealTimeMultiReader4VroutExtend-->{RealTimeRamFlushMainReaders,RealTimeRamFlushMainReaders ,RealTimeRamFlushMainReaders..}
//他的元素就是RealTimeRamFlushMainReaders 子目录的抽象
RealTimeRamFlushMainReaders-->{RealTimeIndexReader4VroutExtend,RealTimeIndexReader4VroutExtend,RealTimeIndexReader4VroutExtend ...}
//他的元素就是RealTimeIndexReader4VroutExtend,对于自定义reader,也即子目录的具体reader
。基于core虚拟分组其他注意事项
由于SolrIndexSearcher对象是core层次的,查询又是路由到子目录的,从而各种默认绑定在SolrIndexSearcher层cache,特别是DocumentResultCache就失效了。例如 数据分2组,id=1 路由子目录1,返回id=1,id=3,id=5的文档,内部id号 分别是0,1,2,查询id=2,路由子目录2,返回id=2,id=4,id=6,内部id分别是0,1,2,此时如果docuementResultCache开启了,根据内部id号,获取的文档就不对了。
如果需要cache的话,需要在查询的时候传入路由信息,单独定义个cache配置到solrconfig中,然后处理cache的时候根据路由选择对应cache内容。因为实时、并且将core细分,cache的命中可能降低。cache的选择和位置,需要平衡。