现象

三节点三副本分布式执行写入,稳定运行 15 天后出现了 OOM。


Unable to render Jira issues macro, execution error.

分析

通过对日志和内存 dump 的分析,发现其中一个节点 RaftLogManager 所保留的已提交日志占用的总内存超过配置值。该节点所配置的保留日志内存大小为每个 RaftLogManager 512MB,考虑到测试时用的负载,约可以保存720条日志,但实际上每个RaftLogManager保留了接近2000条日志。其原因在于:

  • 分布式模块的内存管理与单机模块的内存管理相互独立,使得单机模块的内存占用与分布式模块的内存占用相加可能超过JVM的总内存;
  • 在本次测试的开始阶段,尽管两者内存相加没有直接超过JVM总内存,但已经非常逼近该值,使得FullGC频繁触发;
  • 由于FullGC频繁触发,单机模块不能快速执行日志;由于日志得不到执行,分布式模块也无法及时清理日志,使得内存居高不下,FullGC也持续不断;
  • 此时Leader仍然在向该节点发送并提交日志,使得该节点保留的已提交日志数进一步增长,加剧了内存的紧张。
  • RaftLog 模块的后台清理内存线程默认被关闭了,导致目前只有在 日志提交的时候才会去检验内存,即便有日志被执行,也可能得不到清理,使得内存释放不及时。

其余发现的问题:

  • 一个Follower已经接收但尚未提交的日志被视作临时内存,没有进行管理
  • Leader正在向一个Follower发送以及等待被发送的日志被视作临时内存,没有进行管理


一个写入请求在分布式进程内存中的流动:

当前的内存控制除了已 Apply 的日志外,对其余三种日志都没有进行内存控制。

方案

分布式模块与单机模块内存管理的联动

    分布式模块分配的内存不再作为一个单独的配置项,而是作为单机内存分配的一个分量。

    避免分布式模块与单机模块的总内存占用超过JVM内存。

Leader 内存控制策略:

  • 当前有太多未提交的日志时,阻塞写入一段时间。
  • Leader 采用 dispatcher 模式进行同步,对于每一个Follower,限制等待发送到该Follower的日志数量。

Follower 内存控制策略:

  • Leader 采用 dispatcher 模式进行同步,对于每一个Follower,限制同时向该Follower发送日志的线程数。
  • Follower 在遇到本地 applyIndex 与 Leader 的 commitIndex 相差过大时,可以暂时拒绝 leader 的写入,直到本地 applyIndex 跟上为止。 


  • No labels