redis存在大量髒頁問題的追查記錄

1、case現場

線上發現一臺機器內存負載很重,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

2、case 分析

看了下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的自動清除 的策略。

3、case 定位

經過以上的分析,問題已經比較明確了,緣由以下:

  1. 因爲某種緣由,redis使用的內存愈來愈大(多是因爲惰性刪除,致使expire的數據越積越多,或者其它緣由,具體緣由取決於redis內部的實現)

  2. redis因爲只當作cache,並無實際讀寫文件,所以操做系統並不會幫它flush到磁盤中(由於沒有地方能夠flush)
  3. 因爲redis沒有設置maxmemory,所以默認的是機器的內存大小,只有當redis自身使用的內存達到機器內存大小時,redis纔會自身進行清理(volatile-lru機制)
  4. 所以當前的redis的內存愈來愈大,並且髒頁數據愈來愈多(大部分可能都是已通過期的數據)

4、case解決

爲了解決這個問題,比較方便且合理的方法就是:

  • 惰性刪除有時候並非很靠譜,特別對於一些log類型的數據,在寫入redis後就無論了,雖然設置了超時時間,可是沒有效果
  • 按期刪除對於expire-keys也不是很靠譜,存在隨機性,也可能過時好久的數據沒有刪除
  • 相對而言,比較合理的方式是基於使用狀況設置redis的maxmemory大小,用於讓redis實現自身的數據清理機制,確保把mem限制在maxmemory設定範圍內
相關文章
相關標籤/搜索