overcommit 对 fork 的影响

问题

线上的一个服务使用Redis作为中转队列,今天读写队列操作突然错误,相关功能处于不可用状态,错误显示读队列时返回异常:

Redis is configured to save RDB snapshots, but is currently not able to persist on disk. Commands that may modify the data set are disabled. Please check Redis logs for details about the error

RDB 是 Redis 持久化数据的一种方式,其主要原理就是在某个时间点把内存中的所有数据的快照保存一份到磁盘上。在条件达到时通过fork一个子进程把内存中的数据写到一个临时文件中来实现保存数据快照。

查看 Redis 的日志发现:

[23830] 14 Jan 20:13:11.059 * 1 changes in 900 seconds. Saving...
[23830] 14 Jan 20:13:11.060 # Can't save in background: fork: Cannot allocate memory
[23830] 14 Jan 20:13:17.067 * 1 changes in 900 seconds. Saving...
[23830] 14 Jan 20:13:17.068 # Can't save in background: fork: Cannot allocate memory

原因

看到问题应该是由于fork出来一个进程后台持久化数据,由于内存不足导致fork失败,当Redis持久化失败时,设置当前为禁写的状态(读队列会删除元素,也算写操作)。使用info查看Redis状态,rdb_last_bgsave_status显示为 err。此时即使重新设置状态为ok也无用,因为很快就会下一轮fork,状态会被重置。

overcommit

官方文档有相关信息,需要设置echo 1 > /proc/sys/vm/overcommit_memory,查阅Linux Memory 发现,overcommit_memory 指定了Linux内存分配的策略。可选值:0,1,2。

  • 0:表示内核将检查是否有足够的可用内存供应用进程使用;如果有足够的可用内存,内存申请允许;否则,内存申请失败,并把错误返回给应用进程。
  • 1:表示内核允许分配所有的物理内存,而不管当前的内存状态如何。
  • 2:表示内核允许分配超过所有物理内存和交换空间总和的内存

具体什么含义呢?当使用malloc等函数,向系统申请内存时,一种情况是有可用内存时返回成功,无可用内存时返回失败NULL,另一种情况是都返回成功,以便能运行更大的程序,因为申请内存后,可能不会立即使用,当使用时发现内存不足时,会发生OOM killer,它会选择杀死一些进程(用户态进程,不是内核线程),以便释放内存。选择进程的函数是oom_badness函数(在mm/oom_kill.c中),该函数会计算每个进程的点数(0~1000)。点数越高,这个进程越有可能被杀死。每个进程的点数跟oom_score_adj有关,而且oom_score_adj可以被设置(-1000最低,1000最高)。

overcommit_memory的三种状态分别表示为三种overcommit策略(Documentation/vm/overcommit-accounting):

  • 0:启发式策略。合理的overcommit会被接受,不合理的overcommit会被拒绝。
  • 1:任何overcommit都会被接受。
  • 2:当系统分配的内存超过swap+N%*物理RAM(N%由vm.overcommit_ratio决定)时,会拒绝commit。

overcommit 的策略通过vm.overcommit_memory设置 overcommit的百分比由vm.overcommit_ratio设置

overcommit_memory设置为0时,Redis fork Linux会假定子进程占用同样多的内存空间(根据实际映射的物理内存决定),如果free空间不足,会发生错误导致无法正常fork。

复盘

复盘一下发现:写入量过大,导致fork时内存不足,fork失败,Redis这种情况下会切换成紧写的状态。导致Redis不可用,如果设置 overcommit_memory 为1,看到Redis上一次dump数据花费了60s左右,内存使用12G,每条数据大小500字节左右,60s时间队列读取速度为4M,基本上相当于2G数据量,可以看到变化的数据有20%不到,根据写时复制的原则,fork就不会有问题了。

不过Redis作为队列,在消息流动比较大,而且消息时效性比较强的情况下,可以禁用数据持久化功能,提升效率。

Built with Hugo
主题 StackJimmy 设计