彙總使用Redis應該注意數據安全

目錄結構

一、Redis宕機後的數據丟失問題

Redis會定期將內存中的數據同步到磁盤,這是我們大家都知道。而且是寫數據越頻繁同步的也就越頻繁,這是在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當緩存服務來使用

看過很多文章,都拿Redis和Memcached來比較使用。剛開始也覺得Redis可以代替Memcached。可後來細想後發現有問題。

如果Memcached服務器宕機了,那麼服務器重啓後,Memcached裏所緩存的數據也就被清空了。再有數據訪問時會先從關係型數據庫中查詢出來後,往Memcache存一份。讓下一次同樣的數據訪問可以命中Memcached。

而Redis不僅會將數據緩存在內存中,也會將數據回寫進磁盤。那麼如果Redis服務器宕機了,宕機後關係型數據庫中的數據被更新過了。然後重啓Redis服務器,此時Redis服務器裏的數據和關係型數據庫中的數據就可能不一致了。除非重啓Redis後立即清空其中做爲緩存的數據。

當然,也可以使用主從模式的Redis集羣來降低此類風險。如果宕掉的是從服務器那不會出現上述問題,但如果宕掉的是主服務器呢,問題則依舊。

因此我個人認爲是否用Redis來替代Memcached看實際的應用場景,還是仁見仁、智見智的問題

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章