Redis 「瘦身」指南

前言

Redis 應該是開發者最經常使用的緩存服務器了,它豐富的數據結構,快速高效的內存操做能幫助開發者迅速完成複雜功能的設計,能夠說讓人一旦使用事後很難再離開它了,甚至在一些業務中,徹底能夠用 Redis 替代傳統的關係型數據庫 Mysql。css

做爲一個內存型數據庫,Redis 常常會遇到內存問題,今天咱們來談一下 Redis 常見的內存滿的問題,介紹一下給 Redis 「瘦身」的通用方式。html

文章常常被人爬,並且還不註明原地址,我在這裏的更新和糾錯無法同步,這裏註明一下原文地址:http://www.cnblogs.com/zhenbianshu/p/7642682.html 以防誤人子弟。web


Redis內存回收

Redis 服務器的最大佔用內存量由配置項 maxmemory 決定,咱們能夠經過 config set maxmemory 2GB 的格式來配置。一旦 Redis 內存滿,全部引發內存增長的操做都會被返回 error。做爲專業 Redis 服務器咱們一般將此項設置爲0,以服務器系統內存來做爲限制;redis

那麼 Redis 使用內存達到了上限怎麼辦?Redis 爲咱們提供了幾種選項以自動回收內存,能夠經過配置項 maxmemory-policy 來配置;sql

  • noeviction 不回收;
  • allkeys-lru 從全部鍵中刪除最近最少使用的鍵;
  • volatile-lru 從設置了過時時間的鍵中刪除最近最少使用的鍵;
  • allkeys-random 從全部鍵中隨機刪除;
  • volatile-random 從設置了過時時間的鍵中隨機刪除;
  • volatile-ttl 從設置了過時時間的鍵中選擇存活時間最短的鍵刪除;

最大內存回收策略須要根據業務來配置,若是純粹作緩存,allkeys-lru無疑是最合適的。若是存儲了稍微重要的數據,爲了防止 Redis 誤刪一些重要鍵,則須要選用 noeviction數據庫

allkeys-lruallkeys-random 在內存滿時都有鍵可刪,能夠騰出內存,但若是配置了其餘的策略,數據庫用久了(根據業務量),隨着業務發展和數據積累,一般會累積到到服務器內存佔用率高,利用率低的狀況,則可能會遇到內存佔用滿的問題。緩存


問題起因

產生問題的緣由有:服務器

持久鍵廢棄

這是致使此問題的最多見狀況。數據結構

有時候是開發人員的鍋,開發不規範,未給有時效性的鍵設置過時時間,後續又不進行手動刪除,鍵就成爲無人管的孤兒鍵了。dom

還多是整個業務慢慢被廢棄,不知道哪一天起,業務總體已再也不維護了,一批鍵天然也就沒用了。比這更嚴重的是,若是使用 List 傳遞數據,消費進程已被中止,但生產進程未同步中止,還在往 Redis 裏寫數據。

過時鍵未回收

這個緣由首先要談到 Redis 的兩種過時鍵刪除策略:

  • 惰性刪除:在讀取鍵時發現鍵已過時,則將其刪除。
  • 按期刪除:Redis 會從全部設置了過時時間的鍵中選取 100 個,刪除已過時的鍵,若是已過時的鍵超過 25 個,則再次進行此操做。 此刪除操做由配置項 hz 決定,Redis 默認每秒進行 10 次;

若是咱們產生過時鍵的速度很快,最多可致使 Redis 25% 的過時鍵沒有被及時刪除。


遍歷清除垃圾鍵

由上,明白了問題產生的緣由,解決 Redis 內存滿的方法就明確了:清除這些垃圾鍵。 因而就面臨着兩個問題:

如何遍歷鍵

對於查找鍵,咱們首先想到的是 KEYS,但 KEYS 的時間複雜度是O(n),n 是 Redis 內鍵的總數,若是 Redis 內鍵不少仍是會有性能問題,致使其餘命令被阻塞的。

這裏介紹一個鍵遍歷命令: SCAN

SCAN cursor:

0 => cursor, // cursor = 0 遍歷結束
1 => array(key1, key2...)

須要注意的是 SCAN 命令是在版本2.8.0 加入的,若是是以前的版本,能夠考慮解析 Redis 的 RDB 文件來獲取全部的鍵。有坑,參見我以前的文章:擴充你的工具箱 - 大行文件的處理

如何判斷鍵是否垃圾

咱們有三種異常鍵須要處理:

  • 過時鍵:這些鍵會在被 SCAN 到時被自動刪除,再也不考慮。若是是解析 RDB 文件獲取到的鍵,在查詢時也會被自動刪除;
  • 長時間未讀寫的鍵,極可能是業務再也不須要的鍵;
  • 佔用大量內存的鍵,有多是在不停地寫,但未消費。

這裏介紹 Redis 的另外一個命令 OBJECT,使用它能夠從內部查看 key 對象的狀態。使用 OBJECT IDLETIME key 來獲取 key 的閒置時間,咱們能夠判斷 key 閒置時間大於一個時間段(根據業務自定)的爲已廢棄。

此外還能使用 OBJECT REFCOUNT key獲取 key 引用所儲存的值的次數,OBJECT ENCODING key 獲取 key 儲存的值所使用的內部表示。

獲取鍵大小

而獲取 Redis 某鍵佔用內存大小,則經過另外一個命令 DEBUG OBJECT 來獲取,此命令會返回比OBJECT命令更詳細的內部數據。

DEBUG OBJECT test
Value at:0x7fb0ee16ebd0 refcount:1 encoding:embstr serializedlength:6 lru:12362780 lru_seconds_idle:4

結果包括內存地址、引用數、內部編碼表示、序列化後的長度、最近最少使用標識值,閒置時間,咱們能夠解析此結果串來獲取對應的數據。

須要注意,key 做爲複合鍵擁有大量字段時使用 DEBUG 命令計算內存會使 Redis 阻塞較長時間,且 Redis 官方並不建議在客戶端使用此命令

咱們也能夠先使用 TYPE key 獲取鍵的類型,再根據類型獲取其鍵的大小,如對字符串使用LEN,對 哈希表使用HLEN

要注意在刪除特別大的複合鍵時,建議先逐步清空鍵內的字段,防止因字段過多,Redis 阻塞較長時間。

管道加速

Redis 支持 pipeline 管道技術,一次 請求/響應 服務器能實現處理並響應多個請求。這樣就能夠將多個命令同時發送到服務器,不等待回覆,直接在最後獲取多個結果。

PHP 中使用 MULTI(Redis::PIPELINE)EXEC() 命令來實現管道;

腳本實現

下面是個簡單的腳本:

$redis = new Redis();
$redis->connect('127.0.0.1');
do {
    $keys = $redis->scan($cursor);

    $pipeline = $redis->multi(Redis::PIPELINE);
    foreach ($keys as $key) {
        $idle_time = $redis->object('idletime', $key);
        if ($idle_time > 180 * 24 * 3600) {
            $pipeline->del($key);
        }
        // todo 判斷類型進而判斷佔用內存大小,再刪除
    }
    $pipeline->exec();
} while ($cursor != 0);

從根源避免問題

以上的腳本確定也會在刪除鍵時影響 Redis 的效率,最好的狀況仍是從根源就避免此類狀況,如下是一些建議:

  • 規範化開發;

    首先是鍵命名要規範,讓人見名知義,這樣在人工排錯或刪除時也有判斷依據,而後最好有完善的 Redis 鍵文檔,以保證業務在很長時間,經手多人後也能資料可查。

  • 使用 HashSet 替代 Key-Value

    將業務中某一族的鍵以 HashSet 的方式存儲,以替代普通的 key-value 類型。不只能夠省去爲每一個鍵設置前綴以節約內存,也便於統一管理。

  • 有時效性的鍵注意設置過時時間;

  • 合理設置定時清除過時鍵頻率 hz,在 Redis 不作多餘操做的狀況下,使過時鍵儘可能能被刪除;

  • 作好 Redis 內存的監控,在達到某個閾值時查找問題並解決。


小結

最後多絮叨兩句經驗:

Redis假死

我在使用守護進程時 Redis 有假死狀況,PHP 和 Redis 都不報錯,但命令都返回 false,這種狀況可使用 Redis 的 ping() 命令,來探測 Redis 鏈接是否還在,若是不在則再創建新的鏈接。此問題極可能是由服務器配置引發的,若是您有知道此問題的起因或有好的解決辦法,煩請指點一二。

危險命令

不要在沒看文檔的狀況下在線上使用 Redis 命令,例如 debug segfault,別問我怎麼知道的。

嗯,但願你們都能處理好跟 Redis 這個好朋友的關係。

關於本文有什麼問題能夠在下面留言交流,若是您以爲本文對您有幫助,能夠點擊下面的 推薦 支持一下我。博客一直在更新,歡迎 關注

相關文章
相關標籤/搜索