知识库

缓解因高垃圾回收(GC)导致的因果集群重新选举

本文介绍了 JVM 的“Stop-the-world”型 GC 停顿对因果集群的影响。在简要介绍垃圾回收、堆大小调整和内存泄漏排查之后,本文将讨论有助于减轻由此产生的心跳超时和集群重选问题的最佳实践与配置。

GC 概览

JVM(Java 虚拟机,例如 Neo4j 服务器)会为新创建的 Java 对象分配所需的内存。这部分内存是启动时通过 neo4j.conf 配置的总堆内存分配的一部分。JVM 会定期检查堆中未使用的对象,并通过一种称为“垃圾回收”(简称 GC)的过程将其丢弃以回收堆内存。

假设已根据 Neo4j 内存配置(参见 /developer/kb/understanding-memory-consumption/)对堆大小进行了优化,使用量/数据量的激增仍可能导致堆利用率增加,进而导致更长和/或更频繁的垃圾回收。内存泄漏是堆利用率增加的另一个原因,在许多情况下会导致堆内存溢出。Java 中的内存泄漏是指某些对象不再被应用程序使用,但垃圾回收机制未能识别出这一点的情况。

在这种情况下,简单地增加堆空间可能只会推迟 JVM 耗尽堆空间(抛出 java.lang.OutOfMemoryError: Java heap space 错误)。此外,增加 Java 堆空间的大小往往也会增加 GC 停顿的时长。用户可以执行堆转储(Heap Dump)并使用 Eclipse MAT 等工具(确保您的机器有足够的分析内存)来诊断内存泄漏。其他工具如 JDK、jconsolevisualvmjstat 以及 neo4j 的 gc.log 也可以帮助识别异常事务。

GC 停顿如何影响因果集群?

上述长时间 GC 或内存泄漏的一个常见后果是集群中的心跳超时,这本质上是集群成员无法及时联系到其他成员。这是因为完整的 GC 会强制执行“Stop-the-world”停顿,在此期间,JVM 会暂停所有其他操作,包括网络通信。在因果集群中,这通常会导致重选,即无法访问的成员被从集群中移除,并选举出新的集群领导者。请注意,还有其他因素可能导致重选,但它们通常是良性的,不在本文讨论范围之内。

重选及其频率在高负载下为何重要?

因果集群中的重选是一个快速、无缝的过程,在正常运行情况下,它会在几十到几百毫秒内完成,而不会对事务客户端产生明显影响。重选实际上是领导者在停顿期间变得不可用的自然结果,追随者不知道领导者正在进行垃圾回收,也不需要关心。然而,在高负载情况下,如果伴随着频繁的长 GC 停顿,重选可能是不可取的。这是因为在重选期间,单位时间内入站请求的拒绝率很高。此外,新选举出的领导者可能需要通过检查点(checkpointing)将任何挂起的事务提交到存储,并成为追随者追赶数据的新来源,而大量新入站事务将再次需要频繁且可能很长的 GC 停顿,这对操作系统 CPU 和内存资源产生累积影响。此外,之前的领导者可能会在短时间内重新加入集群,由于其日志中仍有较高的事务 ID,它可能会再次成为领导者。重要的是,虽然选举本身可能只持续几毫秒,但在选举期间,集群不会接受写操作。这些是导致循环持续进行领导者切换的关键因素,特别是在高负载情况下。

选举和心跳超时。它们是什么?

选举超时(Election timeout)是指 FOLLOWER(追随者)在经过这段时间后将尝试发起选举,从而变为 CANDIDATE(候选者)的时间。这些超时在选举过程中会持续存在;如果 CANDIDATE 未能当选,或者在另一个 causal_clustering.leader_election_timeout 周期后仍未收到新当选 LEADER 的消息,它将变为 FOLLOWER 或再次发起选举。通常不建议设置过长的选举超时时间。它们代表了领导者故障转移期间更长的停机时间。目标不应该是增加超时时间,而是通过管理负载来减少 GC。为第二个超时设置稍长的时间可能会减少选举造成的整体停机时间。causal_clustering.leader_election_timeout 的文档定义中未提及心跳超时,因为心跳是实现细节。大多数集群内部消息也被视为心跳。如果集群成员收到来自领导者的消息,则认为它已收到该领导者的反馈,因此选举超时会重置,随后触发新领导者选举的计时器也会重置。

如何最好地缓解长时间 GC 后引起的心跳/选举超时?

因果集群的心跳超时由配置 causal_clustering.leader_election_timeout 控制,默认值为 7 秒,建议值小于 10 秒。对于具有大堆的图数据库,GC 停顿有时可能达到分钟级别,在这种情况下,此类超时值无法防止重选。请注意,并非大堆本身导致 GC,而是分配的内存远超 GC 能够回收的范围,最终导致了“Stop-the-world”停顿。如果有许多较小的停顿偶尔导致领导选举(即偶尔错过 7 秒的超时),那么稍微增加 causal_clustering.leader_election_timeout 的值可能是一种解决方案,但唯一真正的解决办法是减少垃圾的产生,这通常涉及重写 Cypher 查询并避免大批量数据摄入查询等。查询优化超出了本文的范围,但它是 Neo4j Cypher 手册的一部分,可在以下网址查看:/docs/cypher-manual/current/query-tuning/

什么是预投票(Pre_Voting)以及它有何帮助?

另一个有助于防止频繁重选的配置是 causal_clustering.enable_pre_voting(在 Neo4j 3.2.9 中添加),它控制是否启用“预选举”。预投票是指一个实例询问集群中的其他成员:“如果我要发起选举,你会投票给我吗?”。这是一种乐观的方法,旨在停止可能毫无结果的选举(我们知道在选举进行期间会停止写操作)。在这种情况下,“毫无结果”通常意味着之前的领导者保持不变,而我们得到的只是在“毫无结果”的选举发生期间几秒钟的不可用。选举后,拥有最高“任期”(term)的集群成员获胜并成为新的领导者。请注意,除非其他成员为其投票,否则成员不会增加其任期;如果追随者具有更高的任期,领导者必须下台,这意味着领导者已经落后了。

因此,预投票通过以下两种方式帮助减少不必要的选举:

  1. 它确保只有在法定人数(quorum)与领导者失去心跳后才会触发选举。

  2. 它确保任期低于集群中其他成员的节点不会触发选举。

© . This site is unofficial and not affiliated with Neo4j, Inc.