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

2016-1-7 by Atlasredis


基礎指令篇提到過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默認刪除策略組合是(惰性刪除 + 按期刪除)。dom

  • 定時刪除

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

  • 惰性刪除

策略:聽任過時鍵無論,每次從鍵空間讀寫操做時,都檢查鍵是否過時,若是過時,刪除該鍵,若是沒有過時,返回該鍵。
優勢:對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功能。unix

  • 生成RDB文件時:
    在執行SAVE命令或者BGSAVE命令建立一個新的RDB文件時,程序會對數據庫中的鍵進行檢查,已過時的鍵不會被保存到新建立的RDB文件中。
  • 載入RDB文件時:
    1)若是服務器以主服務器模式運行,那麼載入RDB文件時,程序會對文件中保存的鍵進行檢查,未過時的鍵會被載入到數據庫中,而過時的鍵則被忽略,因此過時鍵對載入RDB文件的主服務器不會形成影響。
    2)若是服務器以從服務器模式運行,那麼載入RDB文件時,文件中保存的全部鍵,不管是否過時,都會被載入到數據庫中。不過,主服務器在進行數據同步的時候,從服務器的數據庫就會被清空,因此通常來說,過時鍵對載入RDB文件的從服務器也不會形成影響。

* AOF功能對過時鍵的處理

若是服務器以AOF持久化模式運行。
若是數據庫中的某個鍵已通過期,但它尚未被惰性刪除或者按期刪除,那麼AOF文件不會由於這個過時鍵而產生任何影響。
當過時鍵被惰性刪除或者按期刪除以後,程序會向AOF文件中追加一條DEL命令,來顯示地記錄該鍵已被刪除。
AOF重寫的過程當中,程序會對數據庫中的鍵進行檢查,已通過期的鍵不會被保存到重寫後的AOF文件中。code

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

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

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

相關文章
相關標籤/搜索