You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 7 Next »

现象

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


Unable to render Jira issues macro, execution error.

分析

通过对日志和内存 dump 的分析,发现分布式 RaftLogManager 中的内存缓存超出了阈值。在探究内存缓存超出阈值的原因时,发现主要有以下四个问题:

  • 分布式进程中单机内存和 RaftLog 内存没有统一考虑管理,即单机认为自己得到了全部堆内存进行分配,而分布式又在一些地方额外分配了内存并进行了内存控制,这导致分布式进程对自己拥有内存的认知一开始就大于实际的堆内存,从而使得内存一开始读写就比较紧张。
  • RaftLog 模块自身做了简单的内存控制策略,但只是针对已 commit 日志做的,未 commit 的日志没有通过阻写的方式来实现完备的内存控制。
  • RaftLog 模块的内存控制与单机的内存控制产生了正反馈导致系统崩盘。极端情况下,由于内存不够,单机状态机受单机内存控制模块影响导致 apply 日志过慢,然而 RaftLog 模块的清理内存线程只能清理 applyIndex 之后的日志,这导致 RaftLog 模块的内存控制几乎失效,无法清理内存中的日志,导致内存进一步紧张,这又导致单机 apply 日志更慢。两者的双向作用共同导致了问题的进一步恶化。
  • RaftLog 模块的后台清理内存线程默认被关闭了,导致目前只有在 commit 的时候才会去检验内存,这可能使得内存释放不及时。当然,即使有它也不能完全避免前面提到的问题。

方案

需要在分布式进程中将单机内存和 RaftLog 内存统一管理并将单机极端情况阻写的内存控制策略移动至 RaftLog 上层,此外还可默认开启后台的内存清理线程以提高容错性。

Leader 内存控制策略:

  • 当前有太多超过未提交的日志时,参照单机内存控制阻塞写入一段时间。

Follower 内存控制策略:

  • Leader 采用 dispatcher 模式(单独启一个线程)进行同步,对于每一个 Follower,如果其未 catchup,则先向其同步之前的日志再发送之后的日志,同时在发送日志时直接使用 RaftLogManager 的日志即可,不用提前 build request,此处可以参照 pipeline 的优化。
  • Follower 在遇到本地 applyIndex 与 Leader 的 commitIndex 相差过大时,可以暂时拒绝 leader 的写入,直到本地 applyIndex 跟上为止。 


  • No labels