碰到一個悲催的事情:一臺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,決定是否放行。