redis筆記-數據庫之過期鍵刪除策略

2016-1-7 by Atlas


基礎指令篇提到過EXPIRE、PEXPIRE、EXPIREAT、PEXPIREAT四個過期鍵的命令,表達過期鍵刪除策略前先重溫一下這個四個命令的詳細過程。

* 設置過期時間

  • EXPIRE ---轉換成---> PEXPIRE
def EXPIRE(key,ttl_in_sec):
        // 將TTL從秒轉換成毫秒
        ttl_in_ms = sec_to_ms(ttl_in_sec)
        // 調用PEXPIRE
        PEXPIRE(key,ttl_in_ms)
  • PEXPIRE ---轉換成---> PEXPIREAT
def PEXPIRE(key,ttl_in_ms):
        // 獲取以毫秒計算的當前UNIX時間戳
        now_ms = get_current_unix_timestamp_in_ms()
        // 當前時間加上TTL,得出毫秒格式的鍵過期時間
        PEXPIREAT(key,now_ms+tll_in_ms)
  • EXPIREAT ---轉換成---> PEXPIREAT
def EXPIREAT(key,expire_time_in_sec):
        // 將過期時間從秒轉換成毫秒
        expire_time_in_ms = sec_to_ms(expire_time_in_sec)
        // 調用PEXPIREAT
        PEXPIREAT(key,expire_time_in_ms)

EXPIRE、PEXPIRE、EXPIREAT都適配成PEXPIREAT,由PEXPIREAT函數具體實現鍵的過期操作。

def PEXPIREAT(key,expire_time_in_ms):
        // 如果給定的鍵不存在於鍵空間,那麼不能設置過期時間
        if key not in redisDb.dict:
                return 0
        // 在過期字典中關聯鍵和過期時間
        redisDb.expires[key} = expire_time_in_ms
        // 過期時間設置成功
        return 1

* 過期鍵刪除策略

redis默認刪除策略組合是(惰性刪除 + 定期刪除)。

  • 定時刪除

策略:在設置鍵的過期時間的同時,創建一個定時器,讓定時器在鍵的過期時間來臨時,立即執行對鍵的刪除操作。
優點:對內存友好,保證過期鍵會儘可能快地被刪除,並釋放過期鍵所佔用的內存。
缺點:對CPU時間不友好,佔用太多CPU時間,影響服務器的響應時間和吞吐量。

  • 惰性刪除

策略:放任過期鍵不管,每次從鍵空間讀寫操作時,都檢查鍵是否過期,如果過期,刪除該鍵,如果沒有過期,返回該鍵。
優點:對CPU時間友好,讀寫操作鍵時纔對鍵進行過期檢查,刪除過期鍵的操作只會在非做不可的情況下進行。
缺點:對內存不友好,只要鍵不刪除,就不會釋放內存,浪費太多內存,有內存泄漏風險。
實現:所有讀寫數據庫的redis命令在執行前都會調用expireIfNeeded函數對輸入鍵進行檢查,如果輸入鍵已經過期,那麼expireIfNeeded函數就將過期鍵刪除;如果輸入鍵未過期,那麼expireIfNeeded函數不作爲。

  • 定期刪除

策略:對定時刪除策略和惰性刪除策略的一種整合和折中。每隔一段時間執行一次刪除過期鍵操作,並通過限制刪除操作執行的時長和頻率來減少刪除操作對CPU時間的影響;通過定期刪除過期鍵,有效減少了因爲過期鍵而帶來的內存浪費。
難點:確定刪除操作執行的時長和頻率。執行太頻繁,執行時間過長,就會退化成定時刪除策略;執行得太少,執行時間太短,又會和惰性刪除策略一樣存在內存浪費的情況。
redis服務器使用惰性刪除和定期刪除兩種策略,通過配合使用,很好地在合理使用CPU時間和避免浪費內存之間取得平衡。
實現:
1)在規定的時間內,分多次遍歷服務器中的各個數據庫;
2)從數據庫的expires字典中隨機檢查一部分鍵的過期時間;
3)刪除其中的過期鍵。
僞代碼:

# 默認每次檢查的數據庫數量
DEFAULT_DB_NUMBERS = 16
# 默認每個數據檢查的鍵數量
DEFAULT_KEY_NUMBERS = 20
# 全局變量,記錄檢查進度
current_db = 0
def activeExpireCycle():
        # 初始化要檢查的數據庫數量
        # 如果服務器的數據庫數量比DEFAULT_DB_NUMBERS小
        # 那麼以服務器的數據庫數量爲準
        if server.dbnum < DEFAULT_DB_NUMBERS:
                db_numbers = server.dbnum
        else:
                db_numbers = DEFAULT_DB_NUMBERS
        # 遍歷各個數據庫
        for i  in range(db_numbers):
                # 如果current_db的值等於服務器的數據庫數量
                # 表示檢查程序已經遍歷了服務器的所有數據庫一次
                # 將current_db重置爲0,開始新一輪遍歷
                if current_db == server.dbnum:
                        current_db = 0
                # 獲取當前要處理的數據庫
                redisDb = server.db[current_db]
                # 將數據庫索引增1,指向下一個要處理的數據庫
                current_db +=1
                # 檢查數據庫鍵
                for j in range(DEFAULT_KEY_NUMBERS):
                        # 如果數據庫中沒有過期鍵,那麼跳過這個數據庫
                        if redisDb.expires.size() == 0:break
                        # 隨機獲取一個帶有過期時間的鍵
                        key_with_ttl = redisDb.expires.get_random_key()
                        # 檢查鍵是否過期,如果過期就刪除
                        if is_expired(key_with_ttl):
                                delete_key(key_with_ttl)
                        # 已經達到本次刪除操作時間上限,停止處理
                        if reach_time_limit():return

* RDB功能對過期鍵的處理

如果服務器開啓RDB功能。

  • 生成RDB文件時:
    在執行SAVE命令或者BGSAVE命令創建一個新的RDB文件時,程序會對數據庫中的鍵進行檢查,已過期的鍵不會被保存到新創建的RDB文件中。
  • 載入RDB文件時:
    1)如果服務器以主服務器模式運行,那麼載入RDB文件時,程序會對文件中保存的鍵進行檢查,未過期的鍵會被載入到數據庫中,而過期的鍵則被忽略,所以過期鍵對載入RDB文件的主服務器不會造成影響。
    2)如果服務器以從服務器模式運行,那麼載入RDB文件時,文件中保存的所有鍵,不論是否過期,都會被載入到數據庫中。不過,主服務器在進行數據同步的時候,從服務器的數據庫就會被清空,所以一般來講,過期鍵對載入RDB文件的從服務器也不會造成影響。

* AOF功能對過期鍵的處理

如果服務器以AOF持久化模式運行。
如果數據庫中的某個鍵已經過期,但它還沒有被惰性刪除或者定期刪除,那麼AOF文件不會因爲這個過期鍵而產生任何影響。
當過期鍵被惰性刪除或者定期刪除之後,程序會向AOF文件中追加一條DEL命令,來顯示地記錄該鍵已被刪除。
AOF重寫的過程中,程序會對數據庫中的鍵進行檢查,已經過期的鍵不會被保存到重寫後的AOF文件中。

* 複製功能對過期鍵的處理

主服務器在刪除一個過期鍵之後,會顯示地向所有的從服務器發送一個DEL命令,告知從服務器刪除這個過期鍵。
從服務器在執行客戶端發送的讀命令時,即使碰到過期鍵也不會將過期鍵刪除,而是繼續像處理未過期的鍵一樣來處理過期鍵。
從服務器只有接收到主服務器發來的DEL命令後,纔會刪除過期鍵。

參考文獻:《redis設計與實現》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章