深入剖析SolrCloud(四)

 在上一篇中介绍了连接Zookeeper集群的方法,这一篇将围绕一个有趣的话题---来展开,这就是Replication(索引复制),关于Solr Replication的详细介绍,可以参考http://wiki.apache.org/solr/SolrReplication。

         在开始这个话题之前,先从我最近在应用中引入solr的master/slave架构时,遇到的一个让我困扰的实际问题。
应用场景简单描述如下:
1)首先master节点下载索引分片,然后创建配置文件,加入master节点的replication配置片段,再对索引分片进行合并(关于mergeIndex,可以参考http://wiki.apache.org/solr/MergingSolrIndexes),然后利用上述配置文件和索引数据去创建一个solr核。
2)slave节点创建配置文件,加入slave节点的replication配置片段,创建一个空的solr核,等待从master节点进行索引数据同步
出现的问题:slave节点没有从master节点同步到数据。
问题分析:
1)首先检查master节点,获取最新的可复制索引的版本号,
http://master_host:port/solr/replication?command=indexversion
发现返回的索引版本号是0,这说明mater节点根本没有触发replication动作,
2)为了确认上述判断,在slave节点上进一步查看replication的详细信息
http://slave_host:port/solr/replication?command=details
发现确实如此,尽管master节点的索引版本号和slave节点的索引版本号不一致,但索引却没有同步过来,再分别查看master节点和slave节点的日志,发现索引复制动作确实没有开始。
综上所述,确实是master节点没有触发索引复制动作,那究竟是为何呢?先将原因摆出来,后面会通过源码的分析来加以说明。
原因:solr合并索引时,不管你是通过mergeindexes的http命令,还是调用底层lucene的IndexWriter,记得最后一定要提交一个commit,否则,不仅索引不仅不会对查询可见,更是对于master/slave架构的solr集群来说,master节点的replication动作不会触发,因为indexversion没有感知到变化。
         好了,下面开始对Solr的Replication的分析。
         Solr容器在加载solr核的时候,会对已经注册的各个实现SolrCoreAware接口的Handler进行回调,调用其inform方法。
         对于ReplicationHandler来说,就是在这里对自己是属于master节点还是slave节点进行判断,若是slave节点,则创建一个SnapPuller对象,定时负责从master节点主动拉索引数据下来;若是master节点,则只设置相应的参数。
 
  
  
  
  
  1. public void inform(SolrCore core) { 
  2.     this.core = core; 
  3.     registerFileStreamResponseWriter(); 
  4.     registerCloseHook(); 
  5.     NamedList slave = (NamedList) initArgs.get("slave"); 
  6.     boolean enableSlave = isEnabled( slave ); 
  7.     if (enableSlave) { 
  8.       tempSnapPuller = snapPuller = new SnapPuller(slave, this, core); 
  9.       isSlave = true
  10.     } 
  11.     NamedList master = (NamedList) initArgs.get("master"); 
  12.     boolean enableMaster = isEnabled( master ); 
  13.      
  14.     if (!enableSlave && !enableMaster) { 
  15.       enableMaster = true
  16.       master = new NamedList<Object>(); 
  17.     } 
  18.      
  19.     if (enableMaster) { 
  20.       includeConfFiles = (String) master.get(CONF_FILES); 
  21.       if (includeConfFiles != null && includeConfFiles.trim().length() > 0) { 
  22.         List<String> files = Arrays.asList(includeConfFiles.split(",")); 
  23.         for (String file : files) { 
  24.           if (file.trim().length() == 0) continue
  25.           String[] strs = file.split(":"); 
  26.           // if there is an alias add it or it is null 
  27.           confFileNameAlias.add(strs[0], strs.length > 1 ? strs[1] : null); 
  28.         } 
  29.         LOG.info("Replication enabled for following config files: " + includeConfFiles); 
  30.       } 
  31.       List backup = master.getAll("backupAfter"); 
  32.       boolean backupOnCommit = backup.contains("commit"); 
  33.       boolean backupOnOptimize = !backupOnCommit && backup.contains("optimize"); 
  34.       List replicateAfter = master.getAll(REPLICATE_AFTER); 
  35.       replicateOnCommit = replicateAfter.contains("commit"); 
  36.       replicateOnOptimize = !replicateOnCommit && replicateAfter.contains("optimize"); 
  37.  
  38.       if (!replicateOnCommit && ! replicateOnOptimize) { 
  39.         replicateOnCommit = true
  40.       } 
  41.        
  42.       // if we only want to replicate on optimize, we need the deletion policy to 
  43.       // save the last optimized commit point. 
  44.       if (replicateOnOptimize) { 
  45.         IndexDeletionPolicyWrapper wrapper = core.getDeletionPolicy(); 
  46.         IndexDeletionPolicy policy = wrapper == null ? null : wrapper.getWrappedDeletionPolicy(); 
  47.         if (policy instanceof SolrDeletionPolicy) { 
  48.           SolrDeletionPolicy solrPolicy = (SolrDeletionPolicy)policy; 
  49.           if (solrPolicy.getMaxOptimizedCommitsToKeep() < 1) { 
  50.             solrPolicy.setMaxOptimizedCommitsToKeep(1); 
  51.           } 
  52.         } else { 
  53.           LOG.warn("Replication can't call setMaxOptimizedCommitsToKeep on " + policy); 
  54.         } 
  55.       } 
  56.  
  57.       if (replicateOnOptimize || backupOnOptimize) { 
  58.         core.getUpdateHandler().registerOptimizeCallback(getEventListener(backupOnOptimize, replicateOnOptimize)); 
  59.       } 
  60.       if (replicateOnCommit || backupOnCommit) { 
  61.         replicateOnCommit = true
  62.         core.getUpdateHandler().registerCommitCallback(getEventListener(backupOnCommit, replicateOnCommit)); 
  63.       } 
  64.       if (replicateAfter.contains("startup")) { 
  65.         replicateOnStart = true
  66.         RefCounted<SolrIndexSearcher> s = core.getNewestSearcher(false); 
  67.         try { 
  68.           DirectoryReader reader = s==null ? null : s.get().getIndexReader(); 
  69.           if (reader!=null && reader.getIndexCommit() != null && reader.getIndexCommit().getGeneration() != 1L) { 
  70.             try { 
  71.               if(replicateOnOptimize){ 
  72.                 Collection<IndexCommit> commits = DirectoryReader.listCommits(reader.directory()); 
  73.                 for (IndexCommit ic : commits) { 
  74.                   if(ic.getSegmentCount() == 1){ 
  75.                     if(indexCommitPoint == null || indexCommitPoint.getGeneration() < ic.getGeneration()) indexCommitPoint = ic; 
  76.                   } 
  77.                 } 
  78.               } else
  79.                 indexCommitPoint = reader.getIndexCommit(); 
  80.               } 
  81.             } finally { 
  82.               // We don't need to save commit points for replication, the SolrDeletionPolicy 
  83.               // always saves the last commit point (and the last optimized commit point, if needed) 
  84.               /*** 
  85.               if(indexCommitPoint != null){ 
  86.                 core.getDeletionPolicy().saveCommitPoint(indexCommitPoint.getGeneration()); 
  87.               } 
  88.               ***/ 
  89.             } 
  90.           } 
  91.  
  92.           // reboot the writer on the new index 
  93.           core.getUpdateHandler().newIndexWriter(); 
  94.  
  95.         } catch (IOException e) { 
  96.           LOG.warn("Unable to get IndexCommit on startup", e); 
  97.         } finally { 
  98.           if (s!=null) s.decref(); 
  99.         } 
  100.       } 
  101.       String reserve = (String) master.get(RESERVE); 
  102.       if (reserve != null && !reserve.trim().equals("")) { 
  103.         reserveCommitDuration = SnapPuller.readInterval(reserve); 
  104.       } 
  105.       LOG.info("Commits will be reserved for  " + reserveCommitDuration); 
  106.       isMaster = true
  107.     } 
  108. }  

ReplicationHandler可以响应多种命令: 1) indexversion。 这里需要了解的第一个概念是索引提交点(IndexCommit),这是底层lucene的东西,可以自行查阅资料。首先获取最新的索引提交点,然后从其中获取索引版本号和索引所属代。

 

  
  
  
  
  1. IndexCommit commitPoint = indexCommitPoint;  // make a copy so it won't change 
  2.      if (commitPoint != null && replicationEnabled.get()) { 
  3.        core.getDeletionPolicy().setReserveDuration(commitPoint.getVersion(), reserveCommitDuration); 
  4.        rsp.add(CMD_INDEX_VERSION, commitPoint.getVersion()); 
  5.     rsp.add(GENERATION, commitPoint.getGeneration());   

2)backup。这个命令用来对索引做快照。首先获取最新的索引提交点,然后创建做一个SnapShooter,具体的快照动作由这个对象完成,

 

  
  
  
  
  1.   private void doSnapShoot(SolrParams params, SolrQueryResponse rsp, SolrQueryRequest req) {  
  2.     try { 
  3.       int numberToKeep = params.getInt(NUMBER_BACKUPS_TO_KEEP, Integer.MAX_VALUE); 
  4.       IndexDeletionPolicyWrapper delPolicy = core.getDeletionPolicy(); 
  5.       IndexCommit indexCommit = delPolicy.getLatestCommit(); 
  6.        
  7.       if(indexCommit == null) { 
  8.         indexCommit = req.getSearcher().getReader().getIndexCommit(); 
  9.       } 
  10.        
  11.       // small race here before the commit point is saved 
  12.       new SnapShooter(core, params.get("location")).createSnapAsync(indexCommit, numberToKeep, this); 
  13.        
  14.     } catch (Exception e) { 
  15.       LOG.warn("Exception during creating a snapshot", e); 
  16.       rsp.add("exception", e); 
  17.     } 
  18.   } 
  19.   
  20. 快照对象会启动一个线程去异步地做一个索引备份。 
  21. void createSnapAsync(final IndexCommit indexCommit, final int numberToKeep, final ReplicationHandler replicationHandler) { 
  22.     replicationHandler.core.getDeletionPolicy().saveCommitPoint(indexCommit.getVersion()); 
  23.   
  24.     new Thread() { 
  25.       @Override 
  26.       public void run() { 
  27.         createSnapshot(indexCommit, numberToKeep, replicationHandler); 
  28.       } 
  29.     }.start(); 
  30.  } 
  31.   
  32.  void createSnapshot(final IndexCommit indexCommit, int numberToKeep, ReplicationHandler replicationHandler) { 
  33.     NamedList details = new NamedList(); 
  34.     details.add("startTime", new Date().toString()); 
  35.     File snapShotDir = null
  36.     String directoryName = null
  37.     Lock lock = null
  38.     try { 
  39.       if(numberToKeep<Integer.MAX_VALUE) { 
  40.         deleteOldBackups(numberToKeep); 
  41.       } 
  42.       SimpleDateFormat fmt = new SimpleDateFormat(DATE_FMT, Locale.US); 
  43.       directoryName = "snapshot." + fmt.format(new Date()); 
  44.       lock = lockFactory.makeLock(directoryName + ".lock"); 
  45.       if (lock.isLocked()) return
  46.       snapShotDir = new File(snapDir, directoryName); 
  47.       if (!snapShotDir.mkdir()) { 
  48.         LOG.warn("Unable to create snapshot directory: " + snapShotDir.getAbsolutePath()); 
  49.         return
  50.       } 
  51.       Collection<String> files = indexCommit.getFileNames(); 
  52.       FileCopier fileCopier = new FileCopier(solrCore.getDeletionPolicy(), indexCommit); 
  53.       fileCopier.copyFiles(files, snapShotDir); 
  54.   
  55.       details.add("fileCount", files.size()); 
  56.       details.add("status""success"); 
  57.       details.add("snapshotCompletedAt", new Date().toString()); 
  58.     } catch (Exception e) { 
  59.       SnapPuller.delTree(snapShotDir); 
  60.       LOG.error("Exception while creating snapshot", e); 
  61.       details.add("snapShootException", e.getMessage()); 
  62.     } finally { 
  63.       replicationHandler.core.getDeletionPolicy().releaseCommitPoint(indexCommit.getVersion());   
  64.       replicationHandler.snapShootDetails = details; 
  65.       if (lock != null) { 
  66.         try { 
  67.           lock.release(); 
  68.         } catch (IOException e) { 
  69.           LOG.error("Unable to release snapshoot lock: " + directoryName + ".lock"); 
  70.         } 
  71.       } 
  72.     } 
  73.   } 
  74. 3)fetchindex。响应来自slave节点的取索引文件的请求,会启动一个线程来实现索引文件的获取。 
  75.       String masterUrl = solrParams.get(MASTER_URL); 
  76.       if (!isSlave && masterUrl == null) { 
  77.         rsp.add(STATUS,ERR_STATUS); 
  78.         rsp.add("message","No slave configured or no 'masterUrl' Specified"); 
  79.         return
  80.       } 
  81.       final SolrParams paramsCopy = new ModifiableSolrParams(solrParams); 
  82.       new Thread() { 
  83.         @Override 
  84.         public void run() { 
  85.           doFetch(paramsCopy); 
  86.         } 
  87.       }.start(); 
  88.       rsp.add(STATUS, OK_STATUS); 
  89. 具体的获取动作是通过SnapPuller对象来实现的,首先尝试获取pull对象锁,如果请求锁失败,则说明还有取索引数据动作未结束,如果请求锁成功,就调用SnapPuller对象的fetchLatestIndex方法来取最新的索引数据。 
  90.  void doFetch(SolrParams solrParams) { 
  91.     String masterUrl = solrParams == null ? null : solrParams.get(MASTER_URL); 
  92.     if (!snapPullLock.tryLock()) 
  93.       return
  94.     try { 
  95.       tempSnapPuller = snapPuller; 
  96.       if (masterUrl != null) { 
  97.         NamedList<Object> nl = solrParams.toNamedList(); 
  98.         nl.remove(SnapPuller.POLL_INTERVAL); 
  99.         tempSnapPuller = new SnapPuller(nl, this, core); 
  100.       } 
  101.       tempSnapPuller.fetchLatestIndex(core); 
  102.     } catch (Exception e) { 
  103.       LOG.error("SnapPull failed ", e); 
  104.     } finally { 
  105.       tempSnapPuller = snapPuller; 
  106.       snapPullLock.unlock(); 
  107.     } 
  108.  } 
  109. 最后真正的取索引数据过程,首先,若mastet节点的indexversion为0,则说明master节点根本没有提供可供复制的索引数据,若master节点和slave节点的indexversion相同,则说明slave节点目前与master节点索引数据状态保持一致,无需同步。若两者的indexversion不同,则开始索引复制过程,首先从master节点上下载指定索引版本号的索引文件列表,然后创建一个索引文件同步服务线程来完成同并工作。 
  110. 这里需要区分的是,如果master节点的年代比slave节点要老,那就说明两者已经不相容,此时slave节点需要新建一个索引目录,再从master节点做一次全量索引复制。还需要注意的一点是,索引同步也是可以同步配置文件的,若配置文件发生变化,则需要对solr核进行一次reload操作。最对了,还有,和文章开头一样, slave节点同步完数据后,别忘了做一次commit操作,以便刷新自己的索引提交点到最新的状态。最后,关闭并等待同步服务线程结束。此外,具体的取索引文件是通过FileFetcher对象来完成。 
  111.  boolean fetchLatestIndex(SolrCore core) throws IOException { 
  112.     replicationStartTime = System.currentTimeMillis(); 
  113.     try { 
  114.       //get the current 'replicateable' index version in the master 
  115.       NamedList response = null
  116.       try { 
  117.         response = getLatestVersion(); 
  118.       } catch (Exception e) { 
  119.         LOG.error("Master at: " + masterUrl + " is not available. Index fetch failed. Exception: " + e.getMessage()); 
  120.         return false
  121.       } 
  122.       long latestVersion = (Long) response.get(CMD_INDEX_VERSION); 
  123.       long latestGeneration = (Long) response.get(GENERATION); 
  124.       if (latestVersion == 0L) { 
  125.         //there is nothing to be replicated 
  126.         return false
  127.       } 
  128.       IndexCommit commit
  129.       RefCounted<SolrIndexSearcher> searcherRefCounted = null
  130.       try { 
  131.         searcherRefCounted = core.getNewestSearcher(false); 
  132.         commit = searcherRefCounted.get().getReader().getIndexCommit(); 
  133.       } finally { 
  134.         if (searcherRefCounted != null
  135.           searcherRefCounted.decref(); 
  136.       } 
  137.       if (commit.getVersion() == latestVersion && commit.getGeneration() == latestGeneration) { 
  138.         //master and slave are alsready in sync just return 
  139.         LOG.info("Slave in sync with master."); 
  140.         return false
  141.       } 
  142.       LOG.info("Master's version: " + latestVersion + ", generation: " + latestGeneration); 
  143.       LOG.info("Slave's version: " + commit.getVersion() + ", generation: " + commit.getGeneration()); 
  144.       LOG.info("Starting replication process"); 
  145.       // get the list of files first 
  146.       fetchFileList(latestVersion); 
  147.       // this can happen if the commit point is deleted before we fetch the file list. 
  148.       if(filesToDownload.isEmpty()) return false
  149.       LOG.info("Number of files in latest index in master: " + filesToDownload.size()); 
  150.   
  151.       // Create the sync service 
  152.       fsyncService = Executors.newSingleThreadExecutor(); 
  153.       // use a synchronized list because the list is read by other threads (to show details) 
  154.       filesDownloaded = Collections.synchronizedList(new ArrayList<Map<String, Object>>()); 
  155.       // if the generateion of master is older than that of the slave , it means they are not compatible to be copied 
  156.       // then a new index direcory to be created and all the files need to be copied 
  157.       boolean isFullCopyNeeded = commit.getGeneration() >= latestGeneration; 
  158.       File tmpIndexDir = createTempindexDir(core); 
  159.       if (isIndexStale()) 
  160.         isFullCopyNeeded = true
  161.       successfulInstall = false
  162.       boolean deleteTmpIdxDir = true
  163.       File indexDir = null ; 
  164.       try { 
  165.         indexDir = new File(core.getIndexDir()); 
  166.         downloadIndexFiles(isFullCopyNeeded, tmpIndexDir, latestVersion); 
  167.         LOG.info("Total time taken for download : " + ((System.currentTimeMillis() - replicationStartTime) / 1000) + " secs"); 
  168.         Collection<Map<String, Object>> modifiedConfFiles = getModifiedConfFiles(confFilesToDownload); 
  169.         if (!modifiedConfFiles.isEmpty()) { 
  170.           downloadConfFiles(confFilesToDownload, latestVersion); 
  171.           if (isFullCopyNeeded) { 
  172.             successfulInstall = modifyIndexProps(tmpIndexDir.getName()); 
  173.             deleteTmpIdxDir = false
  174.           } else { 
  175.             successfulInstall = copyIndexFiles(tmpIndexDir, indexDir); 
  176.           } 
  177.           if (successfulInstall) { 
  178.             LOG.info("Configuration files are modified, core will be reloaded"); 
  179.             logReplicationTimeAndConfFiles(modifiedConfFiles, successfulInstall);//write to a file time of replication and conf files. 
  180.             reloadCore(); 
  181.           } 
  182.         } else { 
  183.           terminateAndWaitFsyncService(); 
  184.           if (isFullCopyNeeded) { 
  185.             successfulInstall = modifyIndexProps(tmpIndexDir.getName()); 
  186.             deleteTmpIdxDir = false
  187.           } else { 
  188.             successfulInstall = copyIndexFiles(tmpIndexDir, indexDir); 
  189.           } 
  190.           if (successfulInstall) { 
  191.             logReplicationTimeAndConfFiles(modifiedConfFiles, successfulInstall); 
  192.             doCommit(); 
  193.           } 
  194.         } 
  195.         replicationStartTime = 0; 
  196.         return successfulInstall; 
  197.       } catch (ReplicationHandlerException e) { 
  198.         LOG.error("User aborted Replication"); 
  199.       } catch (SolrException e) { 
  200.         throw e; 
  201.       } catch (Exception e) { 
  202.         throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Index fetch failed : ", e); 
  203.       } finally { 
  204.         if (deleteTmpIdxDir) delTree(tmpIndexDir); 
  205.         else delTree(indexDir); 
  206.       } 
  207.       return successfulInstall; 
  208.     } finally { 
  209.       if (!successfulInstall) { 
  210.         logReplicationTimeAndConfFiles(null, successfulInstall); 
  211.       } 
  212.       filesToDownload = filesDownloaded = confFilesDownloaded = confFilesToDownload = null
  213.       replicationStartTime = 0; 
  214.       fileFetcher = null
  215.       if (fsyncService != null && !fsyncService.isShutdown()) fsyncService.shutdownNow(); 
  216.       fsyncService = null
  217.       stop = false
  218.       fsyncException = null
  219.     } 
  220.  }  

 

你可能感兴趣的:(java,随笔,java语言,solrCloud,休闲)