前几天把公司的solr配置成了solrcloud,好处就不说了,可是发现主节点和从节点的时间字段不一样,其他字段的值都是相同的,一开始觉得很诡异,网上搜索相关的问题也没有找到答案,只能看看源码了。由于solrcloud是通过zookeeper来进行管理的,不是跑在eclipse上的,所以想要调试基本上是不可能的,我只能通过猜测来定位了。
在发起索引建立的请求后首先会通过filter然后来到DataImportHandler的hadleRequestBody(req,rsp)方法,主要代码如下:
处理器工厂创建处理器方法:
获取SolrWriter进行索引创建,processor就是上面的处理器,索引创建主要是靠upload方法完成:
接下来看runAsync和runCmd两个方法,runAsync从字面看就是以异步方式进行,无非就是重新开一个线程执行runCmd方法,进到runCmd方法,主要代码如下:
接着进入doFullImport方法:
进入DocBuilder.execute():
进入cleanByQuery方法:
好了删除的先说到这里,先说增加索引吧,因为问题是出在增加索引的时候,看完代码会发现增加和删除的基本上一样的流程。
进入doFullDump()方法:
进入nextRow()方法:
进入SolrWriter的upload()方法:
进入LogUpdateProcessor.processAdd()方法:
下面进入重点的重点DistributedUpdateProcessor.processAdd()方法,看名字就知道是为分布式而生的,当然你的solr如果不是分布式的也会进入到这里的,具体判断见代码分析:
进入getHashableId()方法,这个方法很重要,判断将当前索引放到哪个节点就是通过获取这个索引的uniqueKey的值来进行hash计算定位放到哪个shard,如果通过该值没有获取到某个具体的shard则solrcloud会默认选择该Document所在的那个shard:
SetupRequest方法中有 Slice slice = coll.getRouter().getTargetSlice(id, doc, req.getParams(), coll);这行代码:通过上面获取到的id来定位目标分片。进入getTargetSlice()方法:
protected int sliceHash(String id, SolrInputDocument sdoc, SolrParams params) {
return Hash.murmurhash3_x86_32(id, 0, id.length(), 0);
}
protected Slice hashToSlice(int hash, DocCollection collection) {
//collection.getSlices()获取该collection的所有分片也就是所有节点
for (Slice slice : collection.getSlices()) {
//计算该节点的索引存储范围,范围计算见下面
Range range = slice.getRange();
if (range != null && range.includes(hash)) return slice;
}
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "No slice servicing hash code " + Integer.toHexString(hash) + " in " + collection);
}
在完成solrcloud集群配置完成之后创建几点是通过Overseer的createCollection方法进行的:
partitionRange()方法,partitions代表节点数目,算法很简单,不细讲:
由cmdDistrib.distribAdd()方法最终会到sumit()(注意每个节点都会调用)方法:
server.request()方法中重要的代码Collection streams = requestWriter.getContentStreams(request);
进入UpdateRequestExt的getContentStreams()方法,将Document构造成一个xml文件并转换成字符流作为参数进行http请求,收到请求后会以xml的方式添加索引(这个过程就很简单了,会调用lucene的IndexWriter):
进入ClientUtil.writeXML()方法:
继续跟进writeVal():
看到这里就豁然开朗了,第一行代码的if(v instance Date),由于数据库的时间字段是Timestamp extends Date类型的,所以这里又将字符串时间值进行了转换,所以该索引的时间字段值就和主节点的值不一样了。
解决方法可以为entity加上自定义的transformer主动将时间字段值toString(),在上面的nextRow()操作就会调用这个transformer,到了writeVal中v就不再是Date类型了,还有一种方法就是修改sql语句convert(varchar(19),120,updateTime) as updateTime,当然推荐后者了,从简单和做索引的速度来看就知道了。