master的工作
对于ReplicationHandler的复制功能来说,核心的问题确定是在一个时间点要复制哪些文件,这就用上了lucene的IndexDeletionPolicy的特性。
lucene在初始化时,会调用IndexDeletionPolicy.onInit(List commits)方法;
lucene在commit(触发的时机也可以是optimize、close,solr在commit时实际上就是close了indexwriter)时,会调用IndexDeletionPolicy.onInit(List commits)。
IndexCommit对象中保存了该次提交关联的文件列表等信息,这使得solr中的复制过程中,slave可以从master得到文件列表后跟本地文件做比较,跳过不变的文件,下载新文件,并删除无用的文件。
IndexDeletionPolicy的两个针对commits的函数,会对当前存在的commits列表做些处理,比如lucene默认的KeepOnlyLastCommitDeletionPolicy会只保留最新的IndexCommit,对那些过时的IndexCommit执行delete操作以将无用的文件删掉。
solr中,SolrDeletionPolicy默认也是保留最新一个IndexCommit,但可以设置maxCommitAge、maxCommitsToKeep、maxOptimizedCommitsToKeep来保留更多的IndexCommit。但solr真正使用的IndexDeletionPolicy实现是IndexDeletionPolicyWrapper,它是SolrDeletionPolicy的wrap。
在slave从master复制文件的过程中,要保证当前正在复制的IndexCommit点不能被删除,这就用到了IndexDeletionPolicyWrapper中的void setReserveDuration(Long indexVersion, long reserveTime)方法,该方法会在master向slave响应indexversion、filelist命令前、以及每向slave传送5M的索引文件内容时调用,而默认的reserveTime时间是10s,如果慢速网络传输5M数据需要10秒以上,就需要调整该值了。
ReplicationHandler复制文件没有采用rsync,而是使用http,它在读一个文件内容传输到slave时,默认是按照1M大小分段输出内容到slave(http chunked?),并且默认是对每段内容做了checksum,保证传输的内容的正确性。上面提到的setReserveDuration点,主要就是它在packetsWritten % 5 == 0次数后触发一次修改。
ReplicationHandler还可以备份索引文件。由于lucene的索引文件只是追加新文件而不会修改已有文件,所以只要针对一个IndexCommit点做备份,其过程还是很简单的。
slave的工作
slave启动时会创建SnapPuller对象,SnapPuller会启动一个线程定时的(pollInterval间隔)从master复制数据(fetchLatestIndex方法)。对于一次复制过程,slave和master交互处理细节如下:
1、slave首先向master询问最新的索引版本号(indexversion命令),slave检查得到的latestVersion、latestGeneration有效后,和本地的IndexCommit的getVersion()、getGeneration()比较,如果不相等,则需要往下进行,否则等待下一次调度。
2、slave向master请求之前得到的indexversion下的文件列表(filelist命令,包括索引文件和可选的配置文件)。如果文件列表为空,则返回等待下一次调度。否则,就需要检查哪些文件需要被下载过来。这里做的判断有:1)如果本地的commit.getGeneration() >= latestGeneration,说明本地索引文件被破坏(比如对slave不小心提交了修改索引的命令),需要完全将master的文件复制过来。2)逐个检查文件列表中的文件是否在本地存在,不存在就下载下来。
3、对于下载文件内容,对应命令是filecontent。下载的文件显然需要放到临时目录中,这个临时目录和已有的索引目录(默认名字index)在同一数据目录下,只是命名为index.<时间戳>。下载完毕后,copy数据有两种情况:
1)如果是完全下载,则不需要将临时目录中的文件copy到已有目录中,而是修改数据目录中的index.properties,标识索引目录为新生成的临时目录,而旧索引目录并不会被删除,可以手工删掉,当然,通常是不应该出现slave的Generation大于master的异常情况。
2)通常就是把临时索引目录的文件copy到旧索引目录,copy时要把segments_N放到最后copy,避免copy中途出现异常造成数据被毁。
4、当新索引和可选的配置文件copy完毕之后,slave会对solrcore的UpdateHandler做commit操作,这会close掉indexwriter并强制重启新的indexsearcher提供服务。同时,如果solrcore的UpdateHandler是DirectUpdateHandler2(不应该不是),会强制调用handler.forceOpenWriter()来删除旧的无用的索引文件,并调用replicationHandler.refreshCommitpoint()来更新slave的indexCommitPoint。
5、如果索引复制失败,slave会向数据目录下的replication.properties输出复制失败的信息。