2016-1-7 by Atlasredis
基礎指令篇提到過EXPIRE、PEXPIRE、EXPIREAT、PEXPIREAT四個過時鍵的命令,表達過時鍵刪除策略前先重溫一下這個四個命令的詳細過程。數據庫
def EXPIRE(key,ttl_in_sec): // 將TTL從秒轉換成毫秒 ttl_in_ms = sec_to_ms(ttl_in_sec) // 調用PEXPIRE PEXPIRE(key,ttl_in_ms)
def PEXPIRE(key,ttl_in_ms): // 獲取以毫秒計算的當前UNIX時間戳 now_ms = get_current_unix_timestamp_in_ms() // 當前時間加上TTL,得出毫秒格式的鍵過時時間 PEXPIREAT(key,now_ms+tll_in_ms)
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功能。unix
- 生成RDB文件時:
在執行SAVE命令或者BGSAVE命令建立一個新的RDB文件時,程序會對數據庫中的鍵進行檢查,已過時的鍵不會被保存到新建立的RDB文件中。- 載入RDB文件時:
1)若是服務器以主服務器模式運行,那麼載入RDB文件時,程序會對文件中保存的鍵進行檢查,未過時的鍵會被載入到數據庫中,而過時的鍵則被忽略,因此過時鍵對載入RDB文件的主服務器不會形成影響。
2)若是服務器以從服務器模式運行,那麼載入RDB文件時,文件中保存的全部鍵,不管是否過時,都會被載入到數據庫中。不過,主服務器在進行數據同步的時候,從服務器的數據庫就會被清空,因此通常來說,過時鍵對載入RDB文件的從服務器也不會形成影響。
若是服務器以AOF持久化模式運行。
若是數據庫中的某個鍵已通過期,但它尚未被惰性刪除或者按期刪除,那麼AOF文件不會由於這個過時鍵而產生任何影響。
當過時鍵被惰性刪除或者按期刪除以後,程序會向AOF文件中追加一條DEL命令,來顯示地記錄該鍵已被刪除。
AOF重寫的過程當中,程序會對數據庫中的鍵進行檢查,已通過期的鍵不會被保存到重寫後的AOF文件中。code
主服務器在刪除一個過時鍵以後,會顯示地向全部的從服務器發送一個DEL命令,告知從服務器刪除這個過時鍵。
從服務器在執行客戶端發送的讀命令時,即便碰到過時鍵也不會將過時鍵刪除,而是繼續像處理未過時的鍵同樣來處理過時鍵。
從服務器只有接收到主服務器發來的DEL命令後,纔會刪除過時鍵。server
參考文獻:《redis設計與實現》