Redis逾時刪除三種可能的答案,它們分別代表了三種不同的刪除策略:
定時刪除:在設定鍵的過期時間的同時,建立一個定時器(timer),讓定時器在鍵的過期時間來臨時,立即執行對鍵的刪除操作。
惰性刪除:放任鍵過期不管,但是每次從鍵空間中獲取鍵時,都檢查取得的鍵是否過期,如果過期的話,就刪除該鍵;如果沒有過期,就返回該鍵。
定期刪除:每隔一段時間,程式就會對資料庫進行一次檢查,刪除裡面的過期鍵。至於要刪除多少過期鍵,以及要檢查多少個資料庫,則由演算法決定。
在這三種策略中,第一種和第三種為主動刪除策略,而第二種則為被動刪除策略。
定時刪除:
定時刪除策略對記憶體是最友善的:透過使用定時器,定時刪除策略可以保證過期鍵會盡可能快速地被刪除,並釋放過期鍵所佔用的記憶體。
另一方面,定時刪除策略的缺點是,它對CPU時間是最不友善的:在過期鍵比較多的情況下,刪除過期鍵這一行為可能會佔用相當一部分CPU時間,在記憶體不緊張但是CPU時間非常緊張的情況下,將CPU時間用在刪除和當前任務無關的過期鍵上,無疑會對伺服器的回應時間和吞吐量造成影響。
例如,如果正有大量的命令請求在等待伺服器處理,並且伺服器當前不缺少內存,那麼伺服器應該優先將CPU時間用在處理客戶端的命令請求上面,而不是用在刪除過期鍵上面。
除此之外,建立一個定時器需要用到Redis伺服器中的時間事件,而當前時間事件的實作方式-無序鍊錶,找出一個事件的時間複雜度為O(N) ——並不能有效率地處理大量時間事件。
因此,要讓伺服器建立大量的定時器,從而實現定時刪除策略,在現階段來說並不現實。
惰性刪除:
惰性刪除策略對CPU時間來說是最友善的:程式只會在取出鍵時才對鍵進行過期檢查,這可以確保刪除過期鍵的操作只會在非做不可的情況下進行,並且刪除的目標僅限於當前處理的鍵,這個策略不會在刪除其他無關的過期鍵上花費任何CPU時間。
惰性刪除策略的缺點是,它對記憶體是最不友善的:如果一個鍵已經過期,而這個鍵又仍然保留在資料庫中,那麼只要這個過期鍵不被刪除,它所佔用的記憶體就不會釋放。
在使用惰性刪除策略時,如果資料庫中有非常多的過期鍵,而這些過期鍵又恰好沒有被存取到的話,那麼它們也許永遠也不會被刪除(除非使用者手動執行FLUSHDB ),我們甚至可以將這種情況看作是一種內存洩漏——無用的垃圾數據佔用了大量的內存,而伺服器卻不會自己去釋放它們,這對於運行狀態非常依賴於內存的Redis伺服器來說,肯定不是一個好消息。
舉個例子,對於一些和時間有關的數據,例如日誌(log),在某個時間點之後,對它們的訪問就會大大減少,甚至不再訪問,如果這類過期數據大量地積壓在資料庫中,使用者以為伺服器已經自動將它們刪除了,但實際上這些鍵仍然存在,而且鍵所佔用的記憶體也沒有釋放,那麼造成的後果肯定是非常嚴重的。
定期刪除:
從上面對定時刪除和惰性刪除的討論來看,這兩種刪除方式在單一使用時都有明顯的缺陷:
·定時刪除佔用太多CPU時間,影響伺服器的回應時間和吞吐量。
·惰性刪除浪費太多內存,有內存洩漏的危險。
定期刪除策略是前兩種策略的一種整合和折中:
·定期刪除策略每隔一段時間執行一次刪除過期鍵操作,並透過限制刪除操作執行的時長和頻率來減少刪除操作對CPU時間的影響。
·除此之外,透過定期刪除過期鍵,定期刪除策略有效地減少了因為過期鍵而帶來的記憶體浪費。
定期刪除策略的困難是確定刪除操作執行的時長和頻率:
·如果刪除操作執行得太頻繁,或者執行的時間太長,定期刪除策略就會退化成定時刪除策略,以至於將CPU時間過度消耗在刪除過期鍵上面。
·如果刪除操作執行得太少,或是執行的時間太短,定期刪除策略又會和惰性刪除策略一樣,出現浪費記憶體的情況。
過期鍵的定期刪除策略由redis.c/activeExpireCycle函數實現,每當Redis的伺服器週期性操作redis.c/serverCron函數執行時,activeExpireCycle函數就會被調用,它在規定的時間內,分多次遍歷伺服器中的各個資料庫,從資料庫的expires字典中隨機檢查一部分鍵的過期時間,並刪除其中的過期鍵。
整個過程可以用偽代碼描述如下:
# 默认每次检查的数据库数量 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
activeExpireCycle函數的工作模式可以總結如下:
·函數每次執行時,都從一定數量的資料庫中取出一定數量的隨機鍵進行檢查,並刪除其中的過期鍵。
·全域變數current_db會記錄目前activeExpireCycle函數檢查的進度,並在下次activeExpireCycle函數呼叫時,接著上一次的進度進行處理。比方說,如果當前activeExpireCycle函數在遍歷10號資料庫時回傳了,那麼下次activeExpireCycle函數執行時,將從11號資料庫開始尋找並刪除過期鍵。
·隨著activeExpireCycle函數的持續執行,伺服器中的所有資料庫都會被檢查一遍,這時函數將current_db變數重設為0,然後再次開始新一輪的檢查工作。
更多Redis相關知識,請造訪Redis使用教學欄位!
以上是redis有定時刪除功能嗎的詳細內容。更多資訊請關注PHP中文網其他相關文章!