線上發現一臺機器內存負載很重,top後發現一個redis進程佔了大量的內存,TOP內容以下:html
27190 root 20 0 18.6g 18g 600 S 0.3 59.2 926:17.83 redis-server
發現redis佔了18.6G的物理內存。因爲redis只是用於cache一些程序數據,以爲很難以想象,執行redis的info命令,發現實際數據佔用只有112M,以下:node
# Memory used_memory:118140384 used_memory_human:112.67M used_memory_rss:19903766528 used_memory_peak:17871578336 used_memory_peak_human:16.64G used_memory_lua:31744 mem_fragmentation_ratio:168.48 mem_allocator:libc
因而用了pmap -x 27190 查看redis進程的內存映像信息,結果以下:linux
27190: ./redis-server ../redis.conf Address Kbytes RSS Dirty Mode Mapping 0000000000400000 548 184 0 r-x-- redis-server 0000000000689000 16 16 16 rw--- redis-server 000000000068d000 80 80 80 rw--- [ anon ] 0000000001eb6000 132 132 132 rw--- [ anon ] 0000000001ed7000 19436648 19435752 19435752 rw--- [ anon ] 00007f5862cb2000 4 0 0 ----- [ anon ]
發現存在大量的內存髒頁。如今內存負載高的問題已經比較清晰了,是因爲redis的髒頁佔用了大量的內存引發的。但是,redis爲何會存在那麼多的髒頁呢?redis
看了下linux髒頁的定義:緩存
髒頁是linux內核中的概念,由於硬盤的讀寫速度遠趕不上內存的速度,系統就把讀寫比較頻繁的數據事先放到內存中,以提升讀寫速度,這就叫高速緩存,linux是以頁做爲高速緩存的單位,當進程修改了高速緩存裏的數據時,該頁就被內核標記爲髒頁,內核將會在合適的時間把髒頁的數據寫到磁盤中去,以保持高速緩存中的數據和磁盤中的數據是一致的
也就是說,髒頁是由於內存中的不少數據沒來得及更新到磁盤致使的。看了下linux系統的髒頁flush機制:
http://blog.chinaunix.net/uid-17196076-id-2817733.html
發現跟內存flush的能夠進行設置(/proc/sys/vm底下)app
dirty_background_bytes/dirty_background_ratio: - 當系統的髒頁到達必定值(bytes或者比例)後,啓動後臺進程把髒頁刷到磁盤中,此時不影響內存的讀寫(當bytes設置存在時,ratio是自動計算的) dirty_bytes/dirty_ratio: - 當系統的髒頁到達必定值(bytes或者比例)後,啓動進程把髒頁刷到磁盤中,此時內存的寫可能會被阻塞(當bytes設置存在時,ratio是自動計算的) dirty_expire_centisecs: - 當內存和磁盤中的數據不一致存在多久之後(單位爲百分之一秒),纔會被定義爲dirty,並在下一次的flush中被清理。不一致以磁盤中文件的inode時間戳爲準 dirty_writeback_centisecs: - 系統每隔一段時間,會把dirty flush到磁盤中(單位爲百分之一秒)
查看當前系統的flush配置,發現沒問題,dirty_background_ratio爲10%,dirty_ratio爲20%,dirty_writeback_centisecs爲5s,dirty_expire_centisecs爲30s,但是爲啥redis的髒頁沒有被flush到磁盤中呢?dom
通常髒頁是要把內存中的數據flush到磁盤中,那麼會不會是redis的持久化致使了髒頁呢?查看下redis關於這些方面的配置:ui
rdb持久化已經被關閉 # save 900 1 # save 300 10 # save 60 10000 # append持久化也被關閉 appendonly no # 最大內存設置、內存替換策略都是默認值 # maxmemory <bytes> # maxmemory-policy volatile-lru
如上所示,發現redis自身已經徹底關閉持久化,只是做爲cache使用,並且對於最大內存使用默認值(表明沒有限制),內存的淘汰機制是volatile-lru。翻看redis的文檔,查看對應的淘汰機制lua
volatile-lru: 從已設置過時時間的數據集(server.db[i].expires)中挑選最近最少使用的數據淘汰(默認值) volatile-ttl: 從已設置過時時間的數據集(server.db[i].expires)中挑選將要過時的數據淘汰 volatile-random: 從已設置過時時間的數據集(server.db[i].expires)中任意選擇數據淘汰 allkeys-lru: 從數據集(server.db[i].dict)中挑選最近最少使用的數據淘汰 allkeys-random: 從數據集(server.db[i].dict)中任意選擇數據淘汰 no-enviction: 禁止驅逐數據
而在當前的使用環境中,程序對redis的使用是當作cache,而且會對數據設置expire超時時間,到期後等待redis進行刪除的。那麼髒頁的緣由,是否是由於過時數據清理機制的問題呢(好比清理不及時等)?所以,須要查看redis在對過時數據進行刪除時採起的策略,參考信息以下:
Redis中的內存釋放與過時鍵刪除
redis 過時鍵的清除spa
redis過時鍵刪除機制:
惰性刪除: - expire的鍵到期後,不會自動刪除,不過在每次讀取該鍵時進行檢查,檢查該鍵是否已通過期,若是過時,則進行刪除動做。這樣能夠保證刪除操做只會在非作不可的狀況下進行 按期刪除: - 每隔一段時間執行一次cron刪除操做,從每一個db的expire-keys中隨機找出必定數量的key(默認是20個),檢查key是否超時。若是已經超時,則進行刪除 - 經過限制刪除操做執行的時長和頻率,並以此來減小刪除操做對 CPU 時間的影響。 大於maxmemory的自動刪除: - 每次client和server進行command交互時,server都會檢查maxmemory的使用狀況 - 若是當前的內存已經超過了max-memory,那麼則進行清理,直到內存佔用在maxmemory線如下 - 清理的策略基於淘汰機制(LRU,TTL,RANDOM等)
redis對於過時鍵刪除使用的是 惰性刪除 + 按期刪除 + 大於maxmemory的自動清除 的策略。
經過以上的分析,問題已經比較明確了,緣由以下:
因爲某種緣由,redis使用的內存愈來愈大(多是因爲惰性刪除,致使expire的數據越積越多,或者其它緣由,具體緣由取決於redis內部的實現)
爲了解決這個問題,比較方便且合理的方法就是: