Redis server cannot allocate memory

这个问题是redis高端微信群里面反应的,我给记录一下,算是知识补充

线上日志:Redis server cannot allocate memory_第1张图片

 

线上redis配置:

Redis server cannot allocate memory_第2张图片

 

当时网友推荐这个redis的配置,然后要求提供下线上server日志

# By default Redis will stop accepting writes if RDB snapshots are enabled
# (at least one save point) and the latest background save failed.
# This will make the user aware (in a hard way) that data is not persisting
# on disk properly, otherwise chances are that no one will notice and some
# disaster will happen.
#
# If the background saving process will start working again Redis will
# automatically allow writes again.
#
# However if you have setup your proper monitoring of the Redis server
# and persistence, you may want to disable this feature so that Redis will
# continue to work as usual even if there are problems with disk,
# permissions, and so forth.
stop-writes-on-bgsave-error yes

server日志:

Redis server cannot allocate memory_第3张图片

 

最大内存是800MB,机器内存 8gb,峰值redis占用2gb内存 实际分配给redis的只有800mb,日志显示得也是申请内存不够

解答

接下来就是大婶们快速定位和原理普及过程:

xx:跟overcommit_memory配置  有关不

xx:vm.overcommit_memory /etc/sysctl.conf, proc/sys/vm/overcommit_memory 这个值看下

xx:内存8g 不配置为1 还真可能这样

xx:网上搜一下解释如下图:Redis server cannot allocate memory_第4张图片

vm.overcommit_memory = 1,直接放行 vm.overcommit_memory = 0:则比较 此次请求分配的虚拟内存大小和系统当前空闲的物理内存加上swap,决定是否放行。 vm.overcommit_memory =2:则会比较进程所有已分配的虚拟内存加上此次请求分配的虚拟内存和系统当前的空闲物理内存加上swap,决定是否放行。

而问题的线上配置是:Redis server cannot allocate memory_第5张图片

 

xx:这边生产上这个值建议值是1

xx:我们生产上有rdb重写失败, aof重写失败, 都是申请内存导致

xx:这一套服务器有一个jspgou服务 再就是redis,两个公用一套机器,jspgou服务器 经常卡死...,CPU和内存飙升

xx:内存够用 只是不设置为1 按虚拟地址预估不够用 COW实际使用才几M而已

Redis server cannot allocate memory_第6张图片

xx:少复用, 不被带坑里,写时复用才几兆 什么意思?linux写时复用 是直接用的是SWAP吧,如果我们没有分配swap 也没用吧?我们服务没有设置swap

Redis server cannot allocate memory_第7张图片

xx:cow用不了多少内存,都是复制几页

xx:除非这个过程中大量写入涌入

Redis server cannot allocate memory_第8张图片

xx:前提得是尼配置了swap啊

xx:嗯 这个问题我们线上发生过 此时只能接受pingpong 其他操作都拒绝 而实际上监控显示内存富裕 所以overcommit可以

xx:没有swap。0 1 2都没区别吧?

xx:不是,因为虚拟内存,实际上大家都不会用完内存,虚拟地址空间 是 linux逻辑空间

xx:linux使用虚拟内存不是基于swap?

xx:而redis在fork的时候其实只是虚拟内存即刻,不开swap也是虚拟内存,不是一码事,swap是指实际内存不足了交换到了磁盘上

xx:这个问题挺有意思 如果不注意就是线上事故……所以最开始我是觉得奢侈用 只用一半内存 后来还是开了overcommit

xx:不是内存不足才走虚拟内存嘛。为啥子内存够了 内存富裕 还走虚拟内存

xx:虚拟内存的好处 https://draveness.me/whys-the-design-os-virtual-memory/,多反复 会有收获 温故知新

Redis server cannot allocate memory_第9张图片

下面是CSDN其他网友对名词的深入解释
https://blog.csdn.net/zqz_zqz/article/details/53384854


不彻底的解决方式

将这个选项改为false
stop-writes-on-bgsave-error false
但是这样只是当redis写硬盘快照出错时,可以让用户继续做更新操作,但是写硬盘仍然是失败的;

彻底的解决方式

编辑文件 /etc/sysctl.conf 添加:
vm.overcommit_memory=1
执行sysctl -p使其生效;

vm.overcommit_memory 这个参数又是干什么的呢?

Linux对大部分申请内存的请求都回复"yes",以便能跑更多更大的程序。因为申请内存后,并不会马上使用内存,将这些不会使用的空闲内存分配给其它程序使用,以提高内存利用率,这种技术叫做Overcommit。一般情况下,当所有程序都不会用到自己申请的所有内存时,系统不会出问题,但是如果程序随着运行,需要的内存越来越大,在自己申请的大小范围内,不断占用更多内存,直到超出物理内存,当linux发现内存不足时,会发生OOM killer(OOM=out-of-memory)。它会选择杀死一些进程(用户态进程,不是内核线程,哪些占用内存越多,运行时间越短的进程越有可能被杀掉),以便释放内存。
 
当oom-killer发生时,linux会选择杀死哪些进程?选择进程的函数是oom_badness函数(在mm/oom_kill.c中),该函数会计算每个进程的点数(0~1000)。点数越高,这个进程越有可能被杀死。每个进程的点数跟(/proc//oom_adj)oom_score_adj有关,而且oom_score_adj可以被设置(-1000最低,1000最高)。

当发生oom killer时,会将记录在系统日志中/var/log/messages

Out of memory: Kill process 9682 (mysqld) score 9 or sacrifice child
Killed process 9682, UID 27, (mysqld) total-vm:47388kB, anon-rss:3744kB, file-rss:80kB
httpd invoked oom-killer: gfp_mask=0x201da, order=0, oom_adj=0, oom_score_adj=0

在什么条件下,linux会发现内存不足呢?

在Linux下有个CommitLimit 用于限制系统应用使用的内存资源:

[root@iZuf6c2qeenlv7dqgioq2eZ bin]# grep -i commit /proc/meminfo
CommitLimit:      961120 kB
Committed_AS:    1514856 kB

其中:
CommitLimit是一个内存分配上限,
Committed_AS是已经分配的内存大小。

虚拟内存算法:

CommitLimit = 物理内存 * overcommit_ratio(/proc/sys/vm/overcmmit_ratio,默认50,即50%) + swap大小

它是由内核参数overcommit_ratio的控制的,当我们的应用申请内存的时候,当出现以下情况:

应用程序要申请的内存 + 系统已经分配的内存(也就是Committed_AS)> CommitLimit

这时候就是内存不足,到了这里,操作系统要怎么办,就要祭出我们的主角“overcommit_memory”参数了(/proc/sys/vm/overcommit_memory);

vm.overcommit_memory = 0   启发策略
比较 此次请求分配的虚拟内存大小和系统当前空闲的物理内存加上swap,决定是否放行。系统在为应用进程分配虚拟地址空间时,会判断当前申请的虚拟地址空间大小是否超过剩余内存大小,如果超过,则虚拟地址空间分配失败。因此,也就是如果进程本身占用的虚拟地址空间比较大或者剩余内存比较小时,fork、malloc等调用可能会失败。

 vm.overcommit_memory = 1 允许overcommit
直接放行,系统在为应用进程分配虚拟地址空间时,完全不进行限制,这种情况下,避免了fork可能产生的失败,但由于malloc是先分配虚拟地址空间,而后通过异常陷入内核分配真正的物理内存,在内存不足的情况下,这相当于完全屏蔽了应用进程对系统内存状态的感知,即malloc总是能成功,一旦内存不足,会引起系统OOM杀进程,应用程序对于这种后果是无法预测的。

vm.overcommit_memory = 2 禁止overcommit
根据系统内存状态确定了虚拟地址空间的上限,由于很多情况下,进程的虚拟地址空间占用远大于其实际占用的物理内存,这样一旦内存使用量上去以后,对于一些动态产生的进程(需要复制父进程地址空间)则很容易创建失败,如果业务过程没有过多的这种动态申请内存或者创建子进程,则影响不大,否则会产生比较大的影响 。这种情况下系统所能分配的内存不会超过上面提到的CommitLimit大小,如果这么多资源已经用光,那么后面任何尝试申请内存的行为都会返回错误,这通常意味着此时没法运行任何新程序。


前面讲了一大堆参数,那么这些参数又是怎么影响redis的呢?


Redis的数据回写机制分同步和异步两种,

    同步回写即SAVE命令,主进程直接向磁盘回写数据。在数据大的情况下会导致系统假死很长时间,所以一般不是推荐的。
    异步回写即BGSAVE命令,主进程fork后,复制自身并通过这个新的进程回写磁盘,回写结束后新进程自行关闭。由于这样做不需要主进程阻塞,系统不会假死,一般默认会采用这个方法。
默认采用方式2,所以如果我们要将数据刷到硬盘上,这时redis分配内存不能太大,否则很容易发生内存不够用无法fork的问题;
设置一个合理的写磁盘策略,否则写频繁的应用,也会导致频繁的fork操作,对于占用了大内存的redis来说,fork消耗资源的代价是很大的;

你可能感兴趣的:(Redis)