在做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的数据回写机制分同步和异步两种,

  1. 同步回写即SAVE命令,主进程直接向磁盘回写数据。在数据大的情况下会导致系统假死很长时间,所以一般不是推荐的。

  2. 异步回写即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