在搞搜索查看本文
首先讲一讲split的好处,当索引达到一定的数量级的时候,搜索的速度或许就会达到一个瓶颈,因为数据量一旦增多,各种查询开销也会一并增加,在solrcloud中,创建索引的时候我们一般都只会创建符合当前性能需求的分片数量,但是数据假如是不可预期的增长的话,那么当当个shard变的很大的时候,我们就可以对这个shard进行拆分,使单个shard中的数据量变少,这也是集群纵向扩展的常用方法。
首先split只支持通过id hash range来区分文档shard的collection,也就是使用ImplicitDocRouter的collection是不能够split的,具体原因后面会说明。
split能够在不影响索引正常提供服务过程中进行,这个是一个亮点,但是索引过大的时候,还是推荐在服务器空闲时进行,因为split会做大量的io读写操作,原因就是索引的分离和复制当然需要读写,而且当原shard有多个复制节点的时候,split也会生成同样多的复制节点,这些节点的数据是需要从leader同步的,索引很大或者replica过多的情况下会耗费大量的网络资源。
具体工作流程:
- collectins handler接收命令,确保参数正确后,提交一个队列消息至专门放置collection相关处理消息的分布式队列
这部分是solrcloud的对分布式索引对象操作的基本流程,不详说
- overseer collection processor
overseer会启动一个collection processor,用来不停的读取这些消息进行详细的处理,这里就是读到这个消息了,然后开始做split相关操作
- clusterState分区处理
这一步其实就是将当前的hash映射范围均等的分为2部分,作为split的两个新的shard的id hash范围。然后创建两个子shard,并且把子shard的状态设置为construction,这里注意的是,假如有两个shard的状态为construction活着inactive,那么会先删掉这两个shard,然后再进行下一步操作,因为这两个shard可能是上一次创建失败留下来的产物。创建好shard后会在原来shard leader所在的那个机器上创建好两个新的core,各个core并且分别加入到新创建好的两个分区shard,对应代码:
然后会发消息给两个core节点,等待它们更新自己为active状态,进行下一步数据分割操作。String subSlice = subSlices.get(i); String subShardName = subShardNames.get(i); DocRouter.Range subRange = subRanges.get(i); log.info("Creating shard " + subShardName + " as part of slice " + subSlice + " of collection " + collectionName + " on " + nodeName); Map<String, Object> propMap = new HashMap<String, Object>(); propMap.put(Overseer.QUEUE_OPERATION, "createshard"); propMap.put(ZkStateReader.SHARD_ID_PROP, subSlice); propMap.put(ZkStateReader.COLLECTION_PROP, collectionName); propMap.put(ZkStateReader.SHARD_RANGE_PROP, subRange.toString()); propMap.put(ZkStateReader.SHARD_STATE_PROP, Slice.CONSTRUCTION); propMap.put(ZkStateReader.SHARD_PARENT_PROP, parentSlice.getName());//这里shard多了一个parent属性 ZkNodeProps m = new ZkNodeProps(propMap); DistributedQueue inQueue = Overseer.getInQueue(zkStateReader.getZkClient()); inQueue.offer(ZkStateReader.toJSON(m));//发消息给overseer队列,overseer读到后会更新clusterstate创建shard ModifiableSolrParams params = new ModifiableSolrParams(); params.set(CoreAdminParams.ACTION, CoreAdminAction.CREATE.toString()); params.set(CoreAdminParams.NAME, subShardName); params.set(CoreAdminParams.COLLECTION, collectionName); params.set(CoreAdminParams.SHARD, subSlice); addPropertyParams(message, params); sendShardRequest(nodeName, params);//发送创建replica(core)的消息给对应的节点,这个请求是core admin handler处理的
- 索引数据分割处理
其实这个才是重头戏的开始,其实也不是什么高深的技术,在solrcloud之前,这个功能就已经存在了,就是core split,collection split就是借这个做了一部分分布式方面的处理,底层还是靠这个,这里要注意的一个核心的方法就是IndexWriter.addIndexes(IndexReader... readers)
OverseerCollectionHandler继续进行处理,发送core split消息给需要分割的core:
CoreAdminHandler接受到请求后先进行一系列的判断,判断各种条件满足后,提交一个SplitIndexCommand到core的UpdateHandler进行处理,最终还是交给一个叫做SolrIndexSplitter的类来进行分割操作。ModifiableSolrParams params = new ModifiableSolrParams(); params.set(CoreAdminParams.ACTION, CoreAdminAction.SPLIT.toString());//split操作 params.set(CoreAdminParams.CORE, parentShardLeader.getStr("core"));//split源为parent shard,即需要分割的shard for (int i = 0; i < subShardNames.size(); i++) { String subShardName = subShardNames.get(i); params.add(CoreAdminParams.TARGET_CORE, subShardName);//目标源为新分割出来的小shard } params.set(CoreAdminParams.RANGES, rangesStr);//同时带上需要分割处理的id hash范围 sendShardRequest(parentShardLeader.getNodeName(), params);//发请求处理
SolrIndexSplitter里面需要留意一下的是split方法,这个方法通过从hash range来定位document id将整个reader的docid分割成多个BitSet,有了这个bitset的话,就容易多了。有一个简单的内部类LiveDocsReader,这个类对原来core的所有AtomicReader进行了过滤,让getLiveDocs只返回需要分割出去的的doc,最后调用IndexWriter.addIndexes将数据分割到新的索引中去,IndexWriter.addIndexes所做的就是一个lucene segment merge操作,有了codec,这个操作还是比较方便了。 - 后期处理
后期处理主要是做一些收尾的操作,比如给各个新的core发送一个提交updatelog的命令,因为刚才在addIndexes操作中,有些数据会缓存在updatelog,这里提交一下。然后就是根据源parent_shard的replica数量给新split出来的shard也创建相同数量的replica,这部分主要就是索引的recovery了,最后发布shard的状态,如果源shard只有一个单节点,即没有replica的话,直接将新的shard发布成active,旧的shard发布成inactive,旧的shard不再接受请求。如果有多个replica需要进行recovery操作的话,新的shard都会被发布成recovery操作,旧shard状态不变,一旦recovery操作完成。overseer自动将新shard发布成active,旧shard发布成inactive。至此,整个shard 的split操作就完成了。还有,inactive状态的shard可以用collection api中的DELETESHARD删除掉,active的shard是不能被删除的。
全文完