天天看點

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

參見note:https://www.zybuluo.com/SailorXiao/note/136014

case現場

線上發現一台機器記憶體負載很重,top後發現一個redis程序占了大量的記憶體,TOP内容如下:

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,如下:

# 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程序的記憶體映像資訊,結果如下:

  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會存在那麼多的髒頁呢? 

case 分析

看了下linux髒頁的定義:

髒頁是linux核心中的概念,因為硬碟的讀寫速度遠趕不上記憶體的速度,系統就把讀寫比較頻繁的資料事先放到記憶體中,以提高讀寫速度,這就叫高速緩存,linux是以頁作為高速緩存的機關,當程序修改了高速緩存裡的資料時,該頁就被核心标記為髒頁,核心将會在合适的時間把髒頁的資料寫到磁盤中去,以保持高速緩存中的資料和磁盤中的資料是一緻的。
           

也就是說,髒頁是因為記憶體中的很多資料沒來得及更新到磁盤導緻的。看了下linux系統的髒頁flush機制: 

http://blog.chinaunix.net/uid-17196076-id-2817733.html 

發現跟記憶體flush的可以進行設定(/proc/sys/vm底下)

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到磁盤中呢?

一般髒頁是要把記憶體中的資料flush到磁盤中,那麼會不會是redis的持久化導緻了髒頁呢?檢視下redis關于這些方面的配置:

  rdb持久化已經被關閉
  # save 900 1
  # save 300 10
  # save 60 10000

  # append持久化也被關閉
  appendonly no

  # 最大記憶體設定、記憶體替換政策都是預設值
  # maxmemory <bytes>
  # maxmemory-policy volatile-lru
           

如上所示,發現redis自身已經完全關閉持久化,隻是作為cache使用,而且對于最大記憶體使用預設值(代表沒有限制),記憶體的淘汰機制是volatile-lru。翻看redis的文檔,檢視對應的淘汰機制:

  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 過期鍵的清除

redis過期鍵删除機制:

惰性删除:
    -  到期後,不會自動删除,隻會在每次讀取鍵時進行檢查,檢查該鍵是否已經過期,如果過期,則進行删除動作。這樣可以保證删除操作隻會在非做不可的情況下進行
定期删除:
    - 每隔一段時間執行一次删除操作,并通過限制删除操作執行的時長和頻率,籍此來減少删除操作對 CPU 時間的影響。

redis使用的是惰性删除 + 定期删除的政策           

case 定位

通過以上的分析,問題已經比較明确了,原因如下:

  1. 由于某種原因,redis使用的記憶體越來越大(可能是由于惰性删除,導緻expire的資料越積越多,或者其它原因,具體原因取決于redis内部的實作)
  2. redis由于隻當做cache,并沒有實際讀寫檔案,是以作業系統并不會幫它flush到磁盤中(因為沒有地方可以flush)
  3. 由于redis沒有設定maxmemory,是以預設的是機器的記憶體大小,隻有當redis自身使用的記憶體達到機器記憶體大小時,redis才會自身進行清理(volatile-lru機制)
  4. 是以目前的redis的記憶體越來越大,而且髒頁資料越來越多(大部分可能都是已經過期的資料)

case解決

為了解決這個問題,比較友善且合理的方法就是:

  • 合理設定redis的maxmemory大小,用于讓redis實作自身的資料清理

27190   root    20   0  18.6g   18g  600 S  0.3     59.2    926:17.83   redis-server 
           
# 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
           
  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 ]
           

髒頁是linux核心中的概念,因為硬碟的讀寫速度遠趕不上記憶體的速度,系統就把讀寫比較頻繁的資料事先放到記憶體中,以提高讀寫速度,這就叫高速緩存,linux是以頁作為高速緩存的機關,當程序修改了高速緩存裡的資料時,該頁就被核心标記為髒頁,核心将會在合适的時間把髒頁的資料寫到磁盤中去,以保持高速緩存中的資料和磁盤中的資料是一緻的。
           
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到磁盤中(機關為百分之一秒)
           
  rdb持久化已經被關閉
  # save 900 1
  # save 300 10
  # save 60 10000

  # append持久化也被關閉
  appendonly no

  # 最大記憶體設定、記憶體替換政策都是預設值
  # maxmemory <bytes>
  # maxmemory-policy volatile-lru
           
  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:      禁止驅逐資料
           
惰性删除:
    -  到期後,不會自動删除,隻會在每次讀取鍵時進行檢查,檢查該鍵是否已經過期,如果過期,則進行删除動作。這樣可以保證删除操作隻會在非做不可的情況下進行
定期删除:
    - 每隔一段時間執行一次删除操作,并通過限制删除操作執行的時長和頻率,籍此來減少删除操作對 CPU 時間的影響。

redis使用的是惰性删除 + 定期删除的政策