redis:数据持久化失败

redis:数据持久化失败

[TOC]

一:起因

​ 因业务需求,需要将2.7亿的数据存入reids数据库,以保证后期的查询效率。在使用Python脚本语言往Redis数据库插入数据到1.3亿的数据时,差不多用了16G的内存,window的系统内存为32G,相对于支持其他exe的开销,redis的内存都是很在允许范围内的,并且我设置了redis的最大内存为28G,之后多次尝试下,依旧保错,保错内容如下:

1
redis.exceptions.ResponseError: MISCONF 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.

​ 保错的大概意思就是:Redis被配置为保存数据库快照,但它目前不能持久化到硬盘。用来修改集合数据的命令不能用。请查看Redis日志的详细错误信息。

​ 日志内容保错如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
=== REDIS BUG REPORT START: Cut & paste starting from here ===
[17880] 03 Dec 15:53:09.292 # Redis version: 3.0.504
[17880] 03 Dec 15:53:09.292 # --- EXCEPTION_ACCESS_VIOLATION
[17880] 03 Dec 15:53:09.292 # --- STACK TRACE
redis-server.exe!LogStackTrace(c:\release\redis\src\win32_interop\win32_stacktrace.cpp:95)(0x0014E620, 0x0014FF60, 0x0014E620, 0x40124730)
redis-server.exe!UnhandledExceptiontHandler(c:\release\redis\src\win32_interop\win32_stacktrace.cpp:185)(0x40028E60, 0x40028E60, 0x0014E620, 0x00000008)
KERNELBASE.dll!UnhandledExceptionFilter(c:\release\redis\src\win32_interop\win32_stacktrace.cpp:185)(0x00000000, 0x37002008, 0x00000000, 0x00000000)
ntdll.dll!memset(c:\release\redis\src\win32_interop\win32_stacktrace.cpp:185)(0x0014F2B0, 0x00000000, 0x0014F2B0, 0x0014EBE8)
ntdll.dll!_C_specific_handler(c:\release\redis\src\win32_interop\win32_stacktrace.cpp:185)(0x00000000, 0x0014EBD0, 0x00000000, 0x40000000)
ntdll.dll!_chkstk(c:\release\redis\src\win32_interop\win32_stacktrace.cpp:185)(0x0014EBD0, 0x00000000, 0x344BBC3C, 0x34410000)
ntdll.dll!RtlWalkFrameChain(c:\release\redis\src\win32_interop\win32_stacktrace.cpp:185)(0x0014EE10, 0x40050ACC, 0x00000385, 0x0000000B)
ntdll.dll!KiUserExceptionDispatcher(c:\release\redis\src\win32_interop\win32_stacktrace.cpp:185)(0x52B0FA8D, 0x4008EBC9, 0x03C07040, 0x0014F6B0)
redis-server.exe!rdbSaveStringObject(c:\release\redis\src\rdb.c:334)(0x0014F6B0, 0x00000385, 0x4004FE00, 0x03C07070)
redis-server.exe!rdbSaveObject(c:\release\redis\src\rdb.c:605)(0x0014F6B0, 0x00000001, 0x6E06F310, 0x00000001)
redis-server.exe!rdbSaveRio(c:\release\redis\src\rdb.c:694)(0x40150210, 0x00DD0000, 0x00000005, 0x011D2754)
redis-server.exe!rdbSave(c:\release\redis\src\rdb.c:758)(0x00DD0000, 0x61A4BBB6, 0x00DD0000, 0x00000005)
redis-server.exe!QForkChildInit(c:\release\redis\src\win32_interop\win32_qfork.cpp:337)(0x00000005, 0x00000000, 0x0045EB60, 0x00000005)
redis-server.exe!QForkStartup(c:\release\redis\src\win32_interop\win32_qfork.cpp:515)(0x00000006, 0x00000000, 0x00000000, 0x00464F20)
redis-server.exe!main(c:\release\redis\src\win32_interop\win32_qfork.cpp:1240)(0x00000000, 0x00000000, 0x00000000, 0x00000000)
redis-server.exe!__tmainCRTStartup(f:\dd\vctools\crt\crtw32\startup\crt0.c:255)(0x00000000, 0x00000000, 0x00000000, 0x00000000)
KERNEL32.DLL!BaseThreadInitThunk(f:\dd\vctools\crt\crtw32\startup\crt0.c:255)(0x00000000, 0x00000000, 0x00000000, 0x00000000)
ntdll.dll!RtlUserThreadStart(f:\dd\vctools\crt\crtw32\startup\crt0.c:255)(0x00000000, 0x00000000, 0x00000000, 0x00000000)
ntdll.dll!RtlUserThreadStart(f:\dd\vctools\crt\crtw32\startup\crt0.c:255)(0x00000000, 0x00000000, 0x00000000, 0x00000000)
[17880] 03 Dec 15:53:09.295 # --- INFO OUTPUT
[13488] 03 Dec 15:53:14.518 # fork operation failed
[13488] 03 Dec 15:53:14.940 # Background saving terminated by signal 1
[13488] 03 Dec 15:53:15.042 * 10 changes in 300 seconds. Saving...
[13488] 03 Dec 15:53:15.086 * Background saving started by pid 12472
[13488] 03 Dec 15:53:39.262 # fork operation complete
[13488] 03 Dec 15:53:39.702 * Background saving terminated with success
[13488] 03 Dec 16:03:45.009 #

​ 阔以在日志中看到一堆乱七八糟的保错信息,在最后的日志中阔以清楚的看到fork operation failed,那该报错的信息就是尝试 fork 的时候内存不够,redis的持久化失败了。

二:经过

1、方法一:加内存

​ 数据量存入不到一半,内存已经到16G了,全量存入的话,32G的内存也自然放不下,便又加入了一个一根32G的内存条,加上条子 —> 调整最大内存50G —-> 重启服务 —–> 运行程序

​ 结果依然报错,错误原因一样。

2、方法二:stop-writes-on-bgsave-error :no

​ 按照网上一个copy一个的垃圾帖子上的提示,用记事本打开Redis安装目录中的redis.windows.conf和redis.windows-service.conf文件,然后搜索关键字stop-writes-on-bgsave-error,将其属性设置为no(默认为yes),保存重启Redis服务即可。

​ 这个大概意思就是 如果 bgsave 存储快照失败,那么 redis 将阻止数据继续写入,如果将这个设置成 no 那么即使是 bgsave 快照写入磁盘失败,也不会让 redis 立即对外停止服务。

​ 但是最重要的数据依然无法落盘,不符合业务需求,那这个配置毫无意义。

3、方法三:vm.overcommit_memory

​ 编辑文件 /etc/sysctl.conf 添加:

1
vm.overcommit_memory=1

​ 执行sysctl -p使其生效;

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

1
2
3
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 用于限制系统应用使用的内存资源:

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

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

虚拟内存算法:

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

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

1
应用程序要申请的内存 + 系统已经分配的内存(也就是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大小,如果这么多资源已经用光,那么后面任何尝试申请内存的行为都会返回错误,这通常意味着此时没法运行任何新程序。

​ 但问题是,我的业务系统是Windows,并非Linux!!!

4、方法四:分库写入

​ 我在redis可视化工具页面看到,写入失败后,redis 的DB0 中的 keys 为100%,如下图:

keys 为100%

​ 猜测是不是DB0的键值对到达一定的限制了,毕竟一亿多了呢,然后我便改写程序,让2.7亿的数据随机落盘至DB0~DB4,结果果然成功了。数据安全落入redis数据库中。

数据安全落入redis数据库

三:结果

​ 最终数据落盘成功,总结下来,网上的帖子千篇一律,不过作用不大。还是得依靠自己的判断与知识去解决问题。

​ 最后推荐一篇文章:linux 进程 fork()


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!