AOF重寫導致的Redis程序被kill
Redis環境描述
- 伺服器: 阿里雲16GB伺服器
- Redis版本: 5.0.5
- 持久化方式: AOF
問題描述
阿里雲環境,使用docker安裝的單節點redis5.x,頻繁出現redis程序被作業系統kill,直到redis容器直接啟動失敗,查詢/var/log/messages
檔案,可以看到以下內容:
谷歌了一下total-vm
和anon-rss
,沒看太明白什麼意思,伺服器實體記憶體是16GB,姑且認為total-vm是實體記憶體,anon-rss就是redis程序佔用的記憶體量了,這麼看應該是redis佔用記憶體過高導致的程序被殺;
問題查詢
檢視redis持久化檔案appendonly.aof儲存目錄,如下所示:
從上圖可以看到當前目錄存在很多temp-rewriteaof-xxx.aof,這是aof檔案重寫時產生的臨時檔案,xxx表示重寫時fork的子程序的程序號,這裡存在這麼多的臨時檔案表示redis已經進行了很多次重寫,但是因為記憶體不足導致子程序被kill掉。檢視阿里雲的監控資訊,發現確實存在記憶體飆升的情況:
正常來說子程序被kill掉,不應該影響redis容器,但是現在的情況是redis容器直接不可用了,需要重啟docker服務才可以,這個應該跟docker的程序管理有關,不做深究,現在基本可以定位導致問題的原因是redis發生aof重寫時由於記憶體不足導致子程序被kill掉,從而導致redis服務不可用
aof重寫原理
這個要從AOF檔案重寫的過程來說,AOF是Redis的一種持久化方式,在客戶端執行寫入命令時,Redis會將命令快取在AOF緩衝區中,再根據同步策略(三種:always、everysec、no)將命令同步到appendonly.aof檔案中,隨著aof檔案越來越大,達到配置檔案中auto-aof-rewrite-min-size
和auto-aof-rewrite-percentage
引數配置的閾值時,redis將觸發aof重寫,此時會fork一個子程序進行aof重寫,在aof重寫過程中客戶端新的寫入命令會暫存於aof重寫緩衝區中,直到子程序重寫完成後,將aof重寫緩衝區中的內容再追加到新的aof檔案中,最後使用新的aof檔案替換舊的aof檔案。
基於以上aof重寫原理可以知道,如果在子程序重寫過程中,系統的寫入量很大,那麼aof重寫緩衝區佔用的記憶體就會越來越大,從而導致記憶體佔用量持續上升。
問題處理
修改redis配置檔案中auto-aof-rewrite-percentage
引數值為800,表示噹噹前的aof檔案大小是上次重寫後aof檔案大小的8倍時才觸發重寫。
然後將redis重啟,經過漫長的資料載入之後,通過redis客戶端工具可以看到,redis中資料已經超過13GB,伺服器的屋裡記憶體是16GB,按理說fork子程序進行重寫時使用的是copy_on_write
,每10GB記憶體只需要20MB左右的記憶體頁表,還剩下3GB的記憶體可用,即使aof複製緩衝區zai在持續增大也不至於直接將redis給kill掉,這個牽扯到作業系統另外一個引數的配置,如下所示:
這個牽扯到linux記憶體分配的問題,不做深究,根據提示就是需要將vm.overcommit_memory
這個引數由0改為1,表示核心允許超量使用記憶體直到用完為止,設定命令如下:
$ echo "vm.overcommit_memory=1" >> /etc/sysctl.conf
$ sysctl vm.overcommit_memory=1
redis啟動時另外一個警告,如下:
這個是redis核心預設開啟了THP特性,支援大記憶體頁分配,當開啟時可以降低fork子程序的速度,但fork操作之後,每個記憶體頁由4k變成了2M,這個會大幅度增加重寫期間主程序記憶體的消耗,同時每次寫命令引起的複製記憶體頁單位放大了512倍,會拖慢寫操作的執行時間,導致大量的寫操作慢查詢,因此redis建議關閉該特性。
禁用命令:
$ echo never > /sys/kernel/mm/transparent_hugepage/enabled
以上兩個引數修改完成以後還需要修改redis的最大記憶體,建議單個redis最大記憶體在10GB以內,但是目前redis的記憶體佔用已經達到了13GB,因此將該redis遷移到另外一臺記憶體為32GB的伺服器上,因為還要進行其他操作暫未設定最大記憶體,如果考慮叢集的話最好單個redis記憶體不超過10GB,不搭建叢集的話需要設定redis的最大記憶體,建議保留20%-30%的空閒實體記憶體。
本次問題先使用臨時的解決方案進行處理,後續優化快取內容以及擴充套件機器以後再進行整體的優化配置。