Redis Fork导致OMM killer

OOM 简介


Out Of Memory Killer 是 Linux 的一种系统保护机制,实现了内存紧张时 kill 掉某些进程防止系统卡死的问题。内核官方文档在此 kernel vm instruction。

Kill 的默认机制是扫描所有进程任务的内存占用、CPU占用等因素然后打分(badness),分值越高,kill 的优先级越高。进程分值可以在 /proc/PID/oom_score 文件中查看。分值范围为-17 ~ 50。可以通过手动将一个进程的 oom_score 配置为-17来防止该进程被 kill。

OOM Killer 配置有两种方法:

  1. 在 /etc/sysctl.conf 中配置,然后 sysctl -p 更新
  2. 直接 echo 值到 /proc/sys/vm 中对应的参数接口

OOM 常用配置项

vm.panic_on_oom:触发 oom 机制时是否触发 kernel panic,打开会在触发OOM时重启机器,推荐配置为 0(关闭)

vm.oom_kill_allocating_task:直接 kill 掉触发 oom 机制的进程,而不去扫描进程然后打分(会占用比较多的资源)。此案例中推荐配置为 1 打开,因为内存泄露的进程会以很快的速度占满内存,很可能再扫描打分结束前系统就 freeze 了。

vm.overcommit_memory:是否允许程序申请过量的内存,默认为0。有0,1,2三个选项(此案例推荐为2,平时推荐为0):

0:内核会预估是否有充足的内存,然后再为进程分配内存
1:内核会永远认为有充足的内存可用,进程申请内存时总是允许
2:内核永远不允许进程申请定额以上的内存,定额有两个参数可以配置
vm.overcommit_kbytes:最大允许申请的内存,单位为 kbytes,配置后,应用程序不允许申请 swap + 该值 以上的内存。默认为0表示禁用。

 

Redis 引发系统OOM Killer


昨晚(2016-9-5),生产环境的Redis发生警报,一段时间后,内存被降到50%多(之前一直在90%左右),然后发现Redis的进程挂了。第一时间看Redis的log文件,发现有如下信息:

 

1

2

3

4

5

6

7

8

9

 

10866:M 05 Sep 20:15:19.711 # Background saving terminated by signal 9

10866:M 05 Sep 20:18:43.898 # Background saving terminated by signal 9

10866:M 05 Sep 20:26:46.434 # Background saving terminated by signal 9

10866:M 05 Sep 20:34:49.161 # Background saving terminated by signal 9

10866:M 05 Sep 20:42:52.406 # Background saving terminated by signal 9

10866:M 05 Sep 20:42:55.332 # Background saving terminated by signal 9

1758:M 05 Sep 21:28:11.114 # Background saving terminated by signal 9

1758:M 05 Sep 21:30:18.479 # Background saving terminated by signal 9

1758:M 05 Sep 21:32:55.275 # Background saving terminated by signal 9

可知Redis收到kill -9的信号终止了.然后,当时第一反应,应该是“有人”人工去kill Redis进程吗?不知道怎么的,当时自己就去查看操作系统日志dmesg -T | grep redis,真的发现是有内幕:

 

1

2

3

4

5

6

7

8

9

10

11

12

 

dmesg -T | grep redis | grep "Out of memory"

[Mon Sep 5 20:15:18 2016] Out of memory: Kill process 725 (redis-server) score 517 or sacrifice child

[Mon Sep 5 20:18:42 2016] Out of memory: Kill process 786 (redis-server) score 517 or sacrifice child

[Mon Sep 5 20:26:45 2016] Out of memory: Kill process 914 (redis-server) score 517 or sacrifice child

[Mon Sep 5 20:34:48 2016] Out of memory: Kill process 1022 (redis-server) score 517 or sacrifice child

[Mon Sep 5 20:42:50 2016] Out of memory: Kill process 1127 (redis-server) score 517 or sacrifice child

[Mon Sep 5 20:42:52 2016] Out of memory: Kill process 10866 (redis-server) score 517 or sacrifice child

[Mon Sep 5 20:50:57 2016] Out of memory: Kill process 1235 (redis-server) score 517 or sacrifice child

[Mon Sep 5 20:50:57 2016] Out of memory: Kill process 10866 (redis-server) score 517 or sacrifice child

[Mon Sep 5 21:28:10 2016] Out of memory: Kill process 1886 (redis-server) score 479 or sacrifice child

[Mon Sep 5 21:30:17 2016] Out of memory: Kill process 1758 (redis-server) score 479 or sacrifice child

[Mon Sep 5 21:32:54 2016] Out of memory: Kill process 1972 (redis-server) score 479 or sacrifice child

 

1

2

3

 

dmesg -T | grep redis | grep "oom-killer"

[Mon Sep 5 20:26:44 2016] redis-server invoked oom-killer: gfp_mask=0x10200da, order=0, oom_score_adj=0

[Mon Sep 5 21:32:53 2016] redis-server invoked oom-killer: gfp_mask=0x8200da, order=0, oom_score_adj=0

当天查看的Redis内存信息如下:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

 

# Memory

used_memory:7877146344

used_memory_human:7.34G

used_memory_rss:8699490304

used_memory_rss_human:8.10G

used_memory_peak:8462552976

used_memory_peak_human:7.88G

total_system_memory:16828653568

total_system_memory_human:15.67G

used_memory_lua:37888

used_memory_lua_human:37.00K

maxmemory:9573741824

maxmemory_human:8.92G

maxmemory_policy:noeviction

mem_fragmentation_ratio:1.10

mem_allocator:jemalloc-4.0.3

 

原因


当时服务器还有个MySQL slave在进行复制备份,服务器一共16GB的内存,然后MySQL用掉了5GB,还有11GB内存,除去一些其他的简单的占用和消耗外,估计还有10GB的内存真正给Redis可用。

可以看到上面的INFO,当时分配给Redis最大的内存为差不多9GB,那应试还有1GB可用空间,那到底是什么导致了OS触发OOM Killer机制呢?

原来,Redis当时开启了RDB功能,而Redis自身是通过fork()进程来处理RDB文件的。可以man fork知道,它是精确复制与父进程来处理RDB文件的。

Redis在后台的存储机制依赖于操作系统中fork的copy-on-write:也就是redis fork(创建一个子进程)是父进程的一个完整精确拷贝。子进程转储到磁盘上的数据库然后退出。理论上来说,子进程作为一个副本应该使用和父亲一样多的内存,但是实际上由于大部分现代操作系统的copy-on-write的实现,父进程和子进程将共享内存页。当他被父进程或者子进程改变的时候,一个内存页将被复制。因此,从理论上讲,当子进程存储的时候,所有内存页可能被改变,Linux不能提前告诉子进程多少内存被使用,所以如果overcommit_memory设置被设置为0,创建将会失败,除非有同样多的空闲内存。结果是,如果你有3GB的redis数据并且只有2GB的空闲内存,它将会失败。

把overcommit_memory设置为1来告诉Linux以更加乐观的方式来执行fork操作,并且这确实是你想要的。

由于REdis一般占大内存,所以通常需要关闭系统的OOM,方法为将“/proc/sys/vm/overcommit_memory”的值设置为1(通常不建议设置为2),也可以使用命令sysctl设置,如:sysctl vm.overcommit_memory=1,但注意一定要同时修改文件/etc/sysctl.conf,以便得系统重启后仍然生效:

# vi /etc/sysctl.conf

vm.overcommit_memory=1

 

Redis中,fork是指在进行RDB持久化和AOF重写时,创建一个子进程来执行这些任务。引用提到,在fork时,内存中的数据会被克隆一份,这会导致内存消耗翻倍。尽管Redis使用了写时拷贝技术来减少内存消耗,但如果数据很大,仍然会对性能产生一定的影响。此外,如果Redis在备份周期内意外关闭,则会丢失最后一次快照后的所有修改。所以,fork在备份和恢复数据方面具有一些劣势。 另外,需要注意的是,fork不仅在RDB持久化中使用,还在AOF重写中使用,其中AOF是一种以追加方式记录操作日志的持久化方式。当AOF文件大小达到一定条件时,Redis会触发重写操作。引用提到,默认配置下,当AOF文件大小是上次重写后大小的2倍且文件大小大于64MB时,会触发AOF重写。重写操作可以节约磁盘空间并减少数据恢复的时间,但每次重写仍然会增加一定的负担。 所以,forkRedis中起到了数据备份和恢复的作用,但是也需要注意其劣势和对性能的影响。123 #### 引用[.reference_title] - *1* *2* *3* [Redis 的持久化](https://blog.csdn.net/qq_44704799/article/details/126639040)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值