在做Redis主从同步的时候,前几天一切同步都正常OK,过了几天后,测试人员反映,添加的数据读取不到,当时内部网络进行了一下升级,
第一个想法:怀疑是升级后端口内部没有开发端口,导致不能往外同步数据,找网络管理员确认了一下,不是该问题导致的
第二步:重启一下Redis,重启之后发现过了一段时间可以同步了,可是时间久了之后又不能同步。
第三步:去查看redis的最近日志,发现日志中报错信息都是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
根据度娘的解释:执行如下操作,编辑sysctl.conf文件
vim /etc/sysctl.conf
添加如下行:vm.overcommit_memory = 1
转载于:https://blog.51cto.com/suiwnet/1344485