■ 河南 劉建臣
單位的一臺Web服務器后臺使用的是Redis服務器,其工作一直比較正常。而最近出現無法寫入數據的故障,給業務帶來了很大的影響。
該機安裝的是CentOS6.X系統,使用的是四核的CPU,內存為16GB,在該機上執行“top”命令,發現內存的使用率為70%,CPU的負荷并不大。執行“df”“lsof”“iotop”等命令,發現磁盤讀寫情況沒有問題。進入Redis日志文件路徑(這里為“/var/log/redis”),然后執行“vi redis.log”命令,發現其中存在“Can't save in background:fork:Cannot allocate memory”錯誤提示。
根據以上錯誤信息,說明當Redis存儲數據時,因為內存無法分配導致故障的發生。但是執行“top”命令發現內存并沒有耗盡,似乎應該可以為Redis分配足夠的內存。
執行“vi/etc/redis/redis.conf”命令,在Redis配置文件中發現“save 900 1” “save 300 10” “save 60 10000” “appendonly no”“maxmemory 10GB”等信息。其中三個“save”項目指的是自動觸發Redis數據持久化到磁盤的策略,即當指定的時間內數據發生設定次數變動時,就會觸發“bgsave”命令將數據存儲到磁盤。
例如,對于“save 900 1”來說,當900秒內數據變化為1次時,就將變化的數據存儲到磁盤中。根據Redis日志文件提供的信息,可以看到當觸發了“save 60 1000”策略時,才出現了無法分配內存的故障。這意味著在60秒時間內,當數據變化的次數達到10000次時,才出現無法分配內存的問題。
但是對于其他兩個Save策略來說,在存儲數據時是沒有問題的。在“appendonly”項中的參數為“no”,說明并沒有開啟Resia的AOF持久化機制。如果開啟該機制,那么當每次執行寫操作時,都會自動記錄對應的Log日志,這將提高數據安全性。
我們知道,Redis是內存型數據庫,數據都是存儲在內存中,為了避免Redis進程關閉導致數據的永久丟失,需要定期將Redis中的數據以命令等形式從內存保存到硬盤。當以后重啟Redis時,就可以利用持久化機制實現數據恢復。
當然,為保護數據安全,還要盡可能的將持久化文件復制到遠程存儲空間中保存。Redis持久化分為RDB和AOF兩種方式,前者是將當前進程中的數據生成快照保存到硬盤,也稱快照持久化,保存的文件后綴是“.rdb”。當Redis重新啟動時,可以讀取快照文件恢復數據。
后者是將Redis執行的每次寫命令記錄到單獨的日志文件中,當Redis重啟時再次執行AOF文件中的命令來恢復數據。由于AOF持久化的實時性更好,遇到Redis異常退出時丟失的數據更少,因此AOF是目前主流的持久化方式。在默認狀態下,該機制是處于不開啟的狀態,即Redis會采取異步機制將數據寫入到磁盤中。在“maxmemory”參數中執行Redis可以使用的最大的內存量,當Redis在啟動時,會加能夠數據加載到內存中,當達到最大的內存使用量時,Redis會嘗試清除已經或者即將到期的Key鍵值。
當處理完畢后,如果依然達到最大的內存使用量,就無法執行寫入操作,但依然可以執行數據的讀取操作。這里的Redis的最大內庫存使用量為10GB,但是實際內存量為16GB,看起來還有很多可用的內存,并不應該出現無法分配內存的提示。
繼續查閱Redis日志文件,發現了“WARNING overcommit_memory is set to 0!” “Background save may fail under low memory condition”等提示信息,大意是說明在低內存的環境下,后臺存儲存儲數據存在風險。面對這種情況,一般的應對策略是打開“/etc/sysctl.conf”文件,在其中對名為“vm.overcommit_memory”的內存存儲參數進行修改。
之后執行“sysctl vm.overcommit_memory=1”命令命令使該修改生效。“vm.overcommit_memory”參數實際對應的是“/proc/sys/vm/overcommit_memory”文件,該文件的主要作用是指定系統內核針對內存的分配策略。該參數默認值為0,表示當內核對于內存分配情況進行檢測時,如果發現應有程序沒有足夠的可用內存的話,那么內存申請操作失敗,并將錯誤信息發送給目標程序。反之,則允許申請內存,保證應用程序正常工作。
如果將該參數的值設置為1,表示不管當前的內存使用情況處于什么狀態,都允許分配所有可用的物理內存內存給應用程序。如果參數設置為2,表示內核允許分配超過所有物理內存和交換空間總和的內存量。對于Redis來說,建議將參數的值設置為1,便于其利用所有的物理內存。在本例中該參數的值實際上設置為0,這意味著雖然系統存在一定的空閑內存,但是其小于Redis所申請的內存量,自然會出現無法分配內存的故障。
Redis的持久化機制數據回寫方式包括同步回寫和異步回寫。對于前者來說,Redis對應執行的是SAVE命令,即由Redis主進程直接將數據寫入磁盤。當寫入的數據量較大時,該命令將阻塞主進程,導致客戶端無法連接Redis,只有當SAVE操作完成后,主進程才開始工作,客戶端才可以正常連接Redis。
對于后者來說,實際上和Redis的BGSAVE命令對應。即Redis主進程通過fork一個子進程,復制主進程的內存并通過子進程將數據寫入到磁盤中。
在執行BGSAVE命令過程中,并不影響Redis主進程,客戶端可以正常連接Redis,等子進程fork執行存儲完成后,通知主進程并關閉子進程。
根據在Redis配置文件,可以看到當滿足了自動觸發Redis數據持久化到磁盤的策略時,Redis就會后臺調用BGSAVE命令,BGSAVE命令需要fork一個子進程,這就相當于復制了一個Redis主進程的內存鏡像。該服務器的物理內存是16GB,因為Redis主進程占用了最大10GB內存,對應的由BGSAVE命令行fork的子進程也需占用了最大10GB內存。
這樣,Redis就需要最大的內存量就變成了20GB內存,該機的物理內存顯然無法滿足該要求,所以才出現無法分配內存的故障,這必然導致Redis無法向磁盤寫入數據的問題。
根據以上分析,解決問題最直接的方法是對Redis的配置文件進行修改,將“maxmemory 10GB”配置項進行修改,降低其最大內存使用量,例如修改為“maxmemory 5GB”等。之后執行“/etc/init.d/redisserver restart”之類的命令來重啟Redis,讓配置生效。也可以增加該機的而無力內存量,例如將物理內存增加到32GB,來滿足實際的需要。