Redis会定期将内存中的数据同步到磁盘,这是我们大家都知道。而且是写数据越频繁同步的也就越频繁,这是在Redis配置文件中可配置的。
一般来说,宕机后可能丢失小量数据是在所难免的。可如果宕机后重启发现丢失大量数据这就不正常了,查了些资料,受益非浅。
1、同步回写即SAVE命令,主进程直接向磁盘回写数据。在数据量大的情况下会导致系统假死很长时间,这个情况下对Redis的访问通常也会得不到响应,所以一般不是推荐的。
2、异步回写即BGSAVE命令,主进程fork出一个新进程后,复制自身并通过这个新的进程回写磁盘,回写结束后新进程自行关闭。由于这样做不需要主进程阻塞,系统不会假死,一般默认会采用这个方法。
这里小编就找到一篇在生产环境中使用Redis宕机后数据丢失近80%的案例。(原文在这里哦,骚年),内容如下。
碰到一个悲催的事情:一台Redis服务器,4核,16G内存且没有任何硬件上的问题。持续高压运行了大约3个月,保存了大约14G的数据,设置了比较完备的Save参数。而就是这台主机,在一次重起之后,丢失了大量的数据,14G的数据最终只恢复了几百兆而已。
正常情况下,像Redis这样定期回写磁盘的内存数据库,丢失几个数据也是在情理之中,可超过80%数据丢失率实在太离谱。排除了误操作的可能性之后,开始寻找原因。
重启动时的日志:
[26641] 21 Dec 09:46:34 * Slave ask for synchronization
[26641] 21 Dec 09:46:34 * Starting BGSAVE for SYNC
[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 BGSAVE
[26641] 21 Dec 09:46:34 # Received SIGTERM, scheduling shutdown…
[26641] 21 Dec 09:46:34 # User requested shutdown…
很明显的一个问题,系统不能在后台保存,fork进程失败。
翻查了几个月的日志,发觉系统在频繁报错:
[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 */
}
数据丢失的问题总算搞清楚了!
个人感觉方法2采用fork主进程的方式有些拙劣,但似乎是唯一的方法。
内存中的热数据随时可能修改,要在磁盘上保存某个时间的内存镜像必须要冻结。冻结就会导致假死。
fork一个新的进程之后等于复制了当时的一个内存镜像,这样主进程上就不需要冻结,只要子进程上操作就可以了。
在小内存的进程上做一个fork,不需要太多资源。但当这个进程的内存空间以G为单位时,fork就成为一件很恐怖的操作。如果是在一个有16G内存配置的主机上fork出一个13G内存的进程呢?如果swap有8G,那么肯定会报内存无法分配的,这样数据就无法回写进磁盘。如果这个主机宕机了,那么丢失的数据就不是一点点了。而且越是改动频繁的主机上fork也越频繁,fork一个大数据进程的操作所花费的代价也不容忽视,fork操作本身的代价恐怕也不会比假死好多少。还要考虑物理内存与swap的数据交换所造成的消耗。
所以在使用Redis时要保证数据量 小于 物理内存的一半,个人这么认为。
找到原因之后,直接修改内核参数vm.overcommit_memory = 1,Linux内核会根据参数vm.overcommit_memory参数的设置决定是否放行。
如果 vm.overcommit_memory = 1,直接放行。
#比较此次请求分配的虚拟内存大小和系统当前空闲的物理内存加上swap,决定是否放行。
vm.overcommit_memory = 0
#比较进程所有已分配的虚拟内存加上此次请求分配的虚拟内存和系统当前的空闲物理内存加上swap,决定是否放行。
vm.overcommit_memory = 2
看过很多文章,都拿Redis和Memcached来比较使用。刚开始也觉得Redis可以代替Memcached。可后来细想后发现有问题。
如果Memcached服务器宕机了,那么服务器重启后,Memcached里所缓存的数据也就被清空了。再有数据访问时会先从关系型数据库中查询出来后,往Memcache存一份。让下一次同样的数据访问可以命中Memcached。
而Redis不仅会将数据缓存在内存中,也会将数据回写进磁盘。那么如果Redis服务器宕机了,宕机后关系型数据库中的数据被更新过了。然后重启Redis服务器,此时Redis服务器里的数据和关系型数据库中的数据就可能不一致了。除非重启Redis后立即清空其中做为缓存的数据。
当然,也可以使用主从模式的Redis集群来降低此类风险。如果宕掉的是从服务器那不会出现上述问题,但如果宕掉的是主服务器呢,问题则依旧。
因此我个人认为是否用Redis来替代Memcached看实际的应用场景,还是仁见仁、智见智的问题