理解SolrCloud的事务日志、软提交和硬提交

来源:互联网 发布:魔术教学知乎 编辑:程序博客网 时间:2024/06/06 01:32

转载:http://www.lucener.com/articles/2017/03/17/1489704450618.html

从solr4.0起,solr多了一个软提交(softcommit)功能,而硬提交功能(hardcommit)多了一个参数-openSearcher。通常,软提交和硬提交的作用很容易混淆,尤其是它们对于事务日志(transation log)的意义。这两个功能的作用,虽然solrconfig.xml模板里面有提到,但是通常举例说明都有局限性,如果要完全说明白solrconfig的所有的选项,起码10M的文档才能说得明白,而且没有人会读完整个文档去弄明白所有的东西。因此,这篇文章更详细地阐述一下硬提交、软提交和openSearcher参数的效果。

准则

请记住,“硬提交是关于持久化的,软提交是关于可见性的”。硬提交和软提交是相关的概念,但是它们的目的是不一样的。这句话隐含了很多细节,我们将就其中一些进行阐明。首先是几个定义:

  • Transaction Log(Tlog): 记录了原始文档,用于索引恢复功能。在SolrCloud中,每个节点都有自己的tlog。在更新的时候,整个文档会写入tlog中。在原子更新(Atomic update)时,仍然是整个文档写进来,包括了从老文档中读取出来的内容,换言之,原子更新时,写到tlog的不是增量数据。Tlog是保证一致性的关键,有了它,就算索引段(segment)关闭前JVM崩溃了,索引也不会丢失。

    • 注意:一旦服务器没有正常关闭,则重新启动时,tlog会进行回放。因此,如果你的tlog很大(我们见过GB级别的tlog),则重启会非常慢,例如几个小时才启动成功。

  • Hard commit : 硬提交通过solrconfig.xml的选项来实现,或者客户端显式调用。硬提交会结束当前索引段的构建,并开启新的索引段的构建。

  • openSearcher:选项的子属性,用来控制新提交的数据是否能被后来的搜索操作检索到(是否可见)。

  • Soft commit:软提交是比硬提交(openSearcher=true)使文档可见的更轻量级的操作,而且软提交不会结束当前索引段的构建。

    • 重要:虽然软提交是轻量级的操作,但是也不是完全没有代价的。你应该使软提交的时间间隔尽可能长来保证更好的性能。

  • fsynch:底层IO命令,当fsynch调用返回后,数据必定已写到了磁盘中。这个和java程序的flush是不一样的。java的flush只是保证已经向操作系统提交了数据,操作系统并没有立刻写到磁盘中,而是在适当的时候才会真正写入磁盘。

  • flush:java程序把数据提交给操作系统。这个操作返回后,数据并没有真正落盘,如果操作系统崩溃了,数据有可能会丢失掉。

    • 请注意,特别是在solrcloud,多于一个副本的情况下,所有副本同时挂掉,并且数据都没有落盘,才会发生数据丢失,这种情况是很少见的。

    • 操作系统在flush命令后大概10-50毫秒就会把数据写入磁盘。如果jvm崩溃了,操作系统仍然会把数据写到磁盘,但是,如果是操作系统崩溃,而IO子系统又没来得及把数据刷到磁盘,则会丢失数据。这通常不是你需要关注的,只有当你需要绝对确保没有数据丢失时,这才很重要。

Transaction Logs

事务日志是solr4中数据完整性的保证,但也引入了很多的麻烦,我们详细聊一下。索引构建的流程如下:

  • 写入的数据被一个节点接收,然后转交到正确的leader节点。

  • 从那个leader节点发送到所有相关分片的所有副本。

  • 所有副本索引完后回应leader节点。

  • leader节点回应一开始的接收节点。

  • 当所有的leader节点都回应之后,接收节点回应客户端,在这个点上,*所有的数据都已经flush到了集群中的所有相关节点的tlog中*

  • 如果jvm崩溃了,文档也已经安全地写到了tlog中,但是,如果是操作系统奔溃,那就不一定了。

    • 如果jvm崩溃(或者killed -9杀掉),然后重启,tlog会回放。

    • 你可以修改solrconfig.xml里面的配置,在返回前用fsynch而不是flush,但这样是没有必要的。所有leaders和replicas同时因为硬件挂掉而丢失数据的几率是很小的。有些场景,就算存在细微的几率丢失数据也是不允许的,则可以采用这种牺牲吞吐量的方式。

注意:tlog文件会在hard commit(不管openSearcher是true还是false)时滚动(rolled over)。老的tlog文件会关闭,而打开一个新的tlog文件。保留足够的tlog来存放100个文档,然后其他tlog会删除掉。假设你每批25个文档来建索引,每批完成后执行hard commit。这样,任何时候你都会保持5个tlog文件,最老的4个每个包括25个文档,共100个,加上当前的tlog。当当前tlog关闭后,最老的那个就会删除掉,而一个新的tlog文件会打开。需要特别注意的是,solr不会尝试只把100个文档存入特定的tlog。只有你告诉solr要滚动了,它才会滚动,比如发出commit命令或autoCommit发生了。所以,在大量写入的情况下,比如1000个文档每秒,而你一个小时没有commit,则单个tlog文件就包含了3,600,000个文档。如果solr发生了意外关闭,则重启后,这个tlog就会全部replay,完成后才能提供搜索服务,这可能需要几个小时的时间。而且,你可能没有那么大的耐心来等待,觉得可能哪里出了问题了,然后又重启,这个tlog又重头replay。就是这样,如果你有很大的tlog,则肯定是有问题的,你需要修改你的hard commit配置。这个坑对于从3.x过来的人更容易踩到,3.x的时代,hard commit一般都设置得比较长,因为,那时候还没有openSearcher=false这个选项,hard commit代价是昂贵的。

Soft commit

Soft commit是和可见性有关的,hard commit是和持久性有关的。对于soft commit的理解是,它能使文档可见,但是会有一些代价。尤其是在solrconfig.xml里定义的顶层的cache(filterCache、queryResultCache等)会失效,autowarning会在顶层cache发生(比如filterCache、queryResultCache)。这时候,newSearcher的查询都会被执行,而且,fieldvaluecache也会失效,因此,facet查询也不得不等cache重新生成。在频繁的soft commit下,cache基本上没什么效果,在某些场景下,最好去掉它。然而,索引段级别的cache(译者注:比如fieldcache),用于function 查询,排序等,是基于索引段的,因此不会因为soft commit而失效,它们能继续被使用。

那么这意味着什么呢?

假设一个softcommit被执行了,则:

  • tlog不会被截断,它会继续增长。

  • 新增的文档会可见。

  • 某些cache必须重新加载。

  • 顶层的cache会失效。

  • autowarming会被触发。

  • 新的索引段会生成。

注意,我没有说任何关于索引段的事情,那是hardcommit做的。再次重申,soft commit比hard commit(openSearcher=true)代价小,但是并不是完全没有代价的,正如格言所说的“天下没有免费的午餐”。soft commit用来支持近实时搜索,但是是有代价的,因此,设置soft commit的时间间隔尽可能长,来获取更好的性能。

Hard commit

hard commit是有关持久化的,softcommit是有关可见性的。这里,还要分两种情况,openSearcher=true和openSearcher=false。首先,我会解析一下这两种情况下都会发生的事情。不管openSearcher=true还是openSearcher=false,都会出现以下的结果:

  • tlog会截断:当前tlog会关闭,一个新的tlog会开始,在已关闭的tlog中,如果较新的tlog超过了100个文档,则老的tlog会删除掉。

  • 当前正在索引的索引段会关闭,并flush。

  • 可能会触发后台的段合并。

这些就是hardcommit一定会发生的事情,无论openSearcher怎样设置。

  • openSearcher=true:Solr/Lucene searchers 被重新打开,所有的cache都失效(译者注:索引段级别的cache不会失效),autowarming会执行。这是老版本唯一能看到新增加的文档的方法。

  • openSearcher=false:除了以上四点以外没有其他动作了。如果要搜索新的文档,需要执行一次soft commit。

Recovery

我上面说了持久化的问题,那么我们再来深入探讨下。当机器宕机,jvm 崩溃,无论怎样,你的集群的状态是这样的。

  • 最后返回成功的更新调用已经将你的文档写到了集群中的tlog,默认是你的tlog已经flush,但是没有fsync‘d,我们之前已经提到过,你可以修改这个默认行为,但是不推荐这样做。

  • 当重启机器,它会联系leader节点,然后会执行其中一个恢复动作

    • 如果leader节点接收到小于等于100个文档,则会从它自己的tlog回放文档(译者注:缺失的文档会从leader的tlog同步过来)。注意,在回放的过程中,新进来的文档会写到tlog的末尾,它们也会回放。

    • 如果从节点下线到现在,leader节点接收了大于100个文档,则会采用传统的同步方式从leader节点同步整个索引。

恢复是需要一定的时间的,这是人们在使用solrcloud经常遇到的坑。他们会做各种实验,各种地方关闭、启动服务,用kill -9 命令杀solr进程等。一方面,它是好的,演练了solrcloud的恢复过程。另一方面,它又没有什么效果,因为这是需要高度依赖人的经验的。如果你的某些节点一天中消失好多次,那么你应该去修复它,这个比solr恢复时间较长的问题要严重得多了。

建议

说到建议,我常常觉得很为难,因为在某些场景下,任何建议都是错的。我首先建议的是不要把问题想的过了。一些聪明的人会更可靠地进行调优。先尝试一些简单的调整,只调整一些必要的配置。尤其是,先看一下tlog的大小,调整一下hard commit时间间隔大小,使tlog的大小比较合理。记住,如果jvm崩溃了,最常见的后果是tlog回放时间较长。15分钟能忍受吗?如果不能,那么为什么不设置得小一些呢。我们见过hard commit比soft commit时间间隔小得多的设置,参考下面提到的大批量索引构建。

优雅地关闭solr,换句话说,在索引构建的过程中,“kill -9”只会自找麻烦。

优雅地关闭,意味着:

  • 停止写入新的文档。

  • 发送一个hardcommit命令或等待autoCommit 时间间隔过期。

  • 停止solr服务。

你可以调整一些配置,以适合你的场景。

大批量索引构建

假设你希望把大批量的数据尽可能快地写入系统,将来用于搜索。比如,数据源的初始化。

  • 设置soft commit时间间隔足够长,比如10分钟或更长(配置为-1则不自动soft commit)。soft commit是有关可见性的,我想大批量索引构建应该不是为了满足实时搜索的,所以没必要额外地打开任何类型的searcher。

  • 设置hard commit时间间隔为15秒,openSearcher=false。重申一下,这里假设你只是为了把大量数据导入solr,而不是为了实时搜索。这样设置,就是最坏的情况也只是当你重启,你最多只需要从tlog回放15秒的数据。如果你的系统频繁重启,则先找到频繁重启的原因。

  • 只有当你已经尝试了简单的方案而没有解决问题,你才需要考虑进一步的改进方案,一般是一些不常见的做法,它们包括:

    • 在批量索引时完全关闭tlog。

    • 离线构建索引,比如mapreduce方式构建索引。

    • 在构建索引时,每个分片只有一个leader节点,没有副本,构建完成后,再开启副本,让它们进行传统的复制来保持一致。注意,这个是自动实现的,如果副本发现和leader相差太远,就会触发传统的复制。复制完成后,它会继续接受leader节点的文档,并保存到自己的tlog中。

    • 诸如此类

索引更新很频繁,检索请求量很少

比如,日志检索。这个场景下,每天有大量的日志要写进来,但是检索请求却很少,主要用作故障排除和日志分析。

  • 设置soft commit时间间隔足够长,设置为你能忍受新文档不可见的最长时间。这可能是几分钟或更长。甚至几个小时,直到执行一次hard commit(openSearcher=true)或soft commit。

  • 设置hard commit(openSearcher=false)的时间间隔为15秒。

索引更新不频繁,检索请求量小或大

这是相对静止的索引,但时不时有一些更新操作。比如说5-10分钟有一个更新操作。

  • 除非近实时搜索是必需的功能,在这种场景下,我会去掉soft commit,每5分钟提交一次hard commit(openSearcher=true)。如果你是采用外部单线程来构建索引,由客户端来提交hard commit可能更合理。

索引更新很频繁,检索请求量大

这是近实时搜索的场景,是最复杂的场景,这需要有较多的经验,我是这样做的

  • 设置soft commit的时间间隔为你能忍受的最大长度。不要听信你的产品经理的话“我需要不超过1秒钟的延迟”。时间间隔从长到短,逐渐尝试,看看用户服务是否满足,是否用户注意到了延迟,直到找到一个合适的值。soft commit和近实时搜索非常好用,但是并非没有代价的。

  • 设置hardcommit的时间间隔为15秒。

Solrj和http和客户端索引

一般来说,所有的配置选项也能通过solrj和http实现。 Late edit说过,“从客户端进行提交要特别小心,实际上,最好不要这样做”。一般来说,客户端索引时不要发送提交命令到solr,这样做经常会犯错,尤其是你有多个客户端同时进行索引的时候。提交命令并发地发送过来,并发地执行,你很可能会看到这样的警告“too many warming searchers”,或者你会看到无数的小段。还是在solrconfig.xml中配置自动提交(hard commit或soft commit)来控制提交频率吧。如果你一定要控制可见性,你想索引后马上能看到新的文档,而无法忍受等待自动提交触发。那么,只在最后执行一次就好了。实际上,只在只有一个客户端的情况下,我才会自己来提交。否则,我会在索引全部完成,我才手工提交一次,像这样

http://host:port/solr/collection/update?commit=true

你也可以用solrj,在添加索引的时候,设置commitWithin参数,单位是毫秒。这种方式,无论多少客户端都没问题的。服务端timer在接受到第一个带有commitWithin参数的更新时启动,经过commitWithin时间之后,任何客户端发送的所有的文档都会提交掉,无论那些文档是否包含了commitWithin参数。下一个带有commitWithin参数的更新又会启动一个新的timer。最后记住,优化索引(optimzing)是一般来说是没有必要的。

英文链接
https://lucidworks.com/2013/08/23/understanding-transaction-logs-softcommit-and-commit-in-sorlcloud/

原创粉丝点击