1. 程式人生 > 其它 >一文詳解Redis鍵過期策略

一文詳解Redis鍵過期策略

摘要:Redis採用的過期策略:惰性刪除+定期刪除。

本文分享自華為雲社群《Redis鍵過期策略詳解》,作者:JavaEdge。

1 設定帶過期時間的 key

# 時間複雜度:O(1),最常用方式
expire key seconds

# 字串獨有方式
setex(String key, int seconds, String value)

除了string獨有設定過期時間的方法,其他型別都需依靠expire方法設定時間,若:

  • 未設定時間,則快取永不過期
  • 設定過期時間,但之後又想讓快取永不過期,使用persist

設定key的過期時間。超時後,將會自動刪除該key。在Redis的術語中一個key的相關超時是volatile的。

生存時間可以通過使用 DEL 命令來刪除整個 key 來移除,或者被 SET 和 GETSET 命令覆寫(overwrite)。這意味著,如果一個命令只是修改(alter)一個帶生存時間的 key 的值而不是用一個新的 key 值來代替(replace)它的話,那麼生存時間不會被改變。 如使用 INCR 遞增key的值,執行 LPUSH 將新值推到 list 中或用 HSET 改變hash的field,這些操作都使超時保持不變。

  • 使用 PERSIST 命令可以清除超時,使其變成一個永久key
  • 若 key 被 RENAME 命令修改,相關的超時時間會轉移到新key
  • 若 key 被 RENAME 命令修改,比如原來就存在 Key_A,然後呼叫 RENAME Key_B Key_A 命令,這時不管原來 Key_A 是永久的還是設為超時的,都會由Key_B的有效期狀態覆蓋

注意,使用非正超時呼叫 EXPIRE/PEXPIRE 或具有過去時間的 EXPIREAT/PEXPIREAT 將導致key被刪除而不是過期(因此,發出的key事件將是 del,而不是過期)。

1.1 重新整理過期時間

對已經有過期時間的key執行EXPIRE操作,將會更新它的過期時間。有很多應用有這種業務場景,例如記錄會話的session。

1.2 Redis 之前的 2.1.3 的差異

在 Redis 版本之前 2.1.3 中,使用更改其值的命令更改具有過期集的金鑰具有完全刪除key的效果。由於現在修復的複製層中存在限制,因此需要此語義。

EXPIRE 將返回 0,並且不會更改具有超時集的鍵的超時。

1.3 返回值

  • 1,如果成功設定過期時間。
  • 0,如果key不存在或者不能設定過期時間。

1.4 示例

假設有一 Web 服務,對使用者最近訪問的最新 N 頁感興趣,這樣每個相鄰頁面檢視在上一個頁面之後不超過 60 秒。從概念上講,可以將這組頁面檢視視為使用者的導航會話,該會話可能包含有關ta當前正在尋找的產品的有趣資訊,以便你可以推薦相關產品。

可使用以下策略輕鬆在 Redis 中對此模式建模:每次使用者執行頁面檢視時,您都會呼叫以下命令:

MULTI
RPUSH pagewviews.user:<userid> http://.....
EXPIRE pagewviews.user:<userid> 60
EXEC

如果使用者空閒超過 60 秒,則將刪除該key,並且僅記錄差異小於 60 秒的後續頁面檢視。
此模式很容易修改,使用 INCR 而不是使用 RPUSH 的列表。

1.5 帶過期時間的 key

通常,建立 Redis 鍵時沒有關聯的存活時間。key將永存,除非使用者以顯式方式(例如 DEL 命令)將其刪除。EXPIRE 族的命令能夠將過期項與給定key關聯,但代價是該key使用的額外記憶體。當key具有過期集時,Redis 將確保在經過指定時間時刪除該key。

可使用 EXPIRE 和 PERSIST 命令(或其他嚴格命令)更新或完全刪除生存的關鍵時間。

1.6 過期精度

在 Redis 2.4 中,過期可能不準確,並且可能介於 0 到 1 秒之間。
由於 Redis 2.6,過期誤差從 0 到 1 毫秒。

1.7 過期和持久化

過期資訊的鍵儲存為絕對 Unix 時間戳(Redis 版本 2.6 或更高版本為毫秒)。這意味著即使 Redis 例項不處於活動狀態,時間也在流動。

要使過期工作良好,必須穩定計算機時間。若將 RDB 檔案從兩臺計算機上移動,其時鐘中具有大 desync,則可能會發生有趣的事情(如載入時載入到過期的所有key)。

即使執行時的例項,也始終會檢查計算機時鐘,例如,如果將一個key設定為 1000 秒,然後在將來設定計算機時間 2000 秒,則該key將立即過期,而不是持續 1000 秒。

2 Redis的key過期策略

  • 被動方式 - 惰性刪除
  • 主動方式 - 定期刪除

為保證 Redis 的高效能,所以不會單獨安排一個執行緒專門去刪除。

2.1 惰性刪除

key過期時不刪除,每次獲取key時,再去檢查是否過期。若過期,則刪除,返回null。

2.1.1 優點

刪除操作只發生在取key時,且只刪除當前key,所以對CPU時間佔用較少。此時刪除已非做不可,畢竟若還不刪除,就會獲取到已過期key。

當查詢該key時,Redis再很懶惰地檢查是否刪除。這和 Spring 的延遲初始化有著異曲同工之妙。

2.1.2 缺點

但這是不夠的,因為有過期key,永遠不會再訪問。若大量key在超出TTL後,很久一段時間內,都沒有被獲取過,則可能發生記憶體洩露(無用垃圾佔用了大量記憶體)。

無論如何,這些key都應過期,因此還需要定期 Redis 在具有過期集的key之間隨機測試幾個key。已過期的所有key將從key空間中刪除。

定時刪除

在設定key的過期時間的同時,為該key建立一個定時器,讓定時器在key的過期時間來臨時,對key進行刪除。

優點

保證記憶體被儘快釋放

缺點

  • 若過期key很多,刪除這些key會佔用很多的CPU時間,在CPU時間緊張的情況下,CPU不能把所有的時間用來做要緊的事兒,還需要去花時間刪除這些key
  • 定時器的建立耗時,若為每一個設定過期時間的key建立一個定時器(將會有大量的定時器產生),效能影響嚴重

所以沒人用

2.2 定期刪除

每隔一段時間執行一次刪除過期key操作。

優點

  • 通過限制刪除操作的時長和頻率,來減少刪除操作對CPU時間的佔用–處理"定時刪除"的缺點
  • 定期刪除過期key–處理"惰性刪除"的缺點

缺點

  • 在記憶體友好方面,不如"定時刪除"
  • 在CPU時間友好方面,不如"惰性刪除"

難點

  • 合理設定刪除操作的執行時長(每次刪除執行多長時間)和執行頻率(每隔多長時間做一次刪除)(這個要根據伺服器執行情況來定了)

每秒 10 次:

  1. 測試 20 個帶有過期的隨機鍵
  2. 刪除找到的所有已過期key
  3. 如果超過 25% 的key已過期,從步驟 1 重新開始

這是個微不足道的概率演算法,假設樣本為整個key空間,繼續過期,直到可能過期的key百分比低於 25%。
這意味著在任何給定時刻,使用記憶體的已過期的最大鍵量等於最大寫入操作量/秒除以 4。

定期刪除流程

void databasesCron(void) {
    /* Expire keys by random sampling. Not required for slaves
     * as master will synthesize DELs for us. */
    if (server.active_expire_enabled) {
        if (iAmMaster()) {
            activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW);
        } else {
            expireSlaveKeys();
        }
    }
#define ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP 20 /* Keys for each DB loop. */

void activeExpireCycle(int type) {
    config_keys_per_loop = ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP +
                           ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP/4*effort,
 
  // step1
  for (j = 0; j < dbs_per_call && timelimit_exit == 0; j++) {
    /* 如果沒有什麼可以過期,請儘快嘗試下一個資料庫 */
    // step2
    if ((num = dictSize(db->expires)) == 0) {
      db->avg_ttl = 0;
      // step3
      break;
    }
 
  }
}

對指定N個庫的每個庫隨機刪除≤指定個數的過期K。

  1. 遍歷每個資料庫(redis.conf中配置的"database"數量,預設16)
  2. 檢查當前庫中的指定個數的key(預設每個庫檢查20個key,這相當於該迴圈執行20次,迴圈體為下邊的描述)
  3. 若當前庫沒有一個K設定TTL,直接執行下一個庫的遍歷
  4. 隨機獲取一個設定TTL的K,檢查其是否過期,若過期,則刪除
  5. 判斷定期刪除操作是否已達到指定時長,若已達到,直接退出定期刪除

定期刪除程式中有個全域性變數current_db,記錄下一個將要遍歷的庫。預設16個庫,這次定期刪除遍歷了10個,那此時current_db就是11,下一次定期刪除就從第11個庫開始遍歷,假設current_db等於15,那之後就再從0號庫開始遍歷(此時current_db==0)

Redis採用的過期策略

惰性刪除+定期刪除。

惰性刪除流程

在進行get或setnx等操作時,先檢查key是否過期:

  • 若過期,刪除key,然後執行相應操作
  • 若沒過期,直接執行相應操作

RDB處理過期key

過期key對RDB無影響:

  • 從記憶體資料庫持久化資料到RDB檔案

持久化key之前,會檢查是否過期,過期的key不進入RDB檔案

  • 從RDB檔案恢復資料到記憶體資料庫

資料載入資料庫之前,會對K進行過期檢查,若過期,不匯入資料庫(主庫情況)

AOF處理過期K

過期key對AOF沒有任何影響。

從記憶體資料庫持久化到AOF檔案

  • 當key過期後,還沒有被刪除,此時進行執行持久化操作(該key不會進入aof檔案,因為沒有發生修改命令)
  • 當key過期後,在發生刪除操作時,程式會向aof檔案追加一條del命令(在將來的以aof檔案恢復資料的時候該過期的鍵就會被刪掉)

AOF重寫

重寫時,會先判斷key是否過期,已過期的key不會重寫到aof檔案

2.3 在複製鏈路和 AOF 檔案中處理過期的方式

為了在不犧牲一致性的情況下獲得正確行為,當key過期時,DEL 操作將同時在 AOF 檔案中合成並獲取所有附加的從節點。這樣,過期的這個處理過程集中到主節點中,還沒有一致性錯誤的可能性。

但是,雖然連線到主節點的從節點不會獨立過期key(但會等待來自master的 DEL),但它們仍將使用資料集中現有過期的完整狀態,因此,當選擇slave作為master時,它將能夠獨立過期key,完全充當master。

預設每臺Redis伺服器有16個數據庫,預設使用0號資料庫,所有的操作都是對0號資料庫的操作

# 設定資料庫數量。預設為16個庫,預設使用DB 0,可使用"select 1"來選擇一號資料庫
# 注意:由於預設使用0號資料庫,那麼我們所做的所有的快取操作都存在0號資料庫上,
# 當你在1號資料庫上去查詢的時候,就查不到之前set過的快取
# 若想將0號資料庫上的快取移動到1號資料庫,可以使用"move key 1"
databases 16
  • memcached只是用了惰性刪除,而redis同時使用了惰性刪除與定期刪除,這也是二者的一個不同點(可以看做是redis優於memcached的一點)
  • 對於惰性刪除而言,並不是只有獲取key的時候才會檢查key是否過期,在某些設定key的方法上也會檢查(eg.setnx key2 value2:該方法類似於memcached的add方法,如果設定的key2已經存在,那麼該方法返回false,什麼都不做;如果設定的key2不存在,那麼該方法設定快取key2-value2。假設呼叫此方法的時候,發現redis中已經存在了key2,但是該key2已經過期了,如果此時不執行刪除操作的話,setnx方法將會直接返回false,也就是說此時並沒有重新設定key2-value2成功,所以對於一定要在setnx執行之前,對key2進行過期檢查)

可是,很多過期key,你沒及時去查,定期刪除也漏掉了,大量過期key堆積記憶體,Redis記憶體殆耗盡!
因此記憶體滿時,還需有記憶體淘汰機制!這就是 Redis 自己 主動刪除 資料了!

 

點選關注,第一時間瞭解華為雲新鮮技術~