本文链接: http://quentinXXZ.iteye.com/blog/2153210
场景需求与分析
我们的做法,一般将索引构建大致分为两类操作,一为全量索引构建,二为增量索引构建。使用solr建索引,一般会在初始状态的时候,进行一次全量构建,根据当前数据源的整体数据生成一套完整索引,可提供服务,但为了保证索引数据的完整且最新,还需要增量索引,使得数据源的改变(包括记录的增加,修改,与删除)体现在这套索引之上。
在solr单core或者单collection的情况下,类似DataImportHandler之类的工具,都能提供这样全量与增量的索引方式。然而,对一套索引仅使用单core或者单collection的情况下,这种方式存在一种问题:索引上的错误累积。如果增量出错,或者增量的几条修改丢失,这样的错误就会一直在索引上累积。除非你删除这套索引,重新做全量,即re-indexing。这样势必就需要开发人员的人工参与与机器的停止维护。而我们有这种定期re-indexing的需求。
Solr Build search分离问题
要保证搜索引擎正常服务,同时又能做re-indexing。这就涉及到了build与search分离的情况。例如在cloud模式下,我们本来希望的状态是,每个分片shard的副本集中的leader在做全量的时候,只作索引的写,而不提供读服务,在完成索引全量写之后,同步给其它的replica。这样确定index构建速度,同时不给leader太大压力。当然这样有也问题,就是同步操作是集中进行的,这样会给网络带宽带来巨大压力。solr的做法不是这样的,在leader做写入时候,同时在提供search服务。可见solr并没有做到明显build search分离。这也是solr本身的一个问题。所以对re-indexing问题 的解决,本质上就是对build search分离问题的解决。
Solr standalone下的re-indexing的实现
为了实现单机形式下的读写分离,其实就是对不同core的分离。假设core名为 search,提供当前服务的索引,可以再新建一个空白的core, 名为search-rebuild,使用与serach相同schemal配置。需要re-index的时候,就可以这样做:
1、停止当前写往search的增量,search正常服务。
2、对search-rebuild进行全量索引构建。
3、完成search-rebuild全量索引后,做一次coreAdmin的SWAP操作,切换两个索引。
Url形式的api如下:
http://localhost:8983/solr/admin/cores?action=SWAP&core=search&other=search-rebuild
Swap所做的就是将两个core对应的名称做一下交换。也就是说,SWAP之后,search对应的索引为原来在search-rebuild建立的全新索引。而search-rebuild对应的索引为原来search的旧版索引。搜索客户端的搜索url无须修改,做到无缝切换。
4、继续对search(已经是新的索引)做增量。
Solr master/slave下的re-indexing的实现
Master/slave情况下,涉及到多台solr server,需要对多台solr server的索引做切换,会复杂得多。但最后发现,其实Master/slave下的re-indexing其实也可以通过与standalone的相似SWAP的方式实现的,而且无须对多台solr进行SWAP,只须SWAP master即可,slave会自动从master同步最新的索引。
没有找到文档,说solr是可以支持这种方式的。做了几次实验,这样的操作都成功了。但还是不放心,所以仔细的阅读了solr的源码,考虑了多这种情形了,相信这样的处理方式是可行的,并成功在线上进行了一次这样的操作,如果读者研究之后仍觉得有问题,请纠正我。
关于master/salve的索引同步的实现代码,主要在ReplicationHandler(master端)与SnapPuller(slave端)。
以下文章master/salve的索引同步的实现过程的分析比较清晰准备。
http://www.kafka0102.com/2010/07/249.html
Solrcloud模式下的re-indexing的实现
Solrcloud下的re-indexing是无法通过swap实现的。以下是Solr中CoreAdminHandler对SWAP请求最终调用的是SolrCore的一段代码:
protected void swap(String n0, String n1) { synchronized (modifyLock) { SolrCore c0 = cores.get(n0); SolrCore c1 = cores.get(n1); if (c0 == null) { // Might be an unloaded transient core c0 = container.getCore(n0); if (c0 == null) { throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "No such core: " + n0); } } if (c1 == null) { // Might be an unloaded transient core c1 = container.getCore(n1); if (c1 == null) { throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "No such core: " + n1); } } cores.put(n0, c1); cores.put(n1, c0); c0.setName(n1); c0.getCoreDescriptor().putProperty(CoreDescriptor.CORE_NAME, n1); c1.setName(n0); c1.getCoreDescriptor().putProperty(CoreDescriptor.CORE_NAME, n0); }
可见,SWAP只是简单地对两个内存中CoreDescriptor对象的name进行交换,甚至并没有与zookeeper有任何交涉。所以肯定无法适用于solrcloud的复杂情形。要实现cloud下的re-indexing,在core级别下的swap肯定是不够了,这时候,就需要一种在collection级别下的swap。
最后的解决方法我是在下文中找到,其中就提到了re-indexing的场景,cloudera网站上提供的方法:
http://blog.cloudera.com/blog/2013/10/collection-aliasing-near-real-time-search-for-really-big-data/
原文的片段如下:
Re-indexing
Collection aliases are also useful for re-indexing – especially when dealing with static indices. You
can re-index in a new collection while serving from the existing collection. Once the re-index is
complete, you simply swap in the new collection and then remove the first collection using your read
side aliases.
文中主要介绍的是alias 别名的使用,当然别名除了这种场景的应用,还有其他使用场体。利用Alias的修改,我们就可以实现两个collection之间的SWAP。
另外,提一下利用solrj进行alias操作时遇到的一些版本问题:Solrj4.6.0才开始支持CollectionAdmin操作,但是Solrj4.6.0进行alias create操作有明显bug,Solrj4.6.1修复。同时,solrj应使用与solr-core相同的版本,否则可能会有兼容性问题。所以如果想用java调用,而且使用4.6.1之前版本的solr服务,就需要自行实现该API方法,可参考Solrj4.6.1或以后版本,进行修改。
总结
总之,我所发现的solr对re-indexing的实现是增加了另一套索引再做切换实现的,这样的一个代价,就是增加了磁盘空间的占用,另一个代价就是某时间段占用两倍内存的可能。在solrcloud模式,这种做法不一定需要更多的机器,因为不同collection下的分片副本的core是可以共存于一个solr实例中的。
本文链接: http://quentinXXZ.iteye.com/blog/2153210