碰到一个悲催的事情:一台Redis服务器,4核,16G内存且没有任何硬件上的问题。持续高压运行了大约3个月,保存了大约14G的数据,设置了比较完备的Save参数。而就是这台主机,在一次重起以后,丢失了大量的数据,14G的数据最终只恢复了几百兆而已。redis
正常状况下,像Redis这样按期回写磁盘的内存数据库,丢失几个数据也是在情理之中,可超过80%数据丢失率实在太离谱。排除了误操做的可能性以后,开始寻找缘由。数据库
重启动时的日志:服务器
[26641] 21 Dec 09:46:34 * Slave ask for synchronization异步
[26641] 21 Dec 09:46:34 * Starting BGSAVE for SYNCspa
[26641] 21 Dec 09:46:34 # Can’t save in background: fork: Cannot allocate memory日志
[26641] 21 Dec 09:46:34 * Replication failed, can’t BGSAVEcode
[26641] 21 Dec 09:46:34 # Received SIGTERM, scheduling shutdown…server
[26641] 21 Dec 09:46:34 # User requested shutdown…进程
很明显的一个问题,系统不能在后台保存,fork进程失败。ip
翻查了几个月的日志,发觉系统在频繁报错:
[26641] 18 Dec 04:02:14 * 1 changes in 900 seconds. Saving…
[26641] 18 Dec 04:02:14 # Can’t save in background: fork: Cannot allocate memory
系统不能在后台保存,fork进程时没法指定内存。
对源码进行跟踪,在src/rdb.c中定位了这个报错:
int rdbSaveBackground(char *filename) { pid_t childpid; long long start; if (server.bgsavechildpid != -1) return REDIS_ERR; if (server.vm_enabled) waitEmptyIOJobsQueue(); server.dirty_before_bgsave = server.dirty; start = ustime(); if ((childpid = fork()) == 0) { /* Child */ if (server.vm_enabled) vmReopenSwapFile(); if (server.ipfd > 0) close(server.ipfd); if (server.sofd > 0) close(server.sofd); if (rdbSave(filename) == REDIS_OK) { _exit(0); } else { _exit(1); } } else { /* Parent */ server.stat_fork_time = ustime()-start; if (childpid == -1) { redisLog(REDIS_WARNING,"Can't save in background: fork: %s", strerror(errno)); return REDIS_ERR; } redisLog(REDIS_NOTICE,"Background saving started by pid %d",childpid); server.bgsavechildpid = childpid; updateDictResizePolicy(); return REDIS_OK; } return REDIS_OK; /* unreached */ }
数据丢失的问题总算搞清楚了!
Redis的数据回写机制分同步和异步两种,
同步回写即SAVE命令,主进程直接向磁盘回写数据。在数据大的状况下会致使系统假死很长时间,因此通常不是推荐的。
异步回写即BGSAVE命令,主进程fork后,复制自身并经过这个新的进程回写磁盘,回写结束后新进程自行关闭。因为这样作不须要主进程阻塞,系统不会假死,通常默认会采用这个方法。
我的感受方法2采用fork主进程的方式很拙劣,但彷佛是惟一的方法。内存中的热数据随时可能修改,要在磁盘上保存某个时间的内存镜像必需要冻结。 冻结就会致使假死。fork一个新的进程以后等于复制了当时的一个内存镜像,这样主进程上就不须要冻结,只要子进程上操做就能够了。
在小内存的进程上作一个fork,不须要太多资源,但当这个进程的内存空间以G为单位时,fork就成为一件很恐怖的操做。况且在16G内存的主机 上fork 14G内存的进程呢?确定会报内存没法分配的。更可气的是,越是改动频繁的主机上fork也越频繁,fork操做自己的代价恐怕也不会比假死好多少。
找到缘由以后,直接修改内核参数vm.overcommit_memory = 1
Linux内核会根据参数vm.overcommit_memory参数的设置决定是否放行。
若是 vm.overcommit_memory = 1,直接放行
vm.overcommit_memory = 0:则比较 这次请求分配的虚拟内存大小和系统当前空闲的物理内存加上swap,决定是否放行。
vm.overcommit_memory = 2:则会比较 进程全部已分配的虚拟内存加上这次请求分配的虚拟内存和系统当前的空闲物理内存加上swap,决定是否放行。